关于 lenis-smooth-scroll
为网页接入 Lenis 平滑滚动,并与 GSAP ScrollTrigger 同步的技能。它指导开发者选择单一动画驱动、遵循减少动态效果设置并验证效果。
主要功能
- 01配置 Lenis 平滑滚动
- 02选择单一 RAF 驱动
- 03同步 GSAP ScrollTrigger
- 04处理减少动态效果设置
- 05使用 Playwright 验证
适用场景
- 01为落地页添加物理感平滑滚动
- 02让 Lenis 与固定或视差区段同步
- 03验证启用减少动态效果时的滚动
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rheinmir/ setup --skill lenis-smooth-scroll安装后在 Claude Code 中输入/lenis-smooth-scroll确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/demo.htmllenis-demo.csslenis-demo.js详细介绍
技能:lenis-smooth-scroll
将 Lenis 设置为全站的物理滚动基础,并与 GSAP ScrollTrigger 正确同步——这是将这两个库组合使用时最容易出错的一步。
WHAT
用途和背景
- 用途: 将 Lenis 设为全站的物理滚动基础,并与 GSAP ScrollTrigger 正确同步(使用单一 raf 驱动器,不会相位错乱,也不会在切回标签页时卡顿),默认使用
Rheinmir/uiux-asset中未经改动的原版文件。 - 触发条件(何时使用):
- 页面需要类似 Apple/awwwards 的“厚重、顺滑”滚动体验,而不是浏览器默认的生硬滚动。
- 页面已经或将要使用 GSAP ScrollTrigger(视差、显现、固定),并且需要 Lenis 与 ScrollTrigger 不互相干扰(卡顿、触发位置偏移)。
- 如果只需要少量轻量滚动效果,不需要全局平滑滚动基础,则不要使用(改用
scroll-effects— 原生实现,0 依赖)。 - 不要用它来固定/横向滚动单独的 section(那是
gsap-scrolltrigger-pin)——但如果页面同时需要这两种功能,先按本技能初始化 Lenis,然后gsap-scrolltrigger-pin就可以正常运行,因为两者都以 ScrollTrigger 为基准。- 用户提到“lenis”、“smooth scroll”、“Apple 风格的顺滑滚动”、“physics-based scroll”或
/lenis-smooth-scroll。
- 用户提到“lenis”、“smooth scroll”、“Apple 风格的顺滑滚动”、“physics-based scroll”或
- 非目标: 不固定/横向滚动单独的 section(→
gsap-scrolltrigger-pin),不实现零依赖的零散滚动效果(→scroll-effects),不重写作者的原始代码。
心智模型
原版(Rheinmir/uiux-asset)→ 原样复制文件 → 只修改内容 | 如果不能拉取外部库:assets/(精简 vanilla 版)→ ADAPT。技术核心:reduced-motion? → 不使用 Lenis / new Lenis → 驱动器:2A 自行管理 raf(不使用 GSAP) XOR 2B gsap.ticker(使用 GSAP)→ lenis.on('scroll', ScrollTrigger.update) → reveal/data-speed 使用同一驱动器
输入和输出约定
| Field | Required? | 含义 | |
|---|---|---|---|
| In | 目标页面/项目 | 是 | 是否有构建步骤(npm),还是静态页面(CDN) |
| In | 是否使用 GSAP ScrollTrigger? | 是 | 决定使用驱动器 2A 还是 2B |
| In | 是否允许拉取外部库? | 是 | 决定使用原版还是 assets/ |
| Out | Lenis 滚动页面 | 是 | 仅使用一个驱动器;通过 gsap.ticker 时乘以 * 1000;lagSmoothing(0);保留 reduced-motion 分支 |
| Out | 验证证据 | 是 | playwright-verify:快速滚动时位置不跳变,并通过 page.emulateMedia 测试 reduced-motion 分支 |
规则和功能
- RULE-01 (MUST):安装:
npm i lenis gsap(推荐用于有构建步骤的项目)——静态页面/快速 demo 也可以像assets/demo.html中那样使用 CDN(unpkg.com/lenis、cdnjs.cloudflare.com/.../gsap)。不要使用旧包名@studio-freight/lenis的旧版 Lenis(包名已改为lenis),除非项目因某种原因固定在旧版本上。 - RULE-02 (MUST):常见错误 #1:忘记调用
gsap.ticker.lagSmoothing(0)→ 平时滚动顺滑,但用户切换标签页再切回来时会突然卡顿一下,因为 GSAP 的自动延迟补偿会叠加到 Lenis 自己的平滑机制上(参见“机制”部分的说明)。 - RULE-03 (MUST):常见错误 #2:同时运行 2A(自行管理的
requestAnimationFrame(raf))和 2B(gsap.ticker.add)→ 两个 raf 循环相位错乱,持续轻微卡顿。只能选择一个驱动器:使用 GSAP 时选 2B,否则选 2A。 - RULE-04 (MUST):常见错误 #3:从
gsap.ticker调用lenis.raf(time)时忘记乘以* 1000(秒 → 毫秒)→ 缓动计算错误。 - RULE-05 (MUST):
prefers-reduced-motion: reduce→ 不初始化 Lenis,也不要用 CSSscroll-behavior: smooth替代来模拟平滑滚动——完全使用浏览器原生滚动(已在assets/lenis-demo.js中实现,adapt 时不要删除此分支)。 - RULE-06 (MUST):使用
playwright-verify验证:连续快速滚动,检查data-ls-reveal与其所在 section 之间没有位置跳变;也要测试 reduced-motion 分支(通过page.emulateMedia模拟)。 - 功能:从外部仓库或
assets/读取/复制示例文件;写入目标项目;安装前端依赖(lenis、gsap);运行无头浏览器进行验证。
失败边界
- 无法打开/克隆
Rheinmir/uiux-asset→ 使用精简版 vanillaassets/(partial),并告知用户。 - 用户启用了
prefers-reduced-motion: reduce→ 不初始化 Lenis(这是正确行为,不是错误)。 - 验证时发现位置跳动/卡顿 → failed,报告完成前先检查 3 个常见错误(#1–#3)。
- 项目因故固定使用
@studio-freight/lenis→ 保持不变,更换包之前先与用户确认。
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | 需求 | 在“原始作品”表格中选择合适的原始 Pen,打开运行链接确认效果 | 已选择的 Pen | 不允许拉取外部库 → B01 |
| W02 | effect | Pen | 将完整的 .html 文件(+ _assets/)复制到项目中,保留署名注释,只修改内容/图片/颜色;通过 HTTP 运行 | 页面运行 | — |
| W03 | deterministic | 页面 | 安装(npm i lenis gsap 或 CDN),在 reduced-motion 检查后初始化 Lenis;选择一个驱动方式(2A 或 2B) | Lenis 运行 | 已有 GSAP → B02 |
| W04 | deterministic | 页面 | 使用 playwright-verify 验证(快速滚动 + 通过 page.emulateMedia 测试 reduced-motion) | 通过 | 卡顿/跳动 → 检查错误 #1–#3,重复 W03 |
各步骤详情(W01–W04 的准确信息来源):
原始作品 — 优先使用(复制完整文件,不要重写)
作者的原始代码原文(公开 CodePen = MIT)位于外部仓库 Rheinmir/uiux-asset — 可运行的作品集: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 | 运行 · 代码 |
此技能中的 assets/ 只是手写的精简版 vanilla — 仅在项目不允许拉取外部库时使用;默认使用上面的原始版本。
资源
assets/demo.html + assets/lenis-demo.css + assets/lenis-demo.js — 可运行的演示页面(CDN Lenis + GSAP)。阅读后进行改造(根据项目更换 token/section;如果项目已有构建步骤,则将 CDN 改为 npm bundle)。
机制
1. 初始化 Lenis
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. 独立运行(不使用 GSAP)— 自行管理 raf 循环
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
2B. 使用 GSAP ScrollTrigger — 合并为唯一驱动器(不要同时使用 2A 和 2B)
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。如果 Lenis 又单独运行一个requestAnimationFrame(raf)(方法 2A),而此时已有 GSAP,那么两个循环就不会同步——每帧更新会相差几毫秒,ScrollTrigger 读取的滚动位置会比 Lenis 早或晚一帧。快速滚动时会持续感到轻微卡顿,在带有pin或scrub的 section 中尤为明显。解决方法是让 gsap.ticker 成为唯一驱动器:彻底移除单独的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 平滑机制——两套时间补偿机制叠加,会导致切回标签页时明显卡顿一下,而非平滑过渡。彻底关闭 GSAP 的补偿(lagSmoothing(0)),让 Lenis 单独处理这部分。
3. 尊重 prefers-reduced-motion: reduce
对于已启用减少动态效果的用户,不要“伪装平滑”——不要初始化 Lenis,让浏览器使用原生滚动(普通跳转滚动)。调用 new Lenis(...) 之前先检查:
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
}
完整的应用方法(包含 reveal 回退演示)请参阅 assets/lenis-demo.js。
4. 演示:通过 Lenis 滚动时的 reveal + data-speed
assets/demo.html 中有几个 section 带有 data-ls-reveal(逐步显示,使用 ScrollTrigger.create({ once: true }),跟随步骤 2B 中已同步的滚动)和 data-speed="0.12"(通过 gsap.to(..., { scrollTrigger: { scrub: true } }) 让图层轻微错位移动,数值限制为 |v| ≤ 0.3,以避免产生晕动感)——两者共用已设置的 Lenis/GSAP 驱动器,不会另外创建 scroll 监听器。
分支
| ID | 类型 | 条件 | 行为 | 跳过 / 失败 | 重新汇合 |
|---|---|---|---|---|---|
| B01 | conditional_required | 项目不允许引入外部库 / 需要自行编写的版本 | 使用 assets/demo.html + assets/lenis-demo.css + assets/lenis-demo.js,调整 token/section | — | W03 |
| B02 | conditional_required | 页面使用 GSAP ScrollTrigger | 使用 2B 驱动器:lenis.on('scroll', ScrollTrigger.update) + gsap.ticker.add((time) => lenis.raf(time * 1000)) + gsap.ticker.lagSmoothing(0);彻底移除 2A | 不使用 GSAP → 2A | W04 |
| B03 | conditional_required | prefers-reduced-motion: reduce | 不调用 new Lenis,使用原生滚动;通过 CSS/class 直接显示 reveal | — | W04 |
| B04 | user_optional | 页面需要增加 pin/section 横向滚动 | 先按照此技能初始化 Lenis,再使用上方的 gsap-scrolltrigger-pin | — | W04 |
验证与停止条件
当 playwright-verify 连续快速滚动时,data-ls-reveal 与其所在 section 之间不会发生位置跳动,并且通过 page.emulateMedia 验证 reduced-motion 分支使用不带 Lenis 的原生滚动,即表示完成。代码中只能有一个 raf 驱动器。
示例
- 成功案例:“做一个像 Apple 一样带视差效果、滚动顺滑的落地页” → 选择 pen「Lenis Smooth Scroll & GSAP Page — filipz」,复制完整的
lenis-smooth-scroll-gsap-page.html,替换内容和图片 → 验证快速滚动时 pin 不偏移,reduced-motion 下使用原生滚动。 - **边界情况/失败案例:**改编代码同时包含
requestAnimationFrame(raf)和gsap.ticker.add(...)→ 持续轻微卡顿(错误 #2)→ 删除 2A 循环,只保留 2B,然后重新验证;忘记乘以1000会导致滚动“卡住/飞走”(错误 #3)。
来源
Batch 180926 — 原始版本原样位于 Rheinmir/uiux-asset/scroll-effects/(参见上文“原始版本”部分);assets/ 是自行编写的精简版 vanilla 版本。