react-spring 视差滚动实战:Parallax 与 ParallaxLayer 完全指南

发布时间:2026/9/19 12:50:35
react-spring 视差滚动实战:Parallax 与 ParallaxLayer 完全指南 react-spring 视差滚动实战Parallax 与 ParallaxLayer 完全指南【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring导读react-spring/parallax是 react-spring 生态中专用于构建滚动视差Parallax效果的组件包Parallax负责创建一个可滚动容器ParallaxLayer承载你的内容并按照各自offset与speed产生位移实现同一滚动位置、多层不同速度的视觉纵深。读完本文你将掌握该组件的安装方式、全部 Props 与命令式 ref API、sticky 粘性图层用法以及从源码层面理解 spring 物理动画是如何驱动滚动与图层位移的可直接在你的 React 项目中落地可复用的视差页面。一、安装与前置条件yarn add react-spring/parallax注意根据 packages/parallax/README.md 的说明当前版本只支持react-spring/web因此该组件仅适用于浏览器环境不能用于 React Native 等非 Web 渲染目标。从 packages/parallax/package.json 可以看到其依赖关系内部依赖react-spring/shared与react-spring/web均为工作区内的独立包并声明了react ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0与对应react-dom作为 peerDependencies说明它兼容 React 16.8 起的 Hook 时代直至 React 19。使用时导入两个组件import { Parallax, ParallaxLayer } from react-spring/parallax二、快速上手一个最小可运行的视差页面README 给出的核心示例覆盖了本组件的绝大部分能力原样贴出并逐段说明import { useRef } from react import { Parallax, ParallaxLayer } from react-spring/parallax const Example () { const ref useRef() return ( Parallax pages{3} ref{ref} ParallaxLayer offset{0} speed{2.5} pLayers can contain anything/p /ParallaxLayer ParallaxLayer offset{1} speed{-2} factor{1.5} horizontal / ParallaxLayer sticky{{ start: 1, end: 2 }} / ParallaxLayer offset{2} speed{1} button onClick{() ref.current.scrollTo(0)}Scroll to top/button /ParallaxLayer /Parallax ) }要点拆解pages{3}声明容器总空间为 3 页每页占视口的 100%第一层以speed{2.5}快速移动第二层用负速度speed{-2}反向移动并配合factor{1.5}加高horizontal让它沿水平轴运动sticky{{ start: 1, end: 2 }}让该层在 12 页之间保持粘住不动最后一层上的按钮通过ref.current.scrollTo(0)实现点击回顶——这是命令式 API 的典型用法。ParallaxLayer的内容可以是任意 React 节点文本、图片、按钮都可以直接作为其 children。三、Parallax滚动容器组件详解Parallax创建了一个可滚动容器ParallaxLayer们负责其中的内容并按各自参数移动。README 中给出了完整的 Props 表PropertyTypeDescriptionpagesnumberTotal space of the container. Each page takes up 100% of the viewport.config?SpringConfigThe spring behavior. Defaults toconfig.slow(见 configs)。enabled?booleanWhether or not the content can be scrolled. Defaults totrue.horizontal?booleanWhether or not the container scrolls horizontally. Defaults tofalse.innerStyle?CSSPropertiesCSS object to style the innerParallaxwrapper (not the scrollable container)结合 packages/parallax/src/index.tsx 的源码ParallaxProps定义与组件默认值可以给出更精确的语义pages必填number容器的总页数决定内部内容的总长度。源码中update()会计算content.style[sizeProp] ${state.space * pages}px即内容高度/宽度 每页空间 × 页数。每页空间space取容器在滚动轴上的clientWidth水平或clientHeight垂直。config可选SpringConfig滚动与位移所用的 spring 物理参数默认configs.slow源码第 225 行config configs.slow即缓慢配置。它同时作用于滚动动画和每一层的位移/尺寸动画。enabled可选boolean是否允许内容被滚动默认true。当设为false时源码会把容器的 overflow 全部设为hidden并将滚动位置强行设置为state.offset * state.space即内容不再可滚动、固定在某偏移处——适合做程序驱动、禁用用户滚动的场景。horizontal可选boolean容器是否水平滚动默认false。水平时读取scrollLeft/clientWidth垂直时读取scrollTop/clientHeight对应源码中getScrollType与update()的选择逻辑。innerStyle可选CSSProperties作用于内部内容包裹层a.divcontentRef指向的那个 div的样式对象不作用于外层可滚动容器。注意容器本身的样式可通过透传的style/className设置。此外由于ParallaxProps extends ViewPropsReact.ComponentPropsWithoutRefdiv所有HTMLDivElement的原生属性style、className、onClick、data-*等都可以直接传给Parallax这与官方文档 docs/app/routes/docs.components.parallax.mdx 中的说明一致。容器默认样式源码中容器 div 的内置样式为{ position: absolute, top: 0, left: 0, width: 100%, height: 100%, ...overflow, // 依据 enabled / horizontal 决定 overflowX / overflowY WebkitOverflowScrolling: touch, transform: translate3d(0px,0px,0px), // ...rest.style 最后合并 }也就是说Parallax默认是一个铺满父级的绝对定位滚动容器你可以通过传入style覆盖top/left/width/height等属性官方示例中style{{ top: 0, left: 0 }}即如此。四、Parallax 的命令式 APIref除了声明式 PropsParallax还通过 ref 暴露了若干实用的命令式属性。README 文档中列出了三个最常用的1.ref.current.scrollTo(offset)点击滚动函数接收一个参数要滚动到的页数。页数从 0 开始因此scrollTo(0)滚动到第一页scrollTo(1)滚动到第二页依此类推。const ref useRef() // ... Parallax ref{ref} // 某处触发 ref.current.scrollTo(2)源码中的实现scrollTo方法显示它并非瞬间跳转而是使用 spring 物理动画平滑滚动const scrollTo (offset: number) { const container containerRef.current const scrollType getScrollType(horizontal) state.offset offset state.controller.set({ scroll: state.current }) state.controller.stop().start({ scroll: offset * state.space, config, onChange({ value: { scroll } }) { container[scrollType] scroll }, }) }即先把目标值设为offset * state.space用内部Controller类型为Controller{ scroll: number }按config的 spring 配置驱动每帧通过onChange回写容器的scrollTop/scrollLeft从而产生带惯性的平滑滚动效果。2.ref.current.container外层容器 div 的 ref用于获取真正的 DOM 元素。注意它本身也是一个 ref必须通过ref.current.container.current访问。const domNode ref.current.container.current domNode.addEventListener(scroll, handler) // 在容器上监听滚动事件3.ref.current.content内层容器 div 的 ref即innerStyle作用、承载各图层的包裹层同样通过ref.current.content.current访问。IParallax 完整接口README 虽只详述了上述三个属性但 packages/parallax/src/index.tsx 中IParallax接口暴露了更完整的状态官方文档 docs/app/routes/docs.components.parallax.mdx 也给出了完整定义interface IParallax { config: ConfigProp horizontal: boolean busy: boolean // 是否正在批量处理滚动事件raf 调度中 space: number // 每页空间容器在滚动轴上的尺寸 offset: number // 最近一次 scrollTo 的目标偏移 current: number // 当前滚动位置 controller: Controller{ scroll: number } layers: SetIParallaxLayer container: React.MutableRefObjectany content: React.MutableRefObjectany scrollTo(offset: number): void update(): void // 手动重新测量并同步所有图层 stop(): void // 停止正在进行的滚动动画 }其中update()与stop()也是可用的方法update()会重新测量容器尺寸、刷新各图层的位置与高度在窗口 resize 后尤其有用stop()则立即终止进行中的程序化滚动。这些能力都有对应的单元测试覆盖见 packages/parallax/src/parallax.test.tsx测试验证了scrollTo、update、stop均为函数、layers是Set、controller/container/content均存在并断言scrollTo(1)后container.scrollTop恰好等于space500px证明滚动目标是页数 × 每页空间。五、ParallaxLayer图层参数详解ParallaxLayer是真正承载内容并产生视差位移的组件。README 的完整 Props 表如下PropertyTypeDescriptionfactor?numberSize of the layer relative to page size (eg:1 100%,1.5 150%, etc). Defaults to1.offset?numberThe offset of the layer when its corresponding page is fully in view (eg:0 top of 1st page,1 top of 2nd page, etc ). Defaults to0.speed?numberRate at which the layer moves in relation to scroll. Can be positive or negative. Defaults to0.horizontal?booleanWhether or not the layer moves horizontally. Defaults to thehorizontalvalue ofParallax(whose default isfalse).sticky?StickyConfigIf set, the layer will be sticky between the two offsets. All other props are ignored. Default:{start?: number 0, end?: number start 1}结合源码ParallaxLayerProps与组件默认解构factor 1, offset 0, speed 0逐一说明factor可选number默认 1图层在滚动轴方向上的尺寸与每页空间的比值。1表示 100%即一整页高度/宽度1.5表示 150%。源码中图层通过 spring 值space: parent.space * factor控制尺寸对应ctrl.start({ space: height * factor })并在样式里把滚动轴方向的尺寸设为该值、垂直方向设为100%。测试 packages/parallax/src/parallax.test.tsx 也验证了factor{2}时图层高度为space * 2 1000px。offset可选number默认 0该图层对应页面完全进入视口时图层最终停留的位置。0表示第一页顶部1表示第二页顶部支持小数如1.5表示第二页一半处。见下文使用注意中对终点而非起点语义的解释。speed可选number默认 0图层相对滚动的移动速率可为正数或负数。正数同向移动、负数反向移动绝对值越大滚动时位移越显著。速度会影响图层的初始位置但不影响最终offset位置。horizontal可选boolean该图层是否沿水平方向移动。默认继承Parallax的horizontal默认false。源码中决定 transform 方向horizontal时使用translate3d(${x}px,0,0)否则translate3d(0,${y}px,0)。sticky可选{ start?: number; end?: number }设置后图层会在两个 offset 之间保持粘住CSSposition: sticky效果此时其余所有 Propsfactor/offset/speed/horizontal均被忽略。默认值{ start 0, end start 1 }源码第 138-140 行的const start sticky.start || 0; const end sticky.end || start 1。使用注意README 原文要点offset是终点不是起点offset决定图层最终到达的位置。例如offset{1.5}的图层会在第二页完全填满视口时位于第二页零基索引的一半处。speed影响起点不影响终点speed只改变图层滚动中的初始位置不会改变其最终offset位置。这从源码的位移公式可以印证见下文底层原理。sticky 图层层级更高任何设置了sticky的图层都会拥有比普通图层更高的z-index可手动修改覆盖。源码中的实现是Parallax渲染时把子元素分成两批——先渲染所有非 sticky 图层再渲染所有 sticky 图层mapChildrenRecursive两次遍历见 packages/parallax/src/index.tsx 第 376-388 行sticky 图层在 DOM 中靠后天然叠在上层。六、sticky 粘性图层深入sticky 是ParallaxLayer最有特色的能力之一让某个图层在滚动经过指定区间时粘住不动其余内容继续滚动。其底层实现源码setSticky函数值得展开const setSticky (height: number, scrollTop: number) { const start layer.sticky.start * height const end layer.sticky.end * height const isSticky scrollTop start scrollTop end if (isSticky layer.isSticky) return layer.isSticky isSticky const ref layerRef.current ref.style.position isSticky ? sticky : absolute ctrl.set({ translate: isSticky ? 0 : scrollTop start ? start : end, }) }要点当滚动位置落在[start * space, end * space]区间内时图层样式切换为position: sticky且位移归零——表现为钉在当前视口离开区间后恢复absolute定位并立即跳到区间起点或终点对应的位置start/end以页为单位如{ start: 1, end: 3 }表示从第 1 页到第 3 页之间保持粘住可参考仓库中的完整示例 demo/src/sandboxes/parallax-sticky/src/App.tsx其中sticky{{ start: 1, end: 3 }}的卡片在 13 页滚动区间内保持不动而speed{1.5}的普通图层继续产生视差位移。七、底层原理spring 物理如何驱动滚动与图层位移这一节从源码层面解释为什么Parallax的滚动是平滑带惯性的让读者不仅会用、而且理解其机制。1. 图层位移公式每个非 sticky 图层的位移由以下公式计算源码setPositionconst targetScroll Math.floor(offset) * height const distance height * offset targetScroll * speed ctrl.start({ translate: -(scrollTop * speed) distance, config: parent.config, immediate, })即translate distance - scrollTop * speed distance height * offset floor(offset) * height * speed可以看到滚动位置scrollTop乘以speed贡献了实时位移负号使方向相反而distance是固定常数它保证当滚动到达offset对应的页面时translate恰好回到目标位置——这正是speed影响起点、offset决定终点的数学来源。2. 滚动事件与 raf 批处理Parallax的滚动事件不是直接同步更新所有图层而是通过raf.onStart在下一帧开始前统一批量更新源码onScrollconst onScroll (event) { if (!state.busy) { state.busy true state.current event.target[getScrollType(horizontal)] raf.onStart(() { state.layers.forEach(layer layer.setPosition(state.space, state.current)) state.busy false }) } }busy标志保证一帧内只处理一次滚动回调避免高频滚动事件造成重复计算所有图层在同一帧内更新保证视觉同步。同时onWheel与onTouchStart都会调用state.stop()即用户手动滚轮/触摸时会终止进行中的程序化滚动动画避免两者冲突。3. 尺寸测量与窗口 resizeupdate()每次都会重新读取容器的clientWidth/clientHeight作为space并把内容层尺寸设为space * pages窗口大小变化时Parallax通过window.addEventListener(resize, ...)监听并使用raf.onFrame(update)在动画帧上重算同时还保留了setTimeout(update, 150)兜底——源码注释说明部分浏览器在最大化窗口时不会触发 resize 事件。4. 动画输出translate3d 与 will-change每个图层的translate是一个 spring 值ctrl.springs.translate通过.to(...)映射为translate3d字符串并写入样式const translate3d ctrl.springs.translate.to( layer.horizontal ? x translate3d(${x}px,0,0) : y translate3d(0,${y}px,0) )同时图层样式内置了willChange: transform与position: absolute配合translate3d让位移只触发合成层compositor而不触发重排保证视差滚动时的性能。图层的尺寸滚动轴方向的space值同样是一个 spring因此factor带来的高度变化也是平滑过渡的。八、从测试与示例验证行为仓库中既有单元测试又有可运行的沙箱示例可作为学习与验证的素材单元测试packages/parallax/src/parallax.test.tsx在固定尺寸400×500的容器内渲染验证了三条核心行为——命令式 API 完整可用scrollTo/update/stop/layers/controller等scrollTo(1)会把scrollTop动画到 500px即space × 1factor{2}的图层高度为 1000px。纵向视差demo/src/sandboxes/parallax-vert/src/App.tsx3 页纵向滚动的星空场景综合运用了factor{3}的全屏背景层、多个不同speed的云层含负速度、以及通过onClick{() parallax.current.scrollTo(n)}驱动的点击跳页交互。横向视差demo/src/sandboxes/parallax/src/App.tsxParallax pages{3} horizontal的水平滚动版本每个页面用speed{0.2 / 0.3 / 0.6}的多层组合出坡度感。sticky 视差demo/src/sandboxes/parallax-sticky/src/App.tsx演示 sticky 图层与普通视差图层的共存。本地调试环境packages/parallax/test/src/App.tsx提供了 vertical / horizontal 两个路由的可视化测试页test目录配套了 vite 配置与package.json中的test: vite serve ./test脚本可直接pnpm test在 packages/parallax 目录下启动预览。九、常见问题与注意事项汇总 README 与官方文档反复强调的边界条件避免踩坑Parallax的直接子元素必须全部是ParallaxLayer或者直接子元素全部是ParallaxLayer的React.Fragment。源码中的mapChildrenRecursive会递归穿透 Fragment 收集图层其余节点不会被当作图层处理。滚动事件只从容器本身触发Parallax是一个可滚动容器所有 scroll 事件都来自容器自身监听window的 scroll 不会生效如需额外监听请使用ref.current.container拿到 DOM 元素后自行绑定。offset表示终点而非起点它决定图层最终落点所在的页位置理解这一点才能正确规划多图层布局。speed只改变起始位置它影响滚动过程中的相对位移速度与初始位置但不会改变图层在offset处的最终位置。sticky 图层会忽略其他所有 Props一旦设置了stickyfactor/offset/speed/horizontal均不生效其区间由start/end定义默认{ start: 0, end: start 1 }。config决定一切动画手感滚动动画、图层位移与尺寸变化共用同一个 spring 配置默认config.slow想获得更轻快的跟手效果可以传入config.wobbly或自定义SpringConfig如{ tension: 170, friction: 26 }。仅 Web 可用当前只支持react-spring/web渲染目标服务端渲染SSR或非浏览器环境不适用。十、延伸阅读组件包文档与 Props 表packages/parallax/README.md完整源码实现含IParallax/IParallaxLayer接口、位移公式、sticky 逻辑packages/parallax/src/index.tsx官方组件文档含IParallax完整类型与示例入口docs/app/routes/docs.components.parallax.mdx单元测试packages/parallax/src/parallax.test.tsx纵向 / 横向 / sticky 三套可运行示例demo/src/sandboxes/parallax-vert/src/App.tsx、demo/src/sandboxes/parallax/src/App.tsx、demo/src/sandboxes/parallax-sticky/src/App.tsx至此从安装、声明式 Props、命令式 ref API、sticky 粘性图层到 spring 驱动的底层位移机制与测试验证你已经掌握了 react-springParallax视差组件的完整用法可以直接将其应用于落地页、作品集与品牌故事页等需要多层级滚动叙事的场景。【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考