mcpField
L
lenis-smooth-scroll

lenis-smooth-scroll

github.com/rheinmir设计与 UI1082 次安装2 周前更新
代码仓库

关于 lenis-smooth-scroll

为网页接入 Lenis 平滑滚动,并与 GSAP ScrollTrigger 同步的技能。它指导开发者选择单一动画驱动、遵循减少动态效果设置并验证效果。

主要功能

  1. 01配置 Lenis 平滑滚动
  2. 02选择单一 RAF 驱动
  3. 03同步 GSAP ScrollTrigger
  4. 04处理减少动态效果设置
  5. 05使用 Playwright 验证

适用场景

  1. 01为落地页添加物理感平滑滚动
  2. 02让 Lenis 与固定或视差区段同步
  3. 03验证启用减少动态效果时的滚动

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rheinmir/setup --skill lenis-smooth-scroll

安装后在 Claude Code 中输入/lenis-smooth-scroll确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
demo.html
lenis-demo.css
lenis-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。
  • 非目标: 不固定/横向滚动单独的 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 使用同一驱动器

输入和输出约定
FieldRequired?含义
In目标页面/项目是是否有构建步骤(npm),还是静态页面(CDN)
In是否使用 GSAP ScrollTrigger?是决定使用驱动器 2A 还是 2B
In是否允许拉取外部库?是决定使用原版还是 assets/
OutLenis 滚动页面是仅使用一个驱动器;通过 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,也不要用 CSS scroll-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 → 使用精简版 vanilla assets/(partial),并告知用户。
  • 用户启用了 prefers-reduced-motion: reduce → 不初始化 Lenis(这是正确行为,不是错误)。
  • 验证时发现位置跳动/卡顿 → failed,报告完成前先检查 3 个常见错误(#1–#3)。
  • 项目因故固定使用 @studio-freight/lenis → 保持不变,更换包之前先与用户确认。

HOW

Main workflow
StepTypeInputsActionOutputs/exitFailure/next
W01judgment需求在“原始作品”表格中选择合适的原始 Pen,打开运行链接确认效果已选择的 Pen不允许拉取外部库 → B01
W02effectPen将完整的 .html 文件(+ _assets/)复制到项目中,保留署名注释,只修改内容/图片/颜色;通过 HTTP 运行页面运行—
W03deterministic页面安装(npm i lenis gsap 或 CDN),在 reduced-motion 检查后初始化 Lenis;选择一个驱动方式(2A 或 2B)Lenis 运行已有 GSAP → B02
W04deterministic页面使用 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类型条件行为跳过 / 失败重新汇合
B01conditional_required项目不允许引入外部库 / 需要自行编写的版本使用 assets/demo.html + assets/lenis-demo.css + assets/lenis-demo.js,调整 token/section—W03
B02conditional_required页面使用 GSAP ScrollTrigger使用 2B 驱动器:lenis.on('scroll', ScrollTrigger.update) + gsap.ticker.add((time) => lenis.raf(time * 1000)) + gsap.ticker.lagSmoothing(0);彻底移除 2A不使用 GSAP → 2AW04
B03conditional_requiredprefers-reduced-motion: reduce不调用 new Lenis,使用原生滚动;通过 CSS/class 直接显示 reveal—W04
B04user_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 版本。

评论

支持 Markdown