← Tất cả bài viết
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.

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, scrub và parallax — 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 start và end
Mỗi ScrollTrigger có start và end, 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ên | Vì sao |
|---|---|
Chỉ animate transform / opacity | Chạ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 đổi | Font/ảnh làm đổi vị trí phần tử |
Đặt invalidateOnRefresh: true cho giá trị vw/vh | Tí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.