Babel Coder

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

beginner

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

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

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

<div class="container">
  <img class="card" src="https://source.unsplash.com/collection/311028" />
  <img class="card" src="https://source.unsplash.com/collection/395888" />
  <img class="card" src="https://source.unsplash.com/collection/1999207" />
  <img class="card" src="https://source.unsplash.com/collection/827807" />
  <img class="card" src="https://source.unsplash.com/collection/1538150" />
  <img class="card" src="https://source.unsplash.com/collection/1424240" />
  <img class="card" src="https://source.unsplash.com/collection/1767181" />
</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 ดังนี้

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
}

.container {
  display: flex;
  height: 100vh;
  /* กำหนดการสกอร์ตามแนวแกน x และพยายามจับของยึดตามจุด snap เสมอ */
  scroll-snap-type: x mandatory;
  overflow-x: scroll;
}

/* ใช้สร้างเส้นดำตรงกลางเพื่อบอกตำแหน่งเฉย ๆ */
.container::before {
  position: absolute;
  background-color: #000;
  left: calc(50% + 1.5px);
  z-index: -1;
  content: '';
  height: 100vh;
  width: 3px;
}

กำหนดจุด snap ของอีลีเมนต์ด้วย scroll-snap-align

เพราะว่ารูปภาพนั้นสามารถเลื่อนได้จากซ้ายไปขวา จึงเกิดตำแหน่งของรูปภาพข้างใน scroll container ที่สามารถถูกจับได้สามตำแหน่ง คือตำแหน่งซ้ายสุดของภาพ (ตำแหน่ง start) ตำแหน่งตรงกลางภาพ (เรียกว่าจุด center) และตำแหน่งท้ายสุดของภาพ (ตำแหน่ง end) ตามปรากฎจากภาพนี้

scroll-snap-align

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

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

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

Vertical Scroll

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

<div class="container">
  <div class="card">Card#1</div>
  <div class="card">Card#2</div>
  <div class="card">Card#3</div>
  <div class="card">Card#4</div>
  <div class="card">Card#5</div>
</div>

แน่นอนว่าส่วนของ scroll container นั้นต้องกำหนดให้ scroll-snap-type เป็นไปตามแกน y เพราะการสกอร์นั้นเกิดขึ้นตามแนวดิ่ง แต่จากการสังเกตรูปพบว่าแต่ละ .card ที่อยู่ข้างในนั้นจะต้องถูกจับขึงติด container ที่จุดเริ่มต้นของมัน หรือพูดง่าย ๆ คือขอบบนสุดนั่นเอง (จุด start)

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
}

.container {
  // ตรงนี้
  scroll-snap-type: y mandatory;
  overflow-y: scroll;
  height: 100vh;
}

.card {
  display: flex;
  align-items: center;
  justify-content: center;
  // ตรงนี้
  scroll-snap-align: start;
  font-size: 10rem;
  color: white;
  border: 1px solid rgb(210, 210, 210);
  height: 100vh;
}

.card:nth-child(1) {
  background: #37bc9b;
}

.card:nth-child(2) {
  background: #e9573f;
}

.card:nth-child(3) {
  background: #434a54;
}

.card:nth-child(4) {
  background: #f6bb42;
}

.card:nth-child(5) {
  background: #4a89dc;
}

Scroll Padding และ Scroll Marging

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

<article class="container">
  <header class="header">Header</header>
  <section class="card">Card#1</section>
  <section class="card">Card#2</section>
  <section class="card">Card#3</section>
  <section class="card">Card#4</section>
  <section class="card">Card#5</section>
</article>

เพราะว่า .header นั้นมีความสูง 10vh มันจึงกินพื้นที่ของการสกอร์ไปแล้วส่วนหนึ่ง เมื่อแต่ละ .card ถูกเลื่อนมันจึงไม่สามารถแสดงผลตัวมันเองออกมาด้วยความสูง 100vh ได้ เพราะ .header บดบังพื้นที่ส่วนหนึ่งของการสกอร์ไปแล้ว

เพื่อให้ .card สามารถแสดงผลการสกอร์ได้อย่างถูกต้อง เราจึงต้องเลื่อนส่วนแสดงผลสำหรับการจับขึงลงมา 10vh ด้วยการเพิ่ม padding เข้าไป ทำให้เกิดส่วนของการสกอร์จริงห่างจากขอบบนของ container อยู่ 10vh เรียกส่วนพื้นที่นี้ว่า snapport

อาศัยความสามารถของ scroll-padding ทำให้เรากำหนด snapport ได้อย่างถูกต้อง ดังนี้

.header {
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  height: 10vh;
  width: 100%;
  background-color: #fff;
}

.container {
  scroll-snap-type: y mandatory;
  overflow-y: scroll;
  height: 100vh;
  // ตรงนี้
  scroll-padding: 10vh;
}

เช่นเดียวกับ 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


แสดงความคิดเห็นของคุณ


No any discussions