ภาพรวม
ผู้ใช้คาดหวังว่าจะได้รับการตอบสนองแบบภาพทันทีเมื่อโต้ตอบกับองค์ประกอบ UI เช่น ภาพสไลด์หรือแกลเลอรี การสแนปการเลื่อนแบบดั้งเดิมจะแสดงผลลัพธ์หลังจากที่ท่าทางสัมผัสการเลื่อนเสร็จสมบูรณ์และองค์ประกอบหยุดนิ่งเท่านั้น การใช้เหตุการณ์การสแนปการเลื่อน โดยเฉพาะ scrollsnapchanging จะช่วยให้คุณแสดงความคิดเห็นแบบเรียลไทม์ระหว่างท่าทางการเลื่อนได้ ซึ่งจะไฮไลต์เป้าหมายการสแนปที่รอดำเนินการก่อนที่ผู้ใช้จะปล่อยการแตะหรือเมาส์
การใช้งาน
1. ฟังภาษาscrollsnapchanging
แนบ Listener เหตุการณ์สำหรับ scrollsnapchanging กับคอนเทนเนอร์การเลื่อน เหตุการณ์นี้จะเริ่มทำงานเมื่อเบราว์เซอร์พิจารณาว่ามีแนวโน้มที่จะเลือกเป้าหมายสแนปใหม่
const container = document.querySelector('#gallery');
const thumbnails = document.querySelectorAll('.thumbnail');
const items = document.querySelectorAll('.gallery-item');
container.addEventListener('scrollsnapchanging', (event) => {
// Highlight pending snap target during scroll for real-time feedback.
const pendingTarget = event.snapTargetInline;
const index = [...items].indexOf(pendingTarget);
if (index === -1 || !thumbnails[index]) return;
// Use lightweight class toggle to avoid layout thrashing during rapid events.
// Note: aria-current is NOT toggled here. It tracks the settled "current"
// item, which is updated in the scrollsnapchange handler below.
thumbnails.forEach((thumb) => thumb.classList.remove('pending'));
thumbnails[index].classList.add('pending');
});
ตัวอย่างนี้ใช้ snapTargetInline เนื่องจากแกลเลอรีเลื่อนในแนวนอน หากคอนเทนเนอร์การเลื่อนเลื่อนในแนวตั้ง ให้ใช้ snapTargetBlock แทน
2. ฟังภาษาscrollsnapchange
หากต้องการสรุปสถานะเมื่อท่าทางการเลื่อนเสร็จสมบูรณ์และองค์ประกอบสแนปจริง ให้ฟังเหตุการณ์ scrollsnapchange ซึ่งจำเป็นสำหรับการสร้างสถานะใช้งานสุดท้าย
container.addEventListener('scrollsnapchange', (event) => {
// Promote pending state to active on scroll completion.
const snappedTarget = event.snapTargetInline;
const index = [...items].indexOf(snappedTarget);
if (index === -1 || !thumbnails[index]) return;
// Establish final active state and clean up pending.
thumbnails.forEach((thumb) => {
thumb.classList.remove('pending', 'active');
thumb.removeAttribute('aria-current');
});
thumbnails[index].classList.add('active');
thumbnails[index].setAttribute('aria-current', 'true');
});
3. สถานะเริ่มต้นของการซิงค์
เมื่อโหลดหน้าเว็บ เบราว์เซอร์อาจคืนค่าตำแหน่งการเลื่อน (เช่น ผ่านการข้ามประวัติหรือลิงก์ตำแหน่งเฉพาะ) ทั้งเหตุการณ์ scrollsnapchange และ scroll จะไม่เริ่มทำงานโดยอัตโนมัติ เรียกใช้การตรวจสอบเรขาคณิตแบบครั้งเดียวเพื่อซิงค์ UI กับตำแหน่งการเลื่อนเริ่มต้น
// Note: For item.offsetLeft to be relative to the container,
// the container MUST be the offsetParent (e.g., `position: relative`).
const findClosestItemIndex = () => {
// Center-distance assumes scroll-snap-align: center on items.
// For start-aligned snap, compare scrollLeft to item.offsetLeft directly.
const containerCenter = container.scrollLeft + container.clientWidth / 2;
let closestIndex = 0;
let minDistance = Infinity;
items.forEach((item, index) => {
const itemCenter = item.offsetLeft + item.offsetWidth / 2;
const distance = Math.abs(containerCenter - itemCenter);
if (distance < minDistance) {
minDistance = distance;
closestIndex = index;
}
});
return closestIndex;
};
const initActiveItem = () => {
const closestIndex = findClosestItemIndex();
if (!thumbnails[closestIndex]) return;
thumbnails.forEach((thumb) => {
thumb.classList.remove('pending', 'active');
thumb.removeAttribute('aria-current');
});
thumbnails[closestIndex].classList.add('active');
thumbnails[closestIndex].setAttribute('aria-current', 'true');
};
if (document.readyState === 'complete') {
initActiveItem();
} else {
window.addEventListener('load', initActiveItem, { once: true });
}
กลยุทธ์สำรอง
สำหรับเบราว์เซอร์ที่ไม่รองรับ scrollsnapchanging โดยค่าเริ่มต้น UI จะไม่แสดงความคิดเห็นอย่างรวดเร็วในระหว่างท่าทางสัมผัสการเลื่อน และ UI ที่ลิงก์จะไม่ซิงค์กับเนื้อหา
บังคับ: ระบุวิดีโอสำรองสำหรับเบราว์เซอร์ที่ไม่รองรับ มิฉะนั้น UI ที่ลิงก์จะไม่ซิงค์กับเนื้อหา
ควรจำลองลักษณะแบบเรียลไทม์ของ scrollsnapchanging ด้วย Listener เหตุการณ์ scroll ควบคู่ไปกับ requestAnimationFrame และการคำนวณระยะทางเรขาคณิตเพื่อระบุเป้าหมายการสแนปที่ใกล้ที่สุดขณะที่ผู้ใช้กำลังเลื่อนดู
if ('onscrollsnapchanging' in Element.prototype) {
// Use native scroll snap events
} else {
// Fallback: use scroll + requestAnimationFrame for eager feedback
// (assumes the same container, thumbnails, items defined in step 1)
let scrollTimeout;
let rafId = null;
const promotePendingToActive = () => {
const closestIndex = findClosestItemIndex();
if (!thumbnails[closestIndex]) return;
thumbnails.forEach((thumb) => {
thumb.classList.remove('pending', 'active');
thumb.removeAttribute('aria-current');
});
thumbnails[closestIndex].classList.add('active');
thumbnails[closestIndex].setAttribute('aria-current', 'true');
};
container.addEventListener('scroll', () => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
rafId = null;
const closestIndex = findClosestItemIndex();
if (!thumbnails[closestIndex]) return;
// DO NOT forget to clean up stale pending classes
thumbnails.forEach((thumb) => thumb.classList.remove('pending'));
thumbnails[closestIndex].classList.add('pending');
});
// Debounce fallback for browsers that don't support scrollend
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(promotePendingToActive, 100);
}, { passive: true });
// Fallback: use Baseline `scrollend` event to promote pending to active cleanly where supported
container.addEventListener('scrollend', () => {
clearTimeout(scrollTimeout);
promotePendingToActive();
});
}
scroll + requestAnimationFrame ฟอลแบ็กทางเรขาคณิตจะจำลองลักษณะการทำงานของการคาดคะเนการสแนปแบบเนทีฟอย่างใกล้ชิด รวมถึงการจัดการการเลื่อนแบบเป็นโปรแกรมอย่างถูกต้อง เนื่องจากฟังก์ชันอย่าง scrollIntoView จะเริ่มเหตุการณ์ scroll โดยอัตโนมัติระหว่างการดำเนินการ UI จึงจะซิงค์กันอย่างราบรื่นตลอดภาพเคลื่อนไหวการเลื่อนโดยไม่ต้องใช้ตรรกะที่กำหนดเองเพิ่มเติม