news 2026/9/19 16:17:11

Lenis:轻量级平滑滚动库手册——3 个核心机制 + 4 个实战场景 + 配置速查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Lenis:轻量级平滑滚动库手册——3 个核心机制 + 4 个实战场景 + 配置速查

Lenis:轻量级平滑滚动库手册——3 个核心机制 + 4 个实战场景 + 配置速查

【免费下载链接】lenisSmooth scroll as it should be项目地址: https://gitcode.com/GitHub_Trending/le/lenis

Lenis 是一款轻量级、零依赖的平滑滚动库,仅数 KB 体积,它包裹浏览器原生滚动而非用 transform 伪造位移,因此 sticky 与锚点链接原样保留。适合需要做视差、WebGL 滚动同步、滚动驱动动画的前端开发者。读完你能完成初始化、调参,并接入 GSAP 与 React/Vue 生态。

快速上手:三步开启平滑滚动

安装npm i lenis,然后在入口文件里引入、初始化、加上推荐 CSS,共三步。

import Lenis from 'lenis' import 'lenis/dist/lenis.css' // 推荐 CSS,负责 stopped 态与嵌套容器的 overscroll 行为 // autoRaf: true 让实例内部自动跑 requestAnimationFrame 循环 // 不开启的话必须每帧手动调 lenis.raf(time) const lenis = new Lenis({ autoRaf: true }) // 位置变化的每一帧都会触发,progress / velocity 等属性直接读实例即可 lenis.on('scroll', (e) => { console.log(e.animatedScroll, e.progress) }) // 参考 README "Setup" 一节;实例定义见 packages/core/src/lenis.ts

不想搭构建环境也有一行版:HTML 里引lenis.csslenis.min.js,然后执行new Lenis({ autoRaf: true, autoToggle: true, anchors: true, allowNestedScroll: true, naiveDimensions: true, stopInertiaOnNavigate: true }),README 的 "No-code usage" 一节列出了这组"全功能"参数组合,能顺带处理模态框、锚点、页面切换时的滚动复位。想看源码可以直接 clone:git clone https://gitcode.com/GitHub_Trending/le/lenis

核心机制拆解:轻量与顺滑背后的 3 个设计决策

包裹原生滚动,而不是用 transform 伪造

设计意图:多数平滑滚动库用"虚拟画布"方案——把页面塞进容器、用 transform 位移来模拟滚动。Lenis 反其道而行,真正调用浏览器滚动。packages/core/src/lenis.ts文件头的注释把链路写得明明白白:监听wheel事件并preventDefault阻止原生跳动 → 归一化 delta → 累加进targetScroll→ 平滑地把scrollTo动画到目标值;没有动画在跑时,则退化为监听原生scroll事件。

与传统做法的差异一目了然:

对比项Lenis(原生滚动方案)传统 transform 虚拟滚动
实现方式真实scrollTo({ behavior: 'instant' })驱动 window对容器做 translate 位移
position: sticky/ fixed原样生效会失效,需额外补偿
锚点链接、键盘可达性原样保留需手工重建

收益是 README 里 "Runs on native scroll" 那条特性:position: sticky、锚点链接和无障碍交互全部不牺牲。注意setScroll里特意用behavior: 'instant',就是为了绕过页面自己声明的scroll-behaviorCSS,避免双重平滑。

两种手感二选一:damp 插值与固定时长缓动

💡 Lenis 的"手感"由Animate类(packages/core/src/animate.ts)提供,它不是一种算法,而是两种:

// 参考 packages/core/src/animate.ts(advance 方法摘录) advance(deltaTime: number) { if (this.duration && this.easing) { // 模式一:时间驱动。按 duration 走完整条 easing 曲线 this.currentTime += deltaTime const linearProgress = clamp(0, this.currentTime / this.duration, 1) const easedProgress = linearProgress >= 1 ? 1 : this.easing(linearProgress) this.value = this.from + (this.to - this.from) * easedProgress } else if (this.lerp) { // 模式二:指数阻尼。damp 公式 1 - e^(-lambda·dt),帧率无关 this.value = damp(this.value, this.to, this.lerp * 60, deltaTime) } else { // 两者都没给:直接跳到终点 this.value = this.to } }

差异点:传统库通常只给一组固定缓动曲线,帧率一波动手感就漂移。damp1 - Math.exp(-lambda * dt)packages/core/src/maths.ts)做到帧率无关,60Hz 和 120Hz 屏幕上衰减曲线一致,默认lerp: 0.1一个数就能调"滑多远";给duration + easing则走标准时间轴,默认缓动是Math.min(1, 1.001 - 2 ** (-10 * t))。收益:滚轮的"滑行感"更接近真实惯性,而不是匀速补间。

先归一化输入,再统一状态输出

不同硬件的滚轮 delta 量纲完全不同(像素、行、页面)。packages/core/src/virtual-scroll.tsdeltaMode统一换算:按行滚动的设备乘以LINE_HEIGHT = 100 / 6,按页滚动的乘以视口尺寸,再乘wheelMultiplier/touchMultiplier。触摸端还补了两处细节:touchend时按sign(delta) × |velocity|^1.7touchInertiaExponent默认 1.7)模拟抬手后的惯性滑行;iOS 上如果手指落在文本选区手柄 40px 半径内(lenis.tsisTouchOnSelectionHandle),会把事件让给系统去调整选区而不是滚动。

输出侧则把状态写成 CSS 类:lenislenis-smoothlenis-stoppedlenis-locked,随状态自动增删。配套packages/core/lenis.css做了三件小事——html.lenis { height: auto }修正页面高度;给data-lenis-prevent元素加overscroll-behavior: contain防链式回弹;lenis-smooth期间给 iframe 加pointer-events: none,防止 iframe 吞掉 wheel 事件。另外,原生滚动停下 400ms 后velocity归零、isScrolling复位,动画结束后还会派发自定义scrollend事件,方便下游做"滚动停止"逻辑。

实战场景:几乎一定会遇到的 4 个需求

让嵌套容器保持原生滚动

场景:页面里有抽屉、模态框或横向卡片列表,落在它们上面滚动时,不希望外层页面跟着滚。

思路:两档粒度。粗粒度开allowNestedScroll,实例会自动检测可滚动子元素并放行原生滚动;细粒度用data-lenis-prevent属性(另有-wheel-touch-vertical-horizontal变体)或prevent回调,在事件冒泡路径上精确豁免。

// 参考 README "Nested scroll" 一节 const lenis = new Lenis({ allowNestedScroll: true, // 粗粒度:自动识别嵌套可滚动元素 // 细粒度二选一:给元素加>// 参考 README "GSAP ScrollTrigger" 一节 const lenis = new Lenis() // 每帧把 Lenis 的滚动状态喂给 ScrollTrigger lenis.on('scroll', ScrollTrigger.update) // 用 GSAP 的 ticker 驱动 Lenis gsap.ticker.add((time) => { lenis.raf(time * 1000) // ticker 的时间单位是秒,转成毫秒 }) // 关闭 GSAP 的滞后平滑,否则快速滚动会累积延迟 gsap.ticker.lagSmoothing(0)

gsap.ticker回调的时间单位是秒,漏乘 1000 会让动画慢十倍;lagSmoothing(0)忘了关,快速滚动时动画与位置脱节,这是 README Troubleshooting 里专门点名的一条。

开启无限滚动模式

场景:品牌页、作品集的网格希望"滚到尽头回到开头"。

思路:一个开关infinite: true。开启后scroll取值器会用modulo(animatedScroll, limit)按回卷值输出,scrollTo的目标也会自动选"距离最近的方向"(超过limit / 2就往回绕)。

// 参考 playground/infinite/test.ts new Lenis({ infinite: true, // 循环滚动,scroll 取值器按 limit 自动取模 autoRaf: true, syncTouch: true, // README 注明:触摸设备开启 infinite 需要它 })

:触摸设备上必须同时开syncTouch: true,否则惯性触摸会直接打断回卷逻辑。

搭建横向滚动区段

场景:产品画廊、步骤展示需要一段横向滚动的区域。

思路:不用全局实例,单独new Lenis({ wrapper: 容器, orientation: 'horizontal' })挂在具体容器上;scrollTo支持rightend等关键字。

orientation设为horizontalgestureOrientation默认自动变both(见lenis.ts构造参数默认值),横向纵向的 delta 都会进来;如果你的横向区段嵌在纵向页面里,注意用eventsTarget收窄监听范围,并配合overscroll控制边界处的行为。

配置速查:常用参数与调优点

核心参数(默认值均可在packages/core/src/types.ts的 JSDoc 中核对):

参数默认值作用
autoRaffalse实例内部自跑requestAnimationFrame循环;不开则需手动lenis.raf(time)
lerp0.1滚轮平滑的线性插值强度(0~1),定义"滑行距离"
duration未设置(回落 lerp 模式)动画时长(秒),与easing配套,一旦提供lerp即被忽略
easing(t) => Math.min(1, 1.001 - 2 ** (-10 * t))时间模式下的缓动曲线
smoothWheeltrue滚轮输入是否走平滑
syncTouchfalse模拟原生触摸滚动并同步位置(iOS<16 可能不稳)
syncTouchLerp0.075触摸惯性阶段的插值系数
touchInertiaExponent1.7触摸抬手后的惯性强度指数
wheelMultiplier/touchMultiplier1滚轮 / 触摸输入灵敏度
orientation'vertical'滚动轴向,'horizontal'需配合具体wrapper
infinitefalse无限循环滚动
overscrolltrue类 CSSoverscroll-behavior的边界回弹
anchorsfalse接管锚点链接点击并平滑滚动
allowNestedScrollfalse自动放行嵌套可滚动元素的滚动
naiveDimensionsfalse用简化的尺寸计算(有性能代价)
autoTogglefalse按 wrapper 的 overflow 自动 start/stop
stopInertiaOnNavigatefalse点击站内链接时清除滚动惯性

进阶参数(3 条以内,各附原因):

  • prevent: (node) => boolean——按节点豁免平滑。比allowNestedScroll省,因为不需要每次事件遍历 DOM。
  • virtualScroll: (data) => boolean——在输入被消费前改写或否决它(返回false即放弃本次输入),适合"按住某个键时不平滑"这类条件交互。
  • naiveDimensions——改用scrollHeight - clientHeight直接算limit,逻辑更简单,README 标注有性能影响,谨慎开启。

性能调优(3 条以内,各附原因):

  • 一定引入lenis.cssdata-lenis-prevent元素的overscroll-behavior: contain靠它生效,少了会看到弹性回弹把外层页面带滚。
  • scroll事件每帧触发,回调里只读随实例带来的scroll/progress/velocity(都是现成属性,零布局读取);确需读布局就自行节流。
  • 页面尺寸完全固定时可设autoResize: false并手动调resize():内部ResizeObserver的重算本身带 250ms 防抖(packages/core/src/dimensions.ts),对静态页面是一次可省的开销。

生态集成与常见问题

接入 React 与 Vue 官方适配层

  • React(packages/react/):LenisProvider包裹应用后创建实例;组件内用useLenis(callback, deps, priority)注册滚动回调,回调按 priority 排序执行、卸载时自动移除(packages/react/src/use-lenis.ts)。
  • Vue / Nuxt(packages/vue/):同构的 provider +useLenis;Nuxt 模块在packages/vue/nuxt/module.ts,集成示例见playground/nuxt/plugins/lenis.ts
  • 吸附插件(packages/snap/):new Snap(lenis, { type })+snap.add(500 或元素)type支持'proximity'(默认)、'mandatory''lock'distanceThreshold默认'50%'、判定防抖 500ms(packages/snap/src/snap.ts),它监听的是 Lenis 的virtual-scroll事件,不与主滚动逻辑打架。

常见问题(现象 → 原因 → 解决)

  1. 现象:初始化后页面滚动毫无平滑感。原因autoRaf默认false,内部动画没有每帧推进。解决:开autoRaf: true,或在自己的循环里每帧lenis.raf(time)(README Troubleshooting 第一条)。
  2. 现象stop()之后仍能被滚。原因.lenis-stopped { overflow: clip }依赖推荐 CSS,没引入lenis.css时类名没有实际效果。解决:引入lenis/dist/lenis.css
  3. 现象:锚点链接点了没反应。原因:Lenis 默认在滚动期间拦截锚点行为。解决anchors: true(或传ScrollToOptions);hash 含特殊字符也能正常定位,内部走decodeURIComponent解码(lenis.tsonClick)。
  4. 现象:平滑滚动时 iframe 内容点不动。原因lenis.csslenis-smooth期间故意给 iframe 加pointer-events: none,防止它吞掉 wheel 事件导致滚动卡死。解决:需要交互时给 iframe 外层加data-lenis-prevent
  5. 现象:Safari 帧率卡在 60fps,省电模式只剩 30fps。原因:WebKit 对requestAnimationFrame的上限(README Limitations 列出的已知 bug)与系统省电策略。解决:属环境限制而非参数问题,不要为 Safari 单独加"补帧"逻辑。

继续去哪儿

  • 核心源码:packages/core/src/lenis.ts、选项类型与默认值:packages/core/src/types.ts
  • 插件与适配层:snap 吸附插件、React 适配层、Vue 与 Nuxt 模块
  • 可运行的演示:playground 目录(core / horizontal / infinite / snap / touch-debug 各有独立示例)
  • 想深入原理先读 MANIFESTO.md,参与开发看 CONTRIBUTING.md

lenis.ts文件头那六行注释加maths.ts里的damp,就是这套滚动系统的全部骨架——改一个参数之前先看这两处,比翻文档更快。

【免费下载链接】lenisSmooth scroll as it should be项目地址: https://gitcode.com/GitHub_Trending/le/lenis

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 16:14:45

ADAMS SPLINE驱动:用AKISPL函数让模型按数据表运动

简介&#xff1a;一份面向ADAMS用户的技术文档&#xff0c;讲解如何在机械系统动力学仿真中通过SPLINE驱动把外部规划的电机角度位置或速度数据导入软件&#xff0c;并应用于MOTION驱动。内容从准备txt格式的外部数据&#xff08;第一列为时间、第二列为位移&#xff09;讲起&a…

作者头像 李华
网站建设 2026/9/19 16:14:32

ROS 2 Lyrical 编译失败根源:CMake 4.x 与 rosdep 兼容性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 16:14:04

京东技术产品经理面试实战:需求拆解与系统边界意识

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 16:12:28

高中排列组合解题操作系统:从原理到27类实战策略

简介&#xff1a;本资源是一份面向高中数学学习者与教师的排列组合系统性复习资料&#xff0c;聚焦高考及数学竞赛中高频出现的核心考点与解题策略。文档全面梳理加法原理、乘法原理、排列与组合定义及公式推导&#xff0c;并深入解析9类典型应用技巧——包括捆绑法、插空法、定…

作者头像 李华