รู้จัก CSS Scroll Snap มาตรฐาน CSS สำหรับการจับขึง จับขึง!~
หลายสถานการณ์ที่ผู้ใช้งานมักเล่นอยู่กับการสกอร์หน้าเพจไป ๆ มา ๆ และหลายสถานการณ์เช่นกันที่เราอยากกำหนดให้เกิดพฤติกรรมบางอย่างเมื่อการสกอร์รูดปรื๊ดนั้นเสร็จสิ้น
Image Carousel เป็นหนึ่งในตัวอย่างของการกำหนดพฤติกรรมเมื่อการสกอร์นั้นสิ้นสุดลง ทุกครั้งที่ผู้ใช้งานหยุดเลื่อนภาพเราอยากให้ภาพนั้นหยุดอยู่ตรงกลางพอดี พฤติกรรมที่กำหนดด้วยการสิ้นสุดของการหยุดสกอร์เช่นนี้สามารถใช้ CSS เข้าช่วยได้อย่างไร?
กำหนดโค้ด HTML ดังนี้
1<div class="container">2 <img class="card" src="https://source.unsplash.com/collection/311028" />3 <img class="card" src="https://source.unsplash.com/collection/395888" />4 <img class="card" src="https://source.unsplash.com/collection/1999207" />5 <img class="card" src="https://source.unsplash.com/collection/827807" />6 <img class="card" src="https://source.unsplash.com/collection/1538150" />7 <img class="card" src="https://source.unsplash.com/collection/1424240" />8 <img class="card" src="https://source.unsplash.com/collection/1767181" />9</div>
ผลลัพธ์ที่เราต้องการได้รับเป็นเช่นด้านล่าง
ก่อนหน้านี้เราต้องใช้ JavaScript เข้าช่วยเพื่อให้ Image Carousel นั้นสมบูรณ์ ด้วยมาตรฐานของ CSS ตอนนี้เราสามารถใช้ scroll-snap
เข้าช่วยเพื่อกำหนดพฤติกรรมสัตว์ป่าของการหยุดสกอร์ได้อย่างไร้ที่ติ
กำหนด Scroll Container ด้วย scroll-snap-type
จากรูปของ Image Carousel ที่แสดงข้างต้น เมื่อเรากล่าวว่าต้องการให้รูปภาพหยุดอยู่ตรงกลางพอดีสามารถสื่อความได้อีกอย่างคือ ให้มีกล่องปรากฎในแนวนอนโดยกล่องนี้ผู้ใช้งานสามารถสกอร์เพื่อเลื่อนไปยังภาพอื่นได้ ภาพต่าง ๆ จึงถือว่าถูกบรรจุในกล่องดังกล่าวอีกที ทุกครั้งที่เราเลื่อนภาพในกล่องขอให้ภาพต่าง ๆ ถูกยึด (snap) ไว้กลางกล่องพอดีเป๊ะ ๆ นี่หละคือสิ่งที่ใจเรียกร้อง
จากการตีความหมายล่าสุดจึงสรุปได้ว่าสิ่งนึงที่เราต้องมีเสมอคือกล่องที่สามารถถูกเลื่อน (scroll) ได้ ต่อไปนี้เราจะเรียกกล่องนี้ว่า scroll container
แน่นอนว่า scroll container นั้นต้องสามารถสกอร์ได้ เราจึงมักเห็นการใส่ overflow: scroll
เสมอสำหรับอีลีเมนต์นี้ ทว่าตัวกล่องนั้นต้องมีการกำหนดด้วยว่าจะให้ข้อมูลข้างในไหลและถูกจับไว้ได้ (snap) ตามแกนใด
scroll-snap-type
คือพร็อพเพอร์ตี้ดังกล่าวที่เราพูดถึง เมื่ออีลีเมนต์ใดมีพร็อพเพอร์ตี้นี้สิ่งนั้นจะเป็น snap container โดยค่าแรกที่เราสามารถระบุได้คือค่าที่บอกว่าจะให้ข้อมูลภายในสกอร์ในทิศทางไหนและถูกจับตามแกนใด โดยค่าที่เป็นไปได้คือ x, y และ both
นอกจาก scroll-snap-type
จะสามารถระบุแกนได้แล้ว เรายังสามารถส่งค่าที่สองคือค่า snapping strictness หรือค่ากำหนดความเข้มงวดมากน้อยของการตรึงวัตถุ หากกำหนดค่านี้เป็น mandatory
ทุกการหยุดสกอร์จะถูกจับขึงเข้าสู่จุดที่เรากำหนดให้ snap (snap position) ทันที ในทางกลับกันเมื่อกำหนดค่านี้เป็น proximity
อีลีเมนต์จะถูกจับยึดก็ต่อเมื่อเข้าใกล้จุดยึดเท่านั้น กรณีไม่กำหนดค่าใดเลยค่าเริ่มต้นจะเป็น none
ตามรูปนั้นเราได้ใช้แกน x เป็นแกนสำหรับการจับ โดยกำหนดค่า snapping strictness เป็น mandatory โดยปรากฎเป็นโค้ด CSS ดังนี้
1* {2 box-sizing: border-box;3}45body {6 margin: 0;7 padding: 0;8}910.container {11 display: flex;12 height: 100vh;13 /* กำหนดการสกอร์ตามแนวแกน x และพยายามจับของยึดตามจุด snap เสมอ */14 scroll-snap-type: x mandatory;15 overflow-x: scroll;16}1718/* ใช้สร้างเส้นดำตรงกลางเพื่อบอกตำแหน่งเฉย ๆ */19.container::before {20 position: absolute;21 background-color: #000;22 left: calc(50% + 1.5px);23 z-index: -1;24 content: '';25 height: 100vh;26 width: 3px;27}
กำหนดจุด snap ของอีลีเมนต์ด้วย scroll-snap-align
เพราะว่ารูปภาพนั้นสามารถเลื่อนได้จากซ้ายไปขวา จึงเกิดตำแหน่งของรูปภาพข้างใน scroll container ที่สามารถถูกจับได้สามตำแหน่ง คือตำแหน่งซ้ายสุดของภาพ (ตำแหน่ง start) ตำแหน่งตรงกลางภาพ (เรียกว่าจุด center) และตำแหน่งท้ายสุดของภาพ (ตำแหน่ง end) ตามปรากฎจากภาพนี้
ตามโจทย์ของเรานั้น Image Carousel ต้องจัดรูปภาพให้อยู่กลาง scroll container เราจึงกำหนด scroll-snap-align
ให้มีค่าเป็น center
ดังนี้
1<!-- ในที่นี้ card คือรูปภาพ -- > .card {2 flex: 0;3 height: 80vh;4 margin: 10vh 0;5 scroll-snap-align: center;6}
ตามที่เราคุยกันไปครับ แกนสำหรับการสกอร์ไม่จำเป็นต้องเป็นแกนนอน (แกน x) เสมอไป อาจเป็นแกนตั้ง (แกน y) ก็ได้ เช่นภาพข้างล่าง
กำหนดให้ HTML ของเราเป็นแบบนี้
1<div class="container">2 <div class="card">Card#1</div>3 <div class="card">Card#2</div>4 <div class="card">Card#3</div>5 <div class="card">Card#4</div>6 <div class="card">Card#5</div>7</div>
แน่นอนว่าส่วนของ scroll container นั้นต้องกำหนดให้ scroll-snap-type เป็นไปตามแกน y เพราะการสกอร์นั้นเกิดขึ้นตามแนวดิ่ง แต่จากการสังเกตรูปพบว่าแต่ละ .card ที่อยู่ข้างในนั้นจะต้องถูกจับขึงติด container ที่จุดเริ่มต้นของมัน หรือพูดง่าย ๆ คือขอบบนสุดนั่นเอง (จุด start)
1* {2 box-sizing: border-box;3}45body {6 margin: 0;7 padding: 0;8}910.container {11 // ตรงนี้12 scroll-snap-type: y mandatory;13 overflow-y: scroll;14 height: 100vh;15}1617.card {18 display: flex;19 align-items: center;20 justify-content: center;21 // ตรงนี้22 scroll-snap-align: start;23 font-size: 10rem;24 color: white;25 border: 1px solid rgb(210, 210, 210);26 height: 100vh;27}2829.card:nth-child(1) {30 background: #37bc9b;31}3233.card:nth-child(2) {34 background: #e9573f;35}3637.card:nth-child(3) {38 background: #434a54;39}4041.card:nth-child(4) {42 background: #f6bb42;43}4445.card:nth-child(5) {46 background: #4a89dc;47}
Scroll Padding และ Scroll Marging
สมมติตอนนี้เราใส่ส่วนของ header ลงไปใน scroll container ดังนี้ โดยกำหนดให้ .header มีความสูงทั้งสิ้น 10vh และมี position: fixed
1<article class="container">2 <header class="header">Header</header>3 <section class="card">Card#1</section>4 <section class="card">Card#2</section>5 <section class="card">Card#3</section>6 <section class="card">Card#4</section>7 <section class="card">Card#5</section>8</article>
เพราะว่า .header นั้นมีความสูง 10vh มันจึงกินพื้นที่ของการสกอร์ไปแล้วส่วนหนึ่ง เมื่อแต่ละ .card ถูกเลื่อนมันจึงไม่สามารถแสดงผลตัวมันเองออกมาด้วยความสูง 100vh ได้ เพราะ .header บดบังพื้นที่ส่วนหนึ่งของการสกอร์ไปแล้ว
เพื่อให้ .card สามารถแสดงผลการสกอร์ได้อย่างถูกต้อง เราจึงต้องเลื่อนส่วนแสดงผลสำหรับการจับขึงลงมา 10vh ด้วยการเพิ่ม padding เข้าไป ทำให้เกิดส่วนของการสกอร์จริงห่างจากขอบบนของ container อยู่ 10vh เรียกส่วนพื้นที่นี้ว่า snapport
อาศัยความสามารถของ scroll-padding
ทำให้เรากำหนด snapport ได้อย่างถูกต้อง ดังนี้
1.header {2 display: flex;3 justify-content: center;4 align-items: center;5 position: fixed;6 height: 10vh;7 width: 100%;8 background-color: #fff;9}1011.container {12 scroll-snap-type: y mandatory;13 overflow-y: scroll;14 height: 100vh;15 // ตรงนี้16 scroll-padding: 10vh;17}
เช่นเดียวกับ scroll-padding
เรายังมี scroll-marging
แต่ความสามารถนั้นแตกต่างกันเสมือน padding และ margin นั่นหละ
สรุป
การใช้ CSS Scroll Snap นั้นช่วยให้ชีวิตการจับขึง จับขึง และจับขึง นั้นง่ายขึ้น แต่มีสิ่งหนึ่งที่กวนใจเราเสมอคือการที่ตลาดมีเว็บเบราว์เซอร์หลายเจ้าเนี่ยหละ จะใช้ไม่ใช้เชิญพวกเธอว์ดู Browser Support ด้านล่าง
เอกสารอ้างอิง
CSS Scroll Snap Module Level 1. Retrieved Nov, 30, 2018, from https://drafts.csswg.org/css-scroll-snap/
Well-Controlled Scrolling with CSS Scroll Snap. Retrieved Nov, 30, 2018, https://developers.google.com/web/updates/2018/07/css-scroll-snap
สารบัญ
- กำหนด Scroll Container ด้วย scroll-snap-type
- กำหนดจุด snap ของอีลีเมนต์ด้วย scroll-snap-align
- Scroll Padding และ Scroll Marging
- สรุป
- เอกสารอ้างอิง