Scrollytelling เป็นเทคนิคยอดนิยมที่ใช้ในการสร้างประสบการณ์การใช้งานเว็บที่น่าสนใจและสมจริง ซึ่งเกี่ยวข้องกับการสร้างภาพเคลื่อนไหวขององค์ประกอบในหน้าเว็บขณะที่ผู้ใช้เลื่อนดู เพื่อบอกเล่าเรื่องราวหรือแนะนำผู้ใช้ผ่านการเล่าเรื่องได้อย่างมีประสิทธิภาพ ภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน CSS ช่วยให้คุณสร้างเอฟเฟกต์เหล่านี้ได้โดยตรงใน CSS โดยไม่ต้องพึ่งพา JavaScript ภาพเคลื่อนไหวจะควบคุมโดยตำแหน่งการเลื่อน ไม่ใช่นาฬิกาที่อิงตามเวลา ซึ่งช่วยให้มั่นใจได้ว่าภาพเคลื่อนไหวจะซิงค์กับการเลื่อนของผู้ใช้เสมอ
วิธีการนำไปใช้งาน
หากต้องการสร้างประสบการณ์การเล่าเรื่องแบบ Scrollytelling คุณต้องมีองค์ประกอบ 2 ชุด ได้แก่ องค์ประกอบหนึ่งเพื่อติดตามตำแหน่งการเลื่อน และอีกองค์ประกอบหนึ่งเพื่อสร้างภาพเคลื่อนไหว
ก่อนอื่น ให้กำหนด view-timeline ที่มีชื่อในองค์ประกอบที่ต้องการติดตาม ซึ่งจะทำหน้าที่เป็นตัวขับเคลื่อนภาพเคลื่อนไหว
#tracked {
section:nth-child(1){ view-timeline: --tl-1 block; }
section:nth-child(2){ view-timeline: --tl-2 block; }
section:nth-child(3){ view-timeline: --tl-3 block; }
section:nth-child(4){ view-timeline: --tl-4 block; }
section:nth-child(5){ view-timeline: --tl-5 block; }
}
ถัดไป ให้ใช้ภาพเคลื่อนไหวกับองค์ประกอบที่ต้องการทำให้เคลื่อนไหว และลิงก์องค์ประกอบเหล่านั้นกับไทม์ไลน์ที่คุณเพิ่งสร้างโดยใช้พร็อพเพอร์ตี้ animation-timeline
#animated {
section {
animation: animate-in auto linear both, animate-out auto linear forwards;
animation-range: entry 25% cover 50%, exit 50% exit 75%;
}
section:nth-child(1){ animation-timeline: --tl-1; }
section:nth-child(2){ animation-timeline: --tl-2; }
section:nth-child(3){ animation-timeline: --tl-3; }
section:nth-child(4){ animation-timeline: --tl-4; }
section:nth-child(5){ animation-timeline: --tl-5; }
}
เพื่อให้ animation-timeline สามารถอ้างอิงไทม์ไลน์ที่มีชื่อได้ ไทม์ไลน์เหล่านั้นจะต้องอยู่ในขอบเขตเดียวกัน คุณสามารถใช้พร็อพเพอร์ตี้ timeline-scope ในระดับบนสุดที่ใช้ร่วมกันเพื่อให้ไทม์ไลน์พร้อมใช้งานสำหรับองค์ประกอบทั้งหมดที่ต้องการ องค์ประกอบ :root มักเป็นตัวเลือกที่ดีสำหรับกรณีนี้
html {
timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}
สุดท้าย คุณสามารถใช้พร็อพเพอร์ตี้ animation-range เพื่อระบุช่วงเวลาที่แน่นอนของไทม์ไลน์ที่ภาพเคลื่อนไหวควรทำงาน ซึ่งจะช่วยให้คุณควบคุมได้อย่างละเอียดว่าภาพเคลื่อนไหวจะเริ่มทำงานเมื่อใดและจะดำเนินไปอย่างไร
#animated section {
animation-range: entry 25% cover 50%, exit 50% exit 75%;
}
โค้ดตัวอย่าง
html {
timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}
#tracked {
section:nth-child(1){ view-timeline: --tl-1 block; }
section:nth-child(2){ view-timeline: --tl-2 block; }
section:nth-child(3){ view-timeline: --tl-3 block; }
section:nth-child(4){ view-timeline: --tl-4 block; }
section:nth-child(5){ view-timeline: --tl-5 block; }
}
@keyframes animate-in {
from { scale: 0.5; opacity: 0; transform: rotateY(-180deg); }
to { transform: rotateY(0deg); }
}
@keyframes animate-out {
to { translate: 100% 0; opacity: 0; }
}
#animated {
section {
animation: animate-in auto linear both, animate-out auto linear forwards;
animation-range: entry 25% cover 50%, exit 50% exit 75%;
backface-visibility: hidden;
}
section:nth-child(1){ animation-timeline: --tl-1; }
section:nth-child(2){ animation-timeline: --tl-2; }
section:nth-child(3){ animation-timeline: --tl-3; }
section:nth-child(4){ animation-timeline: --tl-4; }
section:nth-child(5){ animation-timeline: --tl-5; }
}
/* MANDATORY Copy-Paste Safety: Disable continuous storytelling motion for sensitive users */
@media (prefers-reduced-motion: reduce) {
#animated section {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}
แนวทางปฏิบัติแนะนำ
เมื่อใช้ภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน คุณควรทำตามแนวทางปฏิบัติแนะนำต่อไปนี้เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ราบรื่นและเข้าถึงได้
- ควรรวมการตรวจหาฟีเจอร์: เบราว์เซอร์บางรายการไม่รองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน ใช้
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%))เพื่อตรวจสอบการรองรับและระบุตัวเลือกสำรองสำหรับเบราว์เซอร์ที่ไม่รองรับ- ต้องรวมการตรวจสอบ
(animation-range: 0% 100%)ไว้ที่นี่เพื่อกรองเบราว์เซอร์ที่รองรับเพียงบางส่วนออก - อย่าใช้แพ็กเกจ
scroll-timeline-polyfillสำหรับกลยุทธ์สำรอง เนื่องจากฟีเจอร์ยังไม่สมบูรณ์และมีปัญหาที่ทราบอยู่แล้วจำนวนมาก - หากภาพเคลื่อนไหวถือเป็นเพียงการตกแต่ง ให้เลือกใช้การเพิ่มประสิทธิภาพแบบต่อเนื่อง และอย่าระบุทางเลือกสำรอง
- ต้องรวมการตรวจสอบ
- ควรเคารพค่ากำหนดของผู้ใช้: ผู้ใช้บางรายต้องการให้มีการเคลื่อนไหวน้อยลงบนเว็บ ใช้คิวรีสื่อ
prefers-reduced-motionเพื่อปิดใช้หรือลดภาพเคลื่อนไหวสำหรับผู้ใช้เหล่านี้ - ควรพยายามทำให้เฉพาะพร็อพเพอร์ตี้ CSS ที่มีประสิทธิภาพเคลื่อนไหว: เพื่อให้ภาพเคลื่อนไหวราบรื่นที่สุด ให้ใช้พร็อพเพอร์ตี้ที่เคลื่อนไหวซึ่งจัดการได้โดยเทรด Compositor ของเบราว์เซอร์ เช่น
transformและopacityการทำให้พร็อพเพอร์ตี้อื่นๆ เช่นwidthหรือheightเคลื่อนไหวอาจทำให้เกิดปัญหาด้านประสิทธิภาพ - ควรใช้ลำดับการประกาศที่ถูกต้อง: เมื่อใช้พร็อพเพอร์ตี้ชวเลข
animationให้ประกาศanimation-timelineและanimation-rangeหลังจากพร็อพเพอร์ตี้ดังกล่าวเพื่อป้องกันไม่ให้ชวเลขรีเซ็ตไทม์ไลน์
เมื่อใช้พร็อพเพอร์ตี้ view-timeline เพื่อสร้างภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน ให้ทำดังนี้
- ควรใช้ CSS
<dashed-ident>สำหรับชื่อ - ไม่บังคับ ระบุแกนที่จะติดตามอย่างชัดเจน: เมื่อไม่ได้กำหนดเป้าหมายแกน
blockเริ่มต้น (เช่น ในแถบเลื่อนแนวนอน) ให้ระบุแกนที่จะติดตามด้วยview-timeline-axisอย่างชัดเจน - ควรตรวจสอบว่าขอบเขตของการค้นหาใช้งานได้: เมื่อองค์ประกอบที่ประกาศ
view-timelineไม่ใช่ระดับบนสุดแบบ Flat Tree ขององค์ประกอบที่เคลื่อนไหว ให้เพิ่มระดับการมองเห็นชื่อของview-timelineโดยใช้timeline-scopeในระดับบนสุดที่ใช้ร่วมกัน
กลยุทธ์สำรอง
สำหรับเบราว์เซอร์ที่ไม่รองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน คุณสามารถใช้โหมดสำรองเพื่อสร้างเอฟเฟกต์ภาพขึ้นมาใหม่ได้ โดยปกติแล้ว ฟังก์ชันสำรองจะสร้างขึ้นด้วย Listener การเลื่อน (สำหรับเอฟเฟกต์ ScrollTimeline) หรือ IntersectionObserver API (สำหรับเอฟเฟกต์ ViewTimeline)
ในเบราว์เซอร์ที่มีการรองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อนในตัว ให้ใช้การติดตั้งใช้งาน CSS แบบเนทีฟเสมอ เนื่องจากมีประสิทธิภาพมากกว่า
โปรดทราบว่าคุณไม่สามารถสร้างเอฟเฟกต์ทั้งหมดขึ้นมาใหม่ได้โดยใช้วิธีการสำรอง
สำหรับ Use Case นี้โดยเฉพาะ สคริปต์ต่อไปนี้จะใช้โหมดสำรองสำหรับเบราว์เซอร์ที่ไม่รองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน โดยใช้ IntersectionObserver เพื่อติดตามระดับการมองเห็นขององค์ประกอบ #tracked section แต่ละรายการ และอัปเดตพร็อพเพอร์ตี้ transform ของ #animated section ที่เกี่ยวข้องตามลำดับ
const animatedSections = document.querySelectorAll('#animated section');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const sectionIndex = Array.from(document.querySelectorAll('#tracked section')).indexOf(entry.target);
if (sectionIndex !== -1) {
const animatedSection = animatedSections[sectionIndex];
const ratio = entry.intersectionRatio;
// Animate-in
animatedSection.style.opacity = ratio;
animatedSection.style.transform = `scale(${0.5 + ratio * 0.5}) rotateY(${-180 + ratio * 180}deg)`;
// Animate-out
if (ratio < 0.5) {
animatedSection.style.translate = `${(0.5 - ratio) * 2 * 100}% 0`;
} else {
animatedSection.style.translate = '0 0';
}
}
});
}, { threshold: Array.from({length: 101}, (_, i) => i / 100) });
document.querySelectorAll('#tracked section').forEach(section => {
observer.observe(section);
});
และ CSS ที่มาพร้อมกัน:
#animated section {
opacity: 0;
transform: scale(0.5) rotateY(-180deg);
backface-visibility: hidden;
}
/* MANDATORY Copy-Paste Safety: Ensure content remains fully visible and legible for assistive technologies or users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
#animated section {
opacity: 1 !important;
transform: none !important;
translate: 0 0 !important;
}
}
ซึ่งจะช่วยให้ภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อนมีความแม่นยำมากขึ้นสำหรับเบราว์เซอร์ที่ไม่รองรับฟีเจอร์ CSS แบบเนทีฟ เพื่อให้ผู้ใช้ทุกคนได้รับประสบการณ์การใช้งานที่สอดคล้องกันมากขึ้น การใช้ชุดเกณฑ์สำหรับ IntersectionObserver ช่วยให้เราติดตามตำแหน่งการเลื่อนได้อย่างแม่นยำยิ่งขึ้นและสร้างภาพเคลื่อนไหวที่ราบรื่นขึ้น