TL;DR

Carl Schoff (@snorklTV) vừa remix một bài kinh điển trong khóa ScrollTrigger Express: header spin vào khi scroll xuống, spin ngược khi scroll lên, nhưng không spin khi rời viewport. Trick: lúc element đang off-screen, nó âm thầm xoay về góc gốc để lần tới vào lại thì bắt đầu lại từ trạng thái sạch. Đây là pattern rất gọn để tách logic play khỏi logic reset trong GSAP ScrollTrigger.

Chuyện gì đang xảy ra

Bài remix xuất hiện trên X (Twitter) ngày 2026-04, từ tài khoản snorklTV — tên thật Carl Schoff, người từng làm 7 năm training tại GreenSock và đứng sau khóa ScrollTrigger Express. Demo cho thấy ba hành vi rõ rệt:

  • Scroll xuống → header spin-in vào view
  • Scroll lên → header spin-in ngược lại vào view
  • Scroll vượt khỏi viewport → không spin, header rời màn hình yên tĩnh

Cái bạn không nhìn thấy: khi element đã hoàn toàn ở ngoài viewport, nó được set lại góc xoay về đúng giá trị khởi đầu. Lần tới scroll tới, animation lại bắt đầu từ số 0 — không bị "nuốt" nửa chừng, không giật.

Tại sao pattern này đáng học

Cách làm naïve: một ScrollTrigger duy nhất với toggleActions: "play reverse play reverse" hoặc scrub: true. Kết quả: element spin mọi lúc — cả khi vào, khi đi, khi quay lại, khi rời hẳn. Trông ồn ào và phá cảm giác "vào thì có style, ra thì yên".

Pattern off-screen reset tách hai quan tâm:

  • Play logic chỉ chạy trong khoảng element còn trong viewport (enter / enter-back).
  • Reset logic chạy đúng lúc element vừa rời khỏi viewport, snap state về giá trị gốc — vô hình, không ai thấy.

Kết quả: mỗi lần header xuất hiện lại, nó luôn bắt đầu từ cùng một trạng thái. Animation lặp lại đẹp như lần đầu.

Cơ chế dưới mui xe

ScrollTrigger expose 4 callback định hướng tương ứng 4 biên của trigger box:

CallbackKhi nào fire
onEnterScroll xuống, element vào start
onLeaveScroll xuống, element vượt end (ra khỏi view phía trên)
onEnterBackScroll lên, element quay lại từ end
onLeaveBackScroll lên, element vượt start ngược (ra khỏi view phía dưới)

Cấu hình ngắn gọn dùng toggleActions — chuỗi 4 slot map đúng thứ tự onEnter onLeave onEnterBack onLeaveBack. Giá trị hợp lệ: play, pause, resume, reset, restart, complete, reverse, none. Mặc định là "play none none none".

Code pattern

Một tween, một ScrollTrigger, hai loại callback — play và reset tách bạch:

gsap.utils.toArray(".header").forEach(el => {
  const spinIn = gsap.from(el, {
    rotation: -180,
    opacity: 0,
    duration: 0.6,
    paused: true,
  });

  ScrollTrigger.create({
    trigger: el,
    start: "top 80%",
    end: "bottom 20%",
    onEnter:     () => spinIn.play(),
    onEnterBack: () => spinIn.play(),
    onLeave:     () => spinIn.pause(0),  // snap về start, off-screen
    onLeaveBack: () => spinIn.pause(0),
  });
});

Điểm mấu chốt: spinIn.pause(0) đưa tween về thời điểm 0 (trạng thái from) khi element đã rời viewport. Người dùng không thấy cú reset này vì nó xảy ra ngoài màn hình. Lần scroll tới, spinIn.play() chạy lại từ góc -180° như lần đầu.

Biến thể: hai ScrollTrigger riêng

Với trường hợp phức tạp hơn (start/end khác nhau cho play và reset), GSAP team khuyên tách thành hai ScrollTrigger độc lập: một cho play, một chỉ làm nhiệm vụ reset ở một vị trí scroll hoàn toàn khác. Pattern này cũng áp dụng được cho x, y, scale, màu sắc — không chỉ rotation.

Dùng ở đâu

  • Header section / tiêu đề có animation xuất hiện trên landing page
  • Card grid / pricing table muốn replay mỗi lần user cuộn lại
  • Portfolio / case-study site kiểu scroll-telling
  • Bất cứ chỗ nào muốn: "vào thì animate, ra thì im, vào lại thì animate lại từ đầu"

Cạm bẫy thường gặp

  • Đừng nhầm reset trong toggleActions với cú reset "vô hình" ngoài màn hình — toggleActions: "... reset" sẽ reset NGAY khi qua biên, có thể thấy được.
  • Nếu dùng scrub, pattern này không còn hợp — scrub đã nối progress với scroll, không có khái niệm play / pause rời rạc.
  • Với nhiều element, luôn dùng gsap.utils.toArray() + forEach để tạo tween + trigger riêng cho từng element — tránh share state giữa các phần tử.

Kết

Off-screen reset là một trong những pattern gọn nhất và hữu dụng nhất của ScrollTrigger: tách play và reset, đẩy reset ra vùng người dùng không nhìn thấy, giữ animation vào luôn "fresh". Nếu bạn đang làm scroll-driven site bằng GSAP thì đây là bài kinh điển đáng bookmark.

Nguồn: snorklTV trên X, GSAP ScrollTrigger docs, ScrollTrigger tips & mistakes, ScrollTrigger Express.