รู้จัก CSS Scroll Snap มาตรฐาน CSS สำหรับการจับขึง จับขึง!~

Nuttavut Thongjor

หลายสถานการณ์ที่ผู้ใช้งานมักเล่นอยู่กับการสกอร์หน้าเพจไป ๆ มา ๆ และหลายสถานการณ์เช่นกันที่เราอยากกำหนดให้เกิดพฤติกรรมบางอย่างเมื่อการสกอร์รูดปรื๊ดนั้นเสร็จสิ้น

Image Carousel เป็นหนึ่งในตัวอย่างของการกำหนดพฤติกรรมเมื่อการสกอร์นั้นสิ้นสุดลง ทุกครั้งที่ผู้ใช้งานหยุดเลื่อนภาพเราอยากให้ภาพนั้นหยุดอยู่ตรงกลางพอดี พฤติกรรมที่กำหนดด้วยการสิ้นสุดของการหยุดสกอร์เช่นนี้สามารถใช้ CSS เข้าช่วยได้อย่างไร?

กำหนดโค้ด HTML ดังนี้

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>

ผลลัพธ์ที่เราต้องการได้รับเป็นเช่นด้านล่าง

Image Carousel

ก่อนหน้านี้เราต้องใช้ 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

Image Carousel

ตามรูปนั้นเราได้ใช้แกน x เป็นแกนสำหรับการจับ โดยกำหนดค่า snapping strictness เป็น mandatory โดยปรากฎเป็นโค้ด CSS ดังนี้

CSS
1* {
2 box-sizing: border-box;
3}
4
5body {
6 margin: 0;
7 padding: 0;
8}
9
10.container {
11 display: flex;
12 height: 100vh;
13 /* กำหนดการสกอร์ตามแนวแกน x และพยายามจับของยึดตามจุด snap เสมอ */
14 scroll-snap-type: x mandatory;
15 overflow-x: scroll;
16}
17
18/* ใช้สร้างเส้นดำตรงกลางเพื่อบอกตำแหน่งเฉย ๆ */
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) ตามปรากฎจากภาพนี้

scroll-snap-align

ตามโจทย์ของเรานั้น Image Carousel ต้องจัดรูปภาพให้อยู่กลาง scroll container เราจึงกำหนด scroll-snap-align ให้มีค่าเป็น center ดังนี้

CSS
1<!-- ในที่นี้ card คือรูปภาพ -- > .card {
2 flex: 0;
3 height: 80vh;
4 margin: 10vh 0;
5 scroll-snap-align: center;
6}

ตามที่เราคุยกันไปครับ แกนสำหรับการสกอร์ไม่จำเป็นต้องเป็นแกนนอน (แกน x) เสมอไป อาจเป็นแกนตั้ง (แกน y) ก็ได้ เช่นภาพข้างล่าง

Vertical Scroll

กำหนดให้ HTML ของเราเป็นแบบนี้

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)

CSS
1* {
2 box-sizing: border-box;
3}
4
5body {
6 margin: 0;
7 padding: 0;
8}
9
10.container {
11 // ตรงนี้
12 scroll-snap-type: y mandatory;
13 overflow-y: scroll;
14 height: 100vh;
15}
16
17.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}
28
29.card:nth-child(1) {
30 background: #37bc9b;
31}
32
33.card:nth-child(2) {
34 background: #e9573f;
35}
36
37.card:nth-child(3) {
38 background: #434a54;
39}
40
41.card:nth-child(4) {
42 background: #f6bb42;
43}
44
45.card:nth-child(5) {
46 background: #4a89dc;
47}

Scroll Padding และ Scroll Marging

สมมติตอนนี้เราใส่ส่วนของ header ลงไปใน scroll container ดังนี้ โดยกำหนดให้ .header มีความสูงทั้งสิ้น 10vh และมี position: fixed

CSS
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 ได้อย่างถูกต้อง ดังนี้

CSS
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}
10
11.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 ด้านล่าง

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
  • สรุป
  • เอกสารอ้างอิง