lenis-smooth-scroll

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Skill: lenis-smooth-scroll

Skill: lenis-smooth-scroll

Thiết lập Lenis làm nền cuộn vật lý (physics-based) cho toàn trang, và đồng bộ đúng cách với GSAP ScrollTrigger — đây là bước hay bị làm sai nhất khi ráp 2 thư viện này chung.
将 Lenis 设置为全页面基于物理(physics-based)的滚动基础,并以正确方式与 GSAP ScrollTrigger 同步——集成这两个库时,这是最容易做错的一步。

WHAT

WHAT

Purpose và context

目的与上下文

  • Purpose: dựng Lenis làm nền cuộn vật lý cho TOÀN TRANG và đồng bộ đúng với GSAP ScrollTrigger (một driver raf, không lệch pha, không giật khi quay lại tab), mặc định bằng bản gốc nguyên văn ở
    Rheinmir/uiux-asset
    .
  • Trigger (when to use):
  • Trang cần cảm giác cuộn "nặng tay/mượt" kiểu Apple/awwwards thay vì cuộn giật cứng mặc định của trình duyệt.
  • Trang đã hoặc sẽ dùng GSAP ScrollTrigger (parallax, reveal, pin) và cần Lenis + ScrollTrigger KHÔNG đá nhau (giật, lệch vị trí trigger).
  • KHÔNG dùng nếu chỉ cần vài hiệu ứng cuộn nhẹ không cần smooth-scroll nền (dùng
    scroll-effects
    — vanilla, 0 dependency).
  • KHÔNG dùng để pin/cuộn-ngang một section riêng (đó là
    gsap-scrolltrigger-pin
    ) — nhưng nếu trang có cả hai nhu cầu, khởi tạo Lenis theo skill này TRƯỚC, rồi
    gsap-scrolltrigger-pin
    hoạt động bình thường bên trên vì cả hai đều lấy ScrollTrigger làm trục.
    • User nói "lenis", "smooth scroll", "cuộn mượt kiểu apple", "physics-based scroll", hoặc
      /lenis-smooth-scroll
      .
  • Non-goals: không pin/cuộn ngang một section (→
    gsap-scrolltrigger-pin
    ), không hiệu ứng cuộn lẻ 0-dependency (→
    scroll-effects
    ), không viết lại code gốc của tác giả.
  • **目的:**构建 Lenis 作为全页面基于物理的滚动基础,并与 GSAP ScrollTrigger 正确同步(只使用一个 raf driver,不发生相位偏移,重新切回标签页时也不会卡顿),默认使用
    Rheinmir/uiux-asset
    中未经改写的原始版本。
  • 触发条件(何时使用):
    • 页面需要 Apple/awwwards 风格富有“分量感/顺滑感”的滚动体验,而不是浏览器默认的生硬滚动。
    • 页面已经或将要使用 GSAP ScrollTrigger(parallax、reveal、pin),并且需要确保 Lenis + ScrollTrigger 不会相互干扰(卡顿或 trigger 位置偏移)。
    • 如果只需要少量轻量滚动效果而不需要全页面 smooth-scroll 基础,则不要使用(应使用
      scroll-effects
      ——vanilla、0 dependency)。
    • 不要用于固定或横向滚动某个独立 section(那属于
      gsap-scrolltrigger-pin
      )——但如果页面同时存在这两种需求,应先按本 skill 初始化 Lenis,再在其上使用
      gsap-scrolltrigger-pin
      ;由于两者都以 ScrollTrigger 为轴,后者仍可正常工作。
    • 用户提到“lenis”“smooth scroll”“Apple 风格的平滑滚动”“physics-based scroll”或
      /lenis-smooth-scroll
      。
  • **非目标:**不用于固定或横向滚动某个 section(→
    gsap-scrolltrigger-pin
    ),不用于零依赖的单项滚动效果(→
    scroll-effects
    ),也不重写作者的原始代码。

Mental model

心智模型

bản gốc (Rheinmir/uiux-asset) → copy nguyên file → chỉ sửa nội dung
| nếu không được kéo lib ngoài:
assets/ (vanilla rút gọn) → ADAPT
. Lõi kỹ thuật:
reduced-motion? → không Lenis
/
new Lenis → driver: 2A raf tự quản (không GSAP) XOR 2B gsap.ticker (có GSAP) → lenis.on('scroll', ScrollTrigger.update) → reveal/data-speed ăn theo cùng driver
.
原始版本(Rheinmir/uiux-asset)→ 原样复制整个文件 → 只修改内容
| 如果无法拉取外部库:
assets/(精简版 vanilla)→ ADAPT
。技术核心:
reduced-motion? → 不启用 Lenis
/
new Lenis → driver 2A:raf 自管(无 GSAP)XOR driver 2B:gsap.ticker(有 GSAP)→ lenis.on('scroll', ScrollTrigger.update) → reveal/data-speed 跟随同一个 driver
。

Input và output contract

输入与输出约定

FieldRequired?Ý nghĩa
Intrang/dự án đíchcócó build step (npm) hay trang tĩnh (CDN)
Incó dùng GSAP ScrollTrigger?cóquyết định driver 2A hay 2B
Inđược kéo lib ngoài?cóquyết định bản gốc hay
assets/
Outtrang cuộn Leniscómột driver duy nhất;
* 1000
nếu qua
gsap.ticker
;
lagSmoothing(0)
; nhánh reduced-motion còn nguyên
Outbằng chứng verifycó
playwright-verify
: cuộn nhanh không nhảy vị trí + nhánh reduced-motion qua
page.emulateMedia
字段必填?含义
输入目标页面/项目是是否有 build step(npm),或是静态页面(CDN)
输入是否使用 GSAP ScrollTrigger?是决定使用 driver 2A 还是 2B
输入是否允许拉取外部库?是决定使用原始版本还是
assets/
输出使用 Lenis 滚动的页面是只能有一个 driver;如果通过
gsap.ticker
,需使用
* 1000
;使用
lagSmoothing(0)
;保留 reduced-motion 分支
输出验证证据是使用
playwright-verify
:快速滚动时不发生位置跳跃,并通过
page.emulateMedia
验证 reduced-motion 分支

Rules và capabilities

规则与能力

  • RULE-01 (MUST): Cài đặt:
    npm i lenis gsap
    (khuyến nghị cho dự án có build step) — hoặc CDN như trong
    assets/demo.html
    (
    unpkg.com/lenis
    ,
    cdnjs.cloudflare.com/.../gsap
    ) cho trang tĩnh/demo nhanh. Không dùng bản Lenis cũ tên gói
    @studio-freight/lenis
    (đã đổi tên thành
    lenis
    ) trừ khi dự án đang pin phiên bản cũ có lý do.
  • RULE-02 (MUST): Lỗi thường gặp #1: quên
    gsap.ticker.lagSmoothing(0)
    → cuộn mượt bình thường nhưng giật một cú khi người dùng đổi tab rồi quay lại — vì GSAP tự bù-lag chồng lên cơ chế làm mượt riêng của Lenis (xem giải thích ở mục Cơ chế).
  • RULE-03 (MUST): Lỗi thường gặp #2: chạy CẢ 2A (
    requestAnimationFrame(raf)
    tự quản) LẪN 2B (
    gsap.ticker.add
    ) cùng lúc → hai vòng raf lệch pha, giật nhẹ liên tục. Chỉ chọn MỘT driver: có GSAP thì dùng 2B, không có thì dùng 2A.
  • RULE-04 (MUST): Lỗi thường gặp #3: gọi
    lenis.raf(time)
    từ
    gsap.ticker
    mà quên nhân
    * 1000
    (giây → mili-giây) → easing tính sai.
  • RULE-05 (MUST):
    prefers-reduced-motion: reduce
    → không khởi tạo Lenis, không giả-mượt bằng CSS
    scroll-behavior: smooth
    thay thế — để cuộn gốc trình duyệt hoàn toàn (đã code trong
    assets/lenis-demo.js
    , đừng xoá nhánh này khi adapt).
  • RULE-06 (MUST): Verify bằng
    playwright-verify
    : cuộn nhanh liên tục, kiểm tra không có nhảy vị trí giữa
    data-ls-reveal
    và section chứa nó; test cả nhánh reduced-motion (giả lập qua
    page.emulateMedia
    ).
  • Capabilities: đọc/copy file mẫu từ repo ngoài hoặc
    assets/
    ; ghi vào dự án đích; cài dependency frontend (lenis, gsap); chạy trình duyệt headless để verify.
  • RULE-01(必须):安装:
    npm i lenis gsap
    (推荐用于带 build step 的项目)——静态页面或快速 demo 可使用
    assets/demo.html
    中的 CDN 方式(
    unpkg.com/lenis
    、
    cdnjs.cloudflare.com/.../gsap
    )。除非项目因故固定了旧版本,否则不要使用旧包名
    @studio-freight/lenis
    (现已更名为
    lenis
    )。
  • RULE-02(必须):常见错误 #1:忘记
    gsap.ticker.lagSmoothing(0)
    → 滚动平时虽然平滑,但用户切换标签页再回来时会出现一次卡顿——因为 GSAP 的自动 lag 补偿与 Lenis 自带的平滑机制发生重叠(详见“机制”部分)。
  • RULE-03(必须):常见错误 #2:同时运行 2A(自行管理的
    requestAnimationFrame(raf)
    )和 2B(
    gsap.ticker.add
    )→ 两个 raf 循环发生相位偏移,并持续产生轻微卡顿。只能选择一个 driver:有 GSAP 时使用 2B,没有时使用 2A。
  • RULE-04(必须):常见错误 #3:从
    gsap.ticker
    调用
    lenis.raf(time)
    时忘记乘以
    * 1000
    (秒 → 毫秒)→ easing 计算错误。
  • RULE-05(必须):当
    prefers-reduced-motion: reduce
    时,不初始化 Lenis,也不要用 CSS
    scroll-behavior: smooth
    伪造平滑效果——应完全保留浏览器的原生滚动(在
    assets/lenis-demo.js
    中已实现该分支,ADAPT 时不要删除)。
  • RULE-06(必须):使用
    playwright-verify
    验证:连续快速滚动,检查
    data-ls-reveal
    与其所在 section 之间没有位置跳跃;通过
    page.emulateMedia
    测试 reduced-motion 分支。
  • 能力:从外部 repo 或
    assets/
    读取/复制示例文件;写入目标项目;安装前端依赖(lenis、gsap);运行 headless browser 进行验证。

Failure boundaries

失败边界

  • Không mở/clone được
    Rheinmir/uiux-asset
    → dùng
    assets/
    vanilla rút gọn (partial), báo user.
  • User bật
    prefers-reduced-motion: reduce
    → không khởi tạo Lenis (đúng hành vi, không phải lỗi).
  • Verify thấy nhảy vị trí/giật → failed, rà lại 3 lỗi thường gặp (#1–#3) trước khi báo xong.
  • Dự án pin
    @studio-freight/lenis
    có lý do → giữ nguyên, clarify với user trước khi đổi gói.
  • 无法打开或 clone
    Rheinmir/uiux-asset
    → 使用
    assets/
    中自行编写的精简 vanilla 版本(partial),并告知用户。
  • 用户启用了
    prefers-reduced-motion: reduce
    → 不初始化 Lenis(这是正确行为,不是错误)。
  • 验证发现位置跳跃或卡顿 → failed,完成前先复查三个常见错误(#1–#3)。
  • 项目因故固定了
    @studio-freight/lenis
    → 保持不变,更改包名前先向用户说明并确认。

HOW

HOW

Main workflow

主工作流

StepTypeInputsActionOutputs/exitFailure/next
W01judgmentyêu cầuChọn pen gốc phù hợp trong bảng "Bản gốc", mở link chạy xem đúng hiệu ứngpen được chọnkhông được kéo lib ngoài → B01
W02effectpenCopy nguyên file
.html
(+
_assets/
) vào dự án, giữ comment ghi công, chỉ sửa nội dung/ảnh/màu; chạy qua HTTP
trang chạy—
W03deterministictrangCài (
npm i lenis gsap
hoặc CDN), khởi tạo Lenis sau guard reduced-motion; chọn MỘT driver (2A hoặc 2B)
Lenis chạycó GSAP → B02
W04deterministictrangVerify bằng
playwright-verify
(cuộn nhanh + reduced-motion qua
page.emulateMedia
)
passgiật/nhảy → rà lỗi #1–#3, lặp W03
Chi tiết từng bước (nguồn chân lý cho W01–W04):
步骤类型输入操作输出/退出条件失败/下一步
W01判断需求在“原始版本”表格中选择合适的 Pen,打开链接并实际运行以确认效果选定 Pen无法拉取外部库 → B01
W02效果Pen将完整
.html
(以及
_assets/
)复制到项目中,保留署名注释,只修改内容、图片和颜色;通过 HTTP 运行
页面可运行—
W03确定性页面安装(
npm i lenis gsap
或使用 CDN);在 reduced-motion guard 之后初始化 Lenis;只选择一个 driver(2A 或 2B)
Lenis 正常运行有 GSAP → B02
W04确定性页面使用
playwright-verify
验证(快速滚动,以及通过
page.emulateMedia
测试 reduced-motion)
通过卡顿/跳跃 → 复查错误 #1–#3,重复 W03
以下为各步骤的详细信息(W01–W04 的事实来源):

Bản gốc — dùng TRƯỚC (copy nguyên file, đừng viết lại)

原始版本——优先使用(完整复制文件,不要重写)

Code gốc NGUYÊN VĂN của tác giả (CodePen public = MIT) nằm ở repo ngoài
Rheinmir/uiux-asset
— gallery chạy thật: https://rheinmir.github.io/uiux-asset/scroll-effects/ . Quy trình: mở link chạy để xem đúng hiệu ứng → copy nguyên file
.html
(+ thư mục
_assets/
nếu file trỏ tới) vào dự án → giữ comment ghi công dòng đầu → chỉ sửa nội dung/ảnh/màu. Chạy local qua HTTP, không
file://
.
git clone --depth 1 https://github.com/Rheinmir/uiux-asset
nếu cần cả bộ offline.
Pen gốc — tác giảLink
Lenis Smooth Scroll & GSAP Page — filipzchạy · code
Lenis Smooth Scroll Cinematic Experience — filipzchạy · code
Layout Explorations with GSAP, Flip, Lenis and ScrollTrigger N°2 — filipzchạy · code
Staggered Text Scroll Reveal — gusevdigitalchạy · code
assets/
trong skill này chỉ là bản rút gọn vanilla tự viết — dùng khi dự án KHÔNG được kéo lib ngoài; mặc định dùng bản gốc ở trên.
作者未经改写的原始代码(公开 CodePen = MIT)位于外部 repo
Rheinmir/uiux-asset
——实际运行 gallery:https://rheinmir.github.io/uiux-asset/scroll-effects/ 。 流程:打开链接并实际运行以查看正确效果 → 将完整
.html
文件(如果文件引用了
_assets/
,也一并复制)复制到项目中 → 保留文件首部的署名注释 → 只修改内容、图片和颜色。通过 HTTP 在本地运行,不要使用
file://
。 如需完整离线副本,可执行
git clone --depth 1 https://github.com/Rheinmir/uiux-asset
。
原始 Pen——作者链接
Lenis Smooth Scroll & GSAP Page — filipz运行 · 代码
Lenis Smooth Scroll Cinematic Experience — filipz运行 · 代码
Layout Explorations with GSAP, Flip, Lenis and ScrollTrigger N°2 — filipz运行 · 代码
Staggered Text Scroll Reveal — gusevdigital运行 · 代码
本 skill 中的
assets/
只是自行编写的精简 vanilla 版本——仅在项目不能拉取外部库时使用;默认使用上述原始版本。

Assets

Assets

assets/demo.html
+
assets/lenis-demo.css
+
assets/lenis-demo.js
— trang demo chạy được (CDN Lenis + GSAP), đọc rồi ADAPT (đổi token/section theo dự án, đổi CDN sang bundle npm nếu dự án đã có build step).
assets/demo.html
+
assets/lenis-demo.css
+
assets/lenis-demo.js
——这是一个可直接运行的 demo 页面(使用 Lenis + GSAP CDN)。先阅读,再进行 ADAPT(按照项目调整 token/section;如果项目已有 build step,则将 CDN 替换为 npm bundle)。

Cơ chế

机制

1. Khởi tạo Lenis
1. 初始化 Lenis
js
const lenis = new Lenis({
  lerp: 0.1,        // nội suy mỗi frame (0–1): thấp = mượt/trễ hơn, cao = bám sát input hơn
  duration: 1.2,     // easing khi có target (vd anchor scroll), không áp dụng cho wheel liên tục
  smoothWheel: true, // bật vật lý mượt cho wheel/trackpad — lý do chính để dùng Lenis
});
js
const lenis = new Lenis({
  lerp: 0.1,        // nội suy mỗi frame (0–1): thấp = mượt/trễ hơn, cao = bám sát input hơn
  duration: 1.2,     // easing khi có target (vd anchor scroll), không áp dụng cho wheel liên tục
  smoothWheel: true, // bật vật lý mượt cho wheel/trackpad — lý do chính để dùng Lenis
});
2A. Đứng một mình (không GSAP) — vòng lặp raf tự quản
2A. 独立运行(无 GSAP)——自行管理的 raf 循环
js
function raf(time) {
  lenis.raf(time);
  requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
js
function raf(time) {
  lenis.raf(time);
  requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
2B. Có GSAP ScrollTrigger — GỘP về một driver duy nhất (không dùng cả 2A lẫn 2B cùng lúc)
2B. 有 GSAP ScrollTrigger——合并到唯一 driver(不要同时使用 2A 和 2B)
js
gsap.registerPlugin(ScrollTrigger);

lenis.on('scroll', ScrollTrigger.update);         // Lenis cuộn -> báo ScrollTrigger cập nhật ngay
gsap.ticker.add((time) => lenis.raf(time * 1000)); // gsap.ticker LÀM vòng raf cho Lenis luôn
gsap.ticker.lagSmoothing(0);                        // tắt bù-lag mặc định của GSAP (xem lý do bên dưới)
Tại sao phải làm đúng thứ tự 3 dòng này (điểm hay sai nhất):
  • gsap.ticker
    đã tự chạy một vòng
    requestAnimationFrame
    nội bộ để lái mọi tween/ScrollTrigger. Nếu Lenis lại tự chạy thêm một
    requestAnimationFrame(raf)
    riêng (cách 2A) TRONG KHI đã có GSAP, hai vòng lặp không cùng nhịp — mỗi frame chúng cập nhật lệch nhau vài mili-giây, ScrollTrigger đọc vị trí cuộn "cũ hơn" hoặc "mới hơn" Lenis một khung hình. Cảm giác là giật nhẹ liên tục khi cuộn nhanh, đặc biệt ở section có
    pin
    hoặc
    scrub
    . Sửa bằng cách để gsap.ticker làm driver DUY NHẤT: bỏ hẳn
    requestAnimationFrame(raf)
    riêng, thay bằng
    gsap.ticker.add(...)
    .
  • gsap.ticker
    phát
    time
    tính bằng giây, còn
    lenis.raf()
    cần mili-giây → phải nhân
    * 1000
    . Quên nhân là lỗi im lặng: Lenis chạy nhưng easing/velocity tính sai theo một hệ thời gian nhỏ hơn 1000 lần, scroll trông "đơ" hoặc "bay" tuỳ lerp.
  • lenis.on('scroll', ScrollTrigger.update)
    bắt buộc vì Lenis cập nhật vị trí cuộn qua transform nội bộ (
    lerp
    mỗi frame, không phải
    scrollTop
    tức thời) — nếu không báo, ScrollTrigger vẫn dùng sự kiện
    scroll
    gốc của trình duyệt để tính pin/progress, vốn KHÔNG khớp với vị trí "mượt" mà Lenis đang vẽ ra → pin bị lệch vài px hoặc trigger bắn sai thời điểm.
  • gsap.ticker.lagSmoothing(0)
    xử lý một lỗi khác, dễ bị bỏ sót: mặc định GSAP tự phát hiện khung hình bị trễ bất thường (ví dụ tab mất focus vài giây rồi quay lại) và "nén" thời gian lại để bù, cho animation không nhảy cóc. Nhưng Lenis đã tự có cơ chế làm mượt delta-time của riêng nó — hai hệ bù-thời-gian chồng lên nhau khiến cú quay-lại-tab bị giật một nhịp rõ rệt thay vì mượt. Tắt hẳn bù của GSAP (
    lagSmoothing(0)
    ) để một mình Lenis lo phần đó.
js
gsap.registerPlugin(ScrollTrigger);

lenis.on('scroll', ScrollTrigger.update);         // Lenis cuộn -> báo ScrollTrigger cập nhật ngay
gsap.ticker.add((time) => lenis.raf(time * 1000)); // gsap.ticker LÀM vòng raf cho Lenis luôn
gsap.ticker.lagSmoothing(0);                        // tắt bù-lag mặc định của GSAP (xem lý do bên dưới)
为什么必须严格按这 3 行的顺序执行(这是最容易出错的点):
  • gsap.ticker
    本身已经运行一个内部
    requestAnimationFrame
    循环,用来驱动所有 tween/ScrollTrigger。如果在已有 GSAP 的情况下,又让 Lenis 自行运行额外的
    requestAnimationFrame(raf)
    (方式 2A),两个循环将无法保持相同节奏——每一帧的更新时间会相差数毫秒,ScrollTrigger 读到的滚动位置会比 Lenis 的位置旧一帧或新一帧。快速滚动时会因此产生持续轻微卡顿,在包含
    pin
    或
    scrub
    的 section 中尤其明显。解决方法是让 gsap.ticker 成为唯一 driver:彻底移除独立的
    requestAnimationFrame(raf)
    ,改用
    gsap.ticker.add(...)
    。
  • gsap.ticker
    输出的
    time
    使用秒,而
    lenis.raf()
    需要毫秒——因此必须乘以
    * 1000
    。忘记相乘会造成静默错误:Lenis 虽然运行,但其 easing/velocity 会按照小 1000 倍的时间系统计算,最终滚动效果会根据 lerp 参数显得“生硬”或“飞驰”。
  • 必须使用
    lenis.on('scroll', ScrollTrigger.update)
    ,因为 Lenis 通过内部 transform 更新滚动位置(每帧执行
    lerp
    ,而不是立即修改
    scrollTop
    )——如果不通知 ScrollTrigger,它仍会使用浏览器原生的
    scroll
    事件计算 pin/progress,这与 Lenis 当前绘制出的“平滑”位置不一致 → pin 会偏移数个 px,或 trigger 在错误的时间触发。
  • gsap.ticker.lagSmoothing(0)
    解决另一个容易被忽略的问题:GSAP 默认会检测异常延迟的帧(例如标签页失去焦点数秒后重新激活),并“压缩”时间来补偿,使动画不会突然跳跃。但 Lenis 本身已经具有独立的 delta-time 平滑机制——两套时间补偿机制叠加后,重新切回标签页时会出现一次明显卡顿。必须通过
    lagSmoothing(0)
    完全关闭 GSAP 的 lag 补偿,让 Lenis 独自负责这一部分。
3. Tôn trọng
prefers-reduced-motion: reduce
3. 尊重
prefers-reduced-motion: reduce
Không được "giả-mượt" cho người đã bật giảm chuyển động — không khởi tạo
Lenis
luôn, để trình duyệt cuộn gốc (jump-scroll bình thường). Kiểm tra TRƯỚC khi gọi
new Lenis(...)
:
js
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  // new Lenis(...) + sync ở trên
} else {
  // cuộn gốc trình duyệt; nếu có reveal-on-scroll đi kèm, hiện thẳng bằng CSS/class, không chờ cuộn mượt
}
Xem cách áp dụng đầy đủ (kèm demo reveal fallback) trong
assets/lenis-demo.js
.
不得为已启用“减少动态效果”的用户“伪平滑”——不要初始化
Lenis
,让浏览器保持原生滚动(正常的 jump-scroll)。必须在调用
new Lenis(...)
之前检查:
js
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  // new Lenis(...) + sync ở trên
} else {
  // cuộn gốc trình duyệt; nếu có reveal-on-scroll đi kèm, hiện thẳng bằng CSS/class, không chờ cuộn mượt
}
有关完整实现方式(包括 demo reveal fallback),请参阅
assets/lenis-demo.js
。
4. Demo: reveal +
data-speed
khi cuộn qua Lenis
4. Demo:通过 Lenis 滚动实现 reveal +
data-speed
assets/demo.html
có vài section gắn
data-ls-reveal
(stagger hiện dần, dùng
ScrollTrigger.create({ once: true })
ăn theo scroll đã sync ở bước 2B) và
data-speed="0.12"
(layer trôi lệch nhẹ qua
gsap.to(..., { scrollTrigger: { scrub: true } })
, giá trị kẹp
|v| ≤ 0.3
để tránh say chuyển động) — cả hai dùng CHUNG driver Lenis/GSAP đã thiết lập, không tạo listener
scroll
rời.
assets/demo.html
包含几个绑定了
data-ls-reveal
的 section(逐级显现,使用
ScrollTrigger.create({ once: true })
跟随步骤 2B 中同步的滚动),以及
data-speed="0.12"
(图层通过
gsap.to(..., { scrollTrigger: { scrub: true } })
轻微漂移,数值限制为
|v| ≤ 0.3
以避免晕动症)——两者都使用已配置的同一个 Lenis/GSAP driver,不创建独立的
scroll
listener。

Branches

分支

IDKindGuardHành viSkip / failureRejoin
B01conditional_requireddự án KHÔNG được kéo lib ngoài / cần bản tự viếtdùng
assets/demo.html
+
assets/lenis-demo.css
+
assets/lenis-demo.js
, ADAPT token/section
—W03
B02conditional_requiredtrang có GSAP ScrollTriggerdriver 2B:
lenis.on('scroll', ScrollTrigger.update)
+
gsap.ticker.add((time) => lenis.raf(time * 1000))
+
gsap.ticker.lagSmoothing(0)
; bỏ hẳn 2A
không GSAP → 2AW04
B03conditional_required
prefers-reduced-motion: reduce
không
new Lenis
, cuộn gốc; reveal hiện thẳng bằng CSS/class
—W04
B04user_optionaltrang cần thêm pin/cuộn ngang sectionkhởi tạo Lenis theo skill này TRƯỚC rồi dùng
gsap-scrolltrigger-pin
bên trên
—W04
ID类型Guard行为跳过/失败汇合点
B01conditional_required项目不能拉取外部库,或需要自行编写的版本使用
assets/demo.html
+
assets/lenis-demo.css
+
assets/lenis-demo.js
,ADAPT token/section
—W03
B02conditional_required页面使用 GSAP ScrollTriggerdriver 2B:
lenis.on('scroll', ScrollTrigger.update)
+
gsap.ticker.add((time) => lenis.raf(time * 1000))
+
gsap.ticker.lagSmoothing(0)
;彻底移除 2A
无 GSAP → 2AW04
B03conditional_required
prefers-reduced-motion: reduce
不执行
new Lenis
,保留浏览器原生滚动;通过 CSS/class 直接显示 reveal
—W04
B04user_optional页面还需要固定/横向滚动某个 section先按本 skill 初始化 Lenis,再在其上使用
gsap-scrolltrigger-pin
—W04

Validation và stopping

验证与停止条件

Xong khi
playwright-verify
cuộn nhanh liên tục không nhảy vị trí giữa
data-ls-reveal
và section chứa nó, và nhánh reduced-motion (qua
page.emulateMedia
) cuộn gốc không Lenis. Code chỉ có MỘT driver raf.
使用
playwright-verify
连续快速滚动时,
data-ls-reveal
与其所在 section 之间没有位置跳跃;同时,通过
page.emulateMedia
验证 reduced-motion 分支使用浏览器原生滚动,而不是 Lenis。代码中只能存在一个 raf driver。

Examples

示例

  • Positive: "làm trang landing cuộn mượt kiểu apple có parallax" → chọn pen "Lenis Smooth Scroll & GSAP Page — filipz", copy nguyên
    lenis-smooth-scroll-gsap-page.html
    , đổi nội dung/ảnh → verify cuộn nhanh không lệch pin, reduced-motion cuộn gốc.
  • Boundary/failure: code adapt có cả
    requestAnimationFrame(raf)
    lẫn
    gsap.ticker.add(...)
    → giật nhẹ liên tục (lỗi #2) → xoá vòng 2A, chỉ giữ 2B, verify lại; quên
    * 1000
    thì scroll "đơ/bay" (lỗi #3).
  • 正向:“制作一个具有 Apple 风格平滑滚动和 parallax 的 landing page”→ 选择 Pen“Lenis Smooth Scroll & GSAP Page — filipz”,完整复制
    lenis-smooth-scroll-gsap-page.html
    ,修改内容/图片 → 验证快速滚动时 pin 不偏移,并确认 reduced-motion 下使用浏览器原生滚动。
  • **边界/失败:**ADAPT 后的代码同时包含
    requestAnimationFrame(raf)
    和
    gsap.ticker.add(...)
    → 持续出现轻微卡顿(错误 #2)→ 删除 2A 循环,仅保留 2B,然后重新验证;忘记
    * 1000
    会导致滚动“生硬/飞驰”(错误 #3)。

Origin

来源

Batch 180926 — bản gốc NGUYÊN VĂN nằm ở
Rheinmir/uiux-asset/scroll-effects/
(xem mục "Bản gốc" ở trên);
assets/
là bản rút gọn vanilla tự viết.
Batch 180926——未经改写的原始版本位于
Rheinmir/uiux-asset/scroll-effects/
(见上文“原始版本”部分);
assets/
是自行编写的精简 vanilla 版本。