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

Category: guide

CSS Scroll-Driven Animations

Animate elements ตาม scroll position โดยใช้ animation-timeline: scroll() และ view() — ไม่ต้องการ JavaScript หรือ ScrollTrigger

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

สารบัญ

Scroll-Driven Animations คืออะไร

แทนที่จะให้ JS คอย listen scroll events และปรับ style เอง CSS Scroll-Driven Animations ผูก animation progress เข้ากับ scroll position โดยตรง — ไม่มี JavaScript, ไม่มี requestAnimationFrame, ไม่ jank


scroll() — Progress Bar

/* Classic scroll progress bar ที่ด้านบนหน้า */
@keyframes grow-x {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.progress-bar {
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 3px;
  background: #2563eb;
  transform-origin: left;

  animation: grow-x linear;
  animation-timeline: scroll();   /* ✓ ผูกกับ scroll ของ document */
  animation-fill-mode: both;
}

scroll() Parameters

/* scroll(scroller axis) */
animation-timeline: scroll();           /* root document, block axis */
animation-timeline: scroll(root);       /* explicit: root document */
animation-timeline: scroll(nearest);    /* nearest scrolling ancestor */
animation-timeline: scroll(self);       /* element นั้นเอง (ถ้า overflow: scroll) */

animation-timeline: scroll(block);      /* vertical scroll (default) */
animation-timeline: scroll(inline);     /* horizontal scroll */
animation-timeline: scroll(y);          /* vertical (same as block for LTR) */
animation-timeline: scroll(x);          /* horizontal */

/* Combined */
animation-timeline: scroll(nearest inline);

view() — Animate เมื่อ Element เข้า Viewport

/* Fade-in เมื่อ element scroll เข้ามาใน viewport */
@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.animate-on-scroll {
  animation: fade-in-up 0.6s ease-out both;
  animation-timeline: view();

  /* เริ่ม animate เมื่อ element เริ่มเข้า viewport,
     จบเมื่อ element ผ่าน 30% ของ viewport */
  animation-range: entry 0% entry 30%;
}

animation-range

/* Ranges ที่ใช้ได้ */
animation-range: cover;       /* ตลอดที่ element อยู่ใน scroll container */
animation-range: contain;     /* ตั้งแต่ element เข้าจนถึงออกทั้งหมด */
animation-range: entry;       /* ขณะที่ element กำลังเข้า viewport */
animation-range: exit;        /* ขณะที่ element กำลังออก viewport */
animation-range: entry-crossing;
animation-range: exit-crossing;

/* กำหนด start และ end แยก */
animation-range-start: entry 0%;
animation-range-end: entry 50%;

/* หรือ shorthand */
animation-range: entry 0% entry 50%;

Named Timeline — Scroll Container แยก

/* เมื่อต้องการ animate element จาก scroll ของ container อื่น */
.scroll-container {
  overflow-y: scroll;
  height: 300px;
  scroll-timeline-name: --my-scroll;   /* ✓ ตั้งชื่อ */
  scroll-timeline-axis: block;
}

.animated-element {
  animation: slide-in linear;
  animation-timeline: --my-scroll;     /* ✓ อ้างอิงชื่อ */
}

Stagger Effect

/* animate หลาย elements พร้อมกัน แต่ delay ต่างกัน */
@keyframes slide-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

.card {
  animation: slide-up ease-out both;
  animation-timeline: view();
  animation-range: entry 0% entry 40%;
}

.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 100ms; }
.card:nth-child(3) { animation-delay: 200ms; }
.card:nth-child(4) { animation-delay: 300ms; }

Parallax Effect

/* Image เลื่อนช้ากว่า scroll */
@keyframes parallax {
  from { transform: translateY(-20%); }
  to   { transform: translateY(20%); }
}

.hero-image {
  animation: parallax linear;
  animation-timeline: view();
  animation-range: cover;   /* ตลอดที่ element อยู่ใน view */
}

เปรียบกับ GSAP ScrollTrigger

/* CSS Scroll-Driven: ง่ายกว่า ไม่ต้องการ JS */
.element {
  animation: fade-in ease-out both;
  animation-timeline: view();
  animation-range: entry 0% entry 50%;
}

/* GSAP ScrollTrigger: ยืดหยุ่นกว่า, รองรับ browser เก่า */
gsap.from('.element', {
  opacity: 0, y: 30,
  scrollTrigger: {
    trigger: '.element',
    start: 'top 80%',
    end: 'top 50%',
    scrub: true,
  },
});

ใช้ CSS Scroll-Driven เมื่อ:

  • animation ง่าย (fade, slide, scale)
  • ต้องการ performance สูงสุด (runs on compositor thread)
  • ไม่ต้องรองรับ browser เก่า

ใช้ GSAP เมื่อ:

  • animation ซับซ้อน (pin, callback, sequence)
  • ต้องรองรับ Safari 15 หรือ Firefox เก่า
  • ต้องการ onEnter, onLeave callbacks

Browser Support

Chrome 115+, Edge 115+, Safari 18+ — รองรับ production ได้แล้วใน 2025+

/* @supports fallback */
@supports not (animation-timeline: scroll()) {
  .animate-on-scroll { opacity: 1; transform: none; }
}

/* หรือ detect ด้วย JS ก่อนใส่ class */
if (CSS.supports('animation-timeline', 'scroll()')) {
  document.body.classList.add('scroll-animations');
}

Reduce Motion

/* เคารพ prefers-reduced-motion เสมอ */
@media (prefers-reduced-motion: reduce) {
  .animate-on-scroll {
    animation: none;
    opacity: 1;
    transform: none;
  }
}