ข้ามไปเนื้อหาหลัก

Category: reference

CSS Scroll Snap

CSS Scroll Snap สำหรับ carousel, full-page scroll และ horizontal scroll — scroll-snap-type, scroll-snap-align, mandatory vs proximity, และ scroll-padding

· อ่านประมาณ 2 นาที

สารบัญ

CSS Scroll Snap คืออะไร

Scroll Snap บังคับให้ scroll หยุดที่จุดที่กำหนดโดยอัตโนมัติ — ใช้งานโดยไม่ต้องการ JavaScript

/* Container */
.carousel {
  overflow-x: scroll;
  scroll-snap-type: x mandatory;  /* axis + behavior */
}

/* Item */
.carousel-item {
  scroll-snap-align: start;       /* จุดที่ snap */
}

scroll-snap-type

/* Axis */
scroll-snap-type: x mandatory;    /* horizontal เท่านั้น */
scroll-snap-type: y mandatory;    /* vertical เท่านั้น */
scroll-snap-type: both mandatory; /* ทั้งสอง axis */

/* Behavior */
scroll-snap-type: x mandatory;    /* mandatory: snap ทุกครั้ง */
scroll-snap-type: x proximity;    /* proximity: snap เมื่อใกล้มากพอ */

mandatory — scroll ต้องหยุดที่ snap point เสมอ ใช้กับ carousel หรือ full-page scroll

proximity — snap เฉพาะเมื่อ scroll position ใกล้ snap point — เหมาะกับ list ที่ scroll ได้อิสระ


scroll-snap-align

.item {
  scroll-snap-align: start;   /* edge ซ้าย/บน align กับ container */
  scroll-snap-align: center;  /* กลาง item align กับ center container */
  scroll-snap-align: end;     /* edge ขวา/ล่าง align กับ container */
  scroll-snap-align: none;    /* ไม่ snap */
}

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch; /* iOS momentum scroll */
  gap: 1rem;
  padding: 0 1rem;
  /* ซ่อน scrollbar */
  scrollbar-width: none;
}
.carousel::-webkit-scrollbar { display: none; }

.carousel-item {
  flex: 0 0 80%;         /* item ไม่หดตัว */
  max-width: 320px;
  scroll-snap-align: start;
}

Full-page Scroll

.page-container {
  height: 100dvh;         /* dynamic viewport height */
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
}

.section {
  height: 100dvh;
  scroll-snap-align: start;
  display: flex;
  align-items: center;
  justify-content: center;
}

scroll-padding — เว้นพื้นที่ Header

/* มี sticky header ความสูง 64px */
.page-container {
  scroll-snap-type: y mandatory;
  scroll-padding-top: 64px;   /* offset snap point ลงมา 64px */
}

/* หรือใช้กับ <html> สำหรับ anchor link scroll */
html {
  scroll-padding-top: 80px;
}

scroll-margin — Offset สำหรับ Item

/* แทนที่จะ padding ที่ container ใช้ margin ที่ item */
.section {
  scroll-margin-top: 64px;  /* เว้นพื้นที่จาก top 64px */
}

scroll-snap-stop — บังคับหยุดทุก Item

/* โดยปกติ scroll เร็วอาจข้ามหลาย items */
.carousel-item {
  scroll-snap-stop: always;  /* หยุดทุก item ไม่ว่า scroll เร็วแค่ไหน */
  scroll-snap-stop: normal;  /* default — อาจข้าม item ได้ */
}

ใช้กับ mandatory เพื่อให้ user ต้องผ่านทุก item


JavaScript Control

// Scroll ไปยัง item ที่เจาะจง
const item = document.querySelector<HTMLElement>('.carousel-item:nth-child(3)');
item?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });

// ตรวจว่า item ไหนอยู่ใน view
const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      entry.target.classList.toggle('is-active', entry.isIntersecting);
    });
  },
  { threshold: 0.5 }
);

document.querySelectorAll('.carousel-item').forEach((item) => observer.observe(item));

Browser Support & Gotchas

Chrome 69+, Firefox 68+, Safari 11+ — ใช้ production ได้เลย

/* ❌ scroll-snap ไม่ทำงานถ้า overflow ไม่ถูกต้อง */
.container {
  display: flex;
  /* ❌ ลืม overflow */
}

/* ✓ ต้อง overflow: auto หรือ scroll */
.container {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}

/* ❌ item ที่ใหญ่กว่า container ทำให้ snap ผิดพลาด */
.item { width: 120%; }  /* ❌ */
.item { width: 100%; }  /* ✓ */