← Tất cả bài viết

[ Blog ]

24 tháng 6, 2026

4 phút đọc

Hướng dẫn GSAP ScrollTrigger: Pin, Scrub & Parallax

Học GSAP ScrollTrigger qua ba mẫu đứng sau các website đoạt giải — pin section, scrub timeline và parallax — trong React/Next.js.

GSAPScrollTriggerReactAnimationNext.js
Hướng dẫn GSAP ScrollTrigger: Pin, Scrub & Parallax

GSAP ScrollTrigger là công cụ hữu ích nhất cho animation theo scroll trên web. Khi đã hiểu ba mẫu — pin, scrubparallax — bạn có thể tái tạo phần lớn hiệu ứng scroll thấy trên các site đoạt giải. Hướng dẫn GSAP ScrollTrigger này dựng cả ba trong React/Next.js, và giải thích cú pháp start/end mà ai cũng từng bối rối.

Nếu bạn muốn nền cuộn mượt bên dưới các hiệu ứng này, hãy kết hợp với cuộn mượt bằng Lenis — hai thứ được thiết kế để chạy cùng nhau.

Cài đặt

npm install gsap @gsap/react

Trong React, luôn animate bên trong useGSAP để các tween được scope và tự dọn dẹp:

'use client';
import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(useGSAP, ScrollTrigger);

Hiểu startend

Mỗi ScrollTrigger có startend, viết theo dạng "[trigger] [scroller]":

  • "top bottom" → khi đỉnh của trigger chạm đáy viewport.
  • "top center" → đỉnh của trigger tới giữa màn hình theo chiều dọc.
  • "bottom top" → đáy của trigger chạm đỉnh viewport.

Bạn cũng có thể dùng offset và giá trị tương đối: start: 'top 80%', end: '+=600' (cuộn thêm 600px sau điểm start). Thêm markers: true khi đang code — nó vẽ vị trí trigger lên màn hình và loại bỏ 90% phần đoán mò.

Mẫu 1 — Ghim (pin) một section

Pin cố định một phần tử tại chỗ trong khi trang vẫn cuộn — nền tảng của các section kể chuyện kiểu "dính".

function Pinned() {
  const ref = useRef<HTMLDivElement>(null);
  useGSAP(() => {
    ScrollTrigger.create({
      trigger: ref.current,
      start: 'top top',
      end: '+=100%',   // giữ ghim trong một viewport cuộn
      pin: true,
    });
  }, { scope: ref });

  return <section ref={ref} className="h-screen">Nội dung được ghim</section>;
}

end: '+=100%' giữ ghim thêm một chiều cao viewport. Tăng giá trị để "giữ" lâu hơn.

Mẫu 2 — Scrub một timeline

scrub gắn tiến trình animation vào thanh cuộn, nên người dùng kéo chuyển động bằng cách cuộn. Kết hợp với pin là ra section ghim-và-animate kinh điển.

function ScrubReveal() {
  const ref = useRef<HTMLDivElement>(null);
  useGSAP(() => {
    const tl = gsap.timeline({
      scrollTrigger: {
        trigger: ref.current,
        start: 'top top',
        end: '+=150%',
        scrub: 1,     // 1 = mượt theo kịp; true = khóa cứng vào scroll
        pin: true,
      },
    });
    tl.from('.headline', { yPercent: 100, opacity: 0 })
      .to('.bg', { scale: 1.2, ease: 'none' }, 0);
  }, { scope: ref });

  return <section ref={ref}>{/* .headline, .bg */}</section>;
}

scrub: 1 thêm một giây làm mượt để animation lướt thay vì giật — đây là mẹo khiến section scrub có cảm giác cao cấp.

Mẫu 3 — Parallax

Parallax đơn giản là các lớp di chuyển ở tốc độ khác nhau. Với scrub, mỗi lớp chỉ một dòng:

useGSAP(() => {
  gsap.to('.layer-back', {
    yPercent: -30,
    ease: 'none',
    scrollTrigger: { trigger: '.scene', start: 'top bottom', end: 'bottom top', scrub: true },
  });
}, { scope: ref });

Cho lớp nền di chuyển ít hơn lớp trước (-30 so với -60) là ảo giác chiều sâu xuất hiện. Luôn parallax bằng yPercent/transform — đừng dùng top/margin — để giữ trên compositor.

Hiệu năng & dọn dẹp

Vài quy tắc giúp ScrollTrigger mượt và không lỗi:

NênVì sao
Chỉ animate transform / opacityChạy trên GPU compositor; không gây layout thrash
Dùng useGSAP({ scope })Tự revert tween + trigger khi unmount (không rò rỉ)
Gọi ScrollTrigger.refresh() sau khi layout đổiFont/ảnh làm đổi vị trí phần tử
Đặt invalidateOnRefresh: true cho giá trị vw/vhTính lại khi resize

Trong App Router, ScrollTrigger chỉ sống trong client component. Đăng ký plugin một lần, và để scope của useGSAP lo việc dọn dẹp qua các lần đổi route.

Câu hỏi thường gặp

Khi nào dùng scrub: true so với scrub: 1?

true khóa animation 1:1 với thanh cuộn (chính xác, có thể hơi cứng). Một con số thêm bấy nhiêu giây làm mượt (scrub: 1 là mức hợp lý cho hầu hết hiệu ứng reveal).

Vì sao section ghim của tôi bị nhảy?

Gần như luôn do layout đổi sau khi ScrollTrigger đã đo trang (font tải trễ, ảnh không có kích thước, hoặc chưa nối thư viện cuộn mượt). Đặt kích thước ảnh và gọi ScrollTrigger.refresh() khi mọi thứ ổn định.

ScrollTrigger có chạy với cuộn mượt không?

Có — chạy cả hai từ một vòng lặp RAF và gọi ScrollTrigger.update() mỗi khung hình. Xem hướng dẫn cuộn mượt để biết cách cài chính xác.


Pin, scrub và parallax là ba "viên gạch" đứng sau gần như mọi trải nghiệm scroll đáng học hỏi. Nắm vững cú pháp start/end, giữ mọi thứ trên transform, và bạn có thể dựng những câu chuyện scroll như được làm thủ công. Muốn mức chuyển động này trong sản phẩm của bạn? Xem cách tôi làm việc với các đội nhóm.