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
สารบัญ
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 */
}
Horizontal Carousel
.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%; } /* ✓ */