ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SVG波浪翻滚背景动画:从贝塞尔曲线到CSS性能优化实战

SVG波浪翻滚背景动画:从贝塞尔曲线到CSS性能优化实战 简介HTML5 SVG波浪翻滚背景动画特效是一份适合前端入门学习者的网页动态背景案例选用jQuery与CSS3技术栈解决网站背景单调、缺乏层次感的问题可应用于个人网站、产品宣传页等场景。压缩包共8个文件含4个JavaScript脚本、3个层叠样式表和1个演示页面整体约73KB结构精简便于直接部署与二次修改。已有105人学习下载。案例核心通过SVG路径元素绘制波浪曲线结合CSS3动画关键帧实现连续翻滚JavaScript负责控制DOM与交互演示页面中的样式表可调整颜色、尺寸与位移参数脚本则提供动画同步和浏览器兼容处理。借助配套演示文件与模块化目录前端初学者可直观对照代码理解SVG、CSS3动画与jQuery插件的协作方式并在修改路径数据或动画参数的过程中锻炼页面特效定制能力。1. 把“SVG波浪翻滚”拆开它到底是什么能用在哪些场景HTML5 SVG 波浪翻滚背景动画特效说白了就是用一个 SVG 路径画出波浪底形再让它在页面上周期性平移制造出水面持续涌动的错觉。它比 GIF 体积小比 Canvas 上手快不需要引入任何前端库是很多网页设计作业和活动页 Hero 区的首选背景方案。你看到的那些登录页底部一层层深浅不一的浪多半就是这么做出来的。这篇文章会从 path 数据讲起给你一份可以直接抄的完整 HTML再把动画参数和几个容易翻车的细节摊开说清楚。适合刚接触 SVG 动画的前端新手也适合想给页面加一个轻量背景但又不想引入框架的开发者。2. 选实现路径纯 CSS、SMIL 和 JS 控制我为什么最后选了 CSS 平移一开始我试过三种做法直接用 CSS 给 SVG 整体做 translateX 平移、用 SMIL 的 animate 改 path 的 d、用 requestAnimationFrame 每帧改 d。三者在视觉上都能让波浪动起来但性能和维护成本差很多。先说结论绝大多数只需要“无限循环、不跟鼠标交互”的波浪背景我都用 CSS transform 平移动画而且只让波浪沿水平方向移动不动路径本身。2.1 一条 SVG 波浪路径是怎么画出来的从贝塞尔曲线到 path 数据SVG 路径的 d 属性本质是一组绘图指令。画波浪用到的是三次贝塞尔曲线指令 C。一个 C 后面跟三组坐标当前点是起点第一组是起点控制点第二组是终点控制点第三组是终点。曲线从这里平滑地弯过去而不是像折线那样硬转折。举个例子下面这个 path 表示从左上角 (0, 120) 出发经过两个周期后在 (1440, 120) 收尾最后沿底部绕回起点闭合svg viewBox0 0 1440 220 xmlnshttp://www.w3.org/2000/svg preserveAspectRationone path dM0,120 C180,40 540,40 720,120 C900,200 1260,200 1440,120 L1440,220 L0,220 Z fill#2d6a9f/ /svg这里M0,120是起点第一个C180,40 540,40 720,120从 0 到 720 画出一个先上后下的完整波起点控制点 y 是 40让曲线先向上抬终点控制点 y 也是 40到 x720 时回到 120。第二个C900,200 1260,200 1440,120让曲线先下后上补齐另一个周期。这样从 x0 到 720 和从 720 到 1440 的波形完全重复动画循环时才有“接得上”的前提。SVG 的坐标系 y 轴向下所以 y 值越小越靠近屏幕顶部。想要波峰更高就把控制点的 y 调得离基线这里是 120更远想要波谷更深就把第二个周期控制点的 y 调到 200 附近。需要说明的是贝塞尔曲线的实际极值点并不落在控制点上控制点只是吸引曲线所以视觉波高会比控制点差值略小。我在调参时会把控制点差值放大 20% 左右这是经验值不是精确公式。viewBox0 0 1440 220的含义是在当前 SVG 的内部坐标系里横向 1440 个单位、纵向 220 个单位。配合样式里的preserveAspectRationone浏览器会把这段坐标区域拉伸填满实际 CSS 尺寸所以背景波浪不会因为屏幕宽高比变化而变形。这个特性在背景图里是优点但如果 SVG 里放了文字或图标千万别设成 none否则会被一起拉扁。2.2 三种动画方案对比CSS transform、SMIL animate、requestAnimationFrame 改 d我把三种方案在一个 1440 宽的页面上做了同样效果的动效对比结果可以简化成下面这张表方案实现方式性能交互控制适配成本CSS transform给包裹 SVG 的层加 keyframes好只动合成层弱只能用 animation 状态最低纯 CSSSMIL animate在 path 上加animate attributeNamed values...中每帧改路径会触发重绘中可用 begin/end 控制高Chrome/Safari 有差异JS requestAnimationFrame每帧用 JS 重写 d低频繁改 DOM 属性强可结合鼠标/滚动高要自己管理循环SMIL 有一个很“玄学”的点它可以对 d 做补间但要求前后两段路径的控制点数量完全一致。很多人把一份 8 个控制点的路径和 12 个控制点的路径做 animate结果浏览器直接不动画或跳到最后一帧。你要么手工补齐到同样数量要么用 JS 生成中间帧维护成本立刻上去了。所以我的选择是把波浪本身的位移交给 CSS把“要不要动、什么时候动”交给 JS 就够了。最推荐的结构是下面这个一个 div 负责定位和显示内部放一个宽度是容器两倍的 SVG然后让整个 SVG 层在水平方向来回平移style .wave-container { width: 100%; height: 220px; overflow: hidden; position: relative; } .wave-body { width: 200%; height: 100%; animation: waveMove 6s linear infinite; } keyframes waveMove { from { transform: translateX(0); } to { transform: translateX(-50%); } } /style div classwave-container div classwave-body svg viewBox0 0 1440 220 preserveAspectRationone stylewidth:100%;height:100% path dM0,120 C180,40 540,40 720,120 C900,200 1260,200 1440,120 L1440,220 L0,220 Z fill#2d6a9f/ /svg /div /div关键逻辑在于.wave-body的宽度是容器宽度的 200%而 SVG 的 viewBox 里恰好画了两个周期的波浪。动画从translateX(0)到translateX(-50%)相当于向左移动了一个周期 720 个单位。因为第二个周期和第一个周期一模一样移动结束后页面看到的位置正好和起始帧重合循环就不跳变。参数上唯一需要小心的是animation-duration的秒数决定了浪速6s 是让波浪看起来比较从容的值低于 3s 会让人感觉水面在抽搐超过 15s 又几乎看不出在动。透明度、颜色、层数都留到第 3 章调先把上面这份代码存成 html 双击打开能看到一根浪在滚就说明链路通了。2.3 为什么我不直接放背景图或 Canvas而坚持用 SVG有人会说一张切好的透明 PNG 波浪图配background-position动画不也一样一样但 PNG 放大到 4K 屏幕会发虚除非你做多倍尺寸文件体积又上去了。Canvas 能画更复杂的动态波浪但要自己处理分辨率、循环帧、性能回收对只想给页面加个背景的普通页面来说属于杀鸡用牛刀。SVG 是矢量任意缩放都清晰DOM 结构直观还能随时在 DevTools 里改 path 看效果——这是我最看重的一点调色和调浪高不用刷新页面。这里再多说一句SVG 的动态效果不一定非要写进 style也可以放在 SVG 内部用g分组。比如把两个 path 包在g里给g加 CSS 类。这样做的好处是将来如果要把波浪切成上下两部分直接复用这个g即可。我一般会先用这种方式搭骨架稳定后再决定要不要拆分到独立文件。如果你一定要做鼠标跟随交互也不要把 d 作为数据源而是把位移量塞给 CSS 变量第 6 章会有一个完整例子。3. 把背景做厚两层波浪 视差一份可以直接抄的 HTML 文件单层波浪已经很常见但要做出“翻滚”感至少要两层。两层波浪在速度、振幅、颜色上错开视觉上才会有近景远景的距离感就像海面上浪花叠着浪花。下面这份 HTML 是我在业务页里最常用的骨架复制成 html 文件用浏览器打开就能看到两层浪在 hero 区底部来回滚。3.1 拼接完整页面两层 SVG 波浪 关键帧动画!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleSVG 波浪背景示例/title style * { box-sizing: border-box; margin: 0; } body { font-family: system-ui, sans-serif; } .hero { position: relative; height: 100vh; background: linear-gradient(180deg, #0b1c3f 0%, #1e3a63 100%); overflow: hidden; } .hero-content { position: relative; z-index: 5; display: flex; height: 100%; align-items: center; justify-content: center; color: #fff; flex-direction: column; } .wave-layer { position: absolute; left: 0; width: 200%; height: 220px; will-change: transform; } .wave-layer svg { display: block; width: 100%; height: 100%; } .layer1 { bottom: 0; animation: waveMove 7s linear infinite; } .layer2 { bottom: 30px; animation: waveMove 11s linear infinite reverse; opacity: .8; } keyframes waveMove { from { transform: translateX(0); } to { transform: translateX(-50%); } } /style /head body section classhero div classhero-content h1SVG 波浪背景/h1 p二层错峰左右流动/p /div div classwave-layer layer1 svg viewBox0 0 1440 220 preserveAspectRationone path dM0,120 C180,40 540,40 720,120 C900,200 1260,200 1440,120 L1440,220 L0,220 Z fill#2d6a9f/ /svg /div div classwave-layer layer2 svg viewBox0 0 1440 220 preserveAspectRationone path dM0,150 C240,60 480,60 720,150 C960,220 1200,220 1440,150 L1440,220 L0,220 Z fill#4fc3f7/ /svg /div /section /body /html这个示例的结构分三块.hero作为定位上下文负责把波浪限制在页面顶部区域.hero-content放在内容层z-index 高于波浪保证文字不被遮挡两个.wave-layer分别承载一层 SVG。代码里最值得说的是width: 200%。因为 SVG 的 viewBox 宽度是 1440实际 CSS 宽度被拉到容器宽度的两倍动画位移-50%在浏览器内部会换算成当前元素自身宽度的一半也就是正好一个 viewBox 周期。如果直接把 SVG 宽度设为 100%那 -50% 只移动了半个周期循环时就会在左右两侧出现明显断层。两个图层用了不同的animation-durationlayer1 是 7slayer2 是 11s方向一个是 normal 一个是 reverse。速度差是“视差感”的主要来源近处的浪走得快远处的浪走得慢人眼就会自动把它们理解成不同空间深度。bottom: 30px让 layer2 浮在 layer1 上方 30px形成上下参差。3.2 参数调优波高、波长、速度、颜色和不透明度的设置口诀改参数之前先明确一个基准viewBox 里的单位不是 px而是坐标单位。当 preserveAspectRationone 且容器按百分比铺满时这些坐标会被映射到实际屏幕像素。比如 1440 个坐标单位对应 1920px 屏幕一个周期 720 坐标单位就会占屏幕宽度的一半差不多是 960px。屏幕越宽波长显示得越长这是符合直觉的。参数修改位置经验值波高path 控制点 C 的 y 值与基线 y 的差基线与控制点差 80~120浪高约 60~90px波长C 的 x 跨度两个 C 拼接成一个周期720 坐标单位适合 1440 宽模板速度animation-duration7s/11s 双图层颜色path 的 fill深色打底亮色叠加透明度opacity 或 fill-opacity上层 0.7~0.9下层 1.0调波高时最实用的套路是先固定基线 y 和底部封闭线 L 的 y然后只改 C 里的控制点 y。比如想浪再高一点把第一个周期的两个控制点 y 从 40 改成 20波谷控制点从 200 改成 220。每次只改一对数字刷新就能看到变化。波浪的视觉极值比控制点差值小一些所以别看到控制点到了屏幕边缘就以为浪会破出容器实际上还有缓冲。波长决定了波浪是“大浪”还是“碎波”。同样 viewBox 宽度下周期数越多浪越碎。把两个 C 的 x 跨度从 720 改成 480一个 viewBox 里就能放三个周期适合做细密的水纹改成 960则适合做宽阔的海浪。注意周期数必须是整数否则首尾无法对齐。调速度和方向时我习惯把两层时长设成质数关系比如 7s 和 11s。如果一层 6s 一层 12s那么每 12s 两层的相对位置会完全重复一次容易看出“这是循环播放”的破绽。反过来7 和 11 的最小公倍数是 77视觉上更难抓住重复点。3.3 响应式让 SVG 铺满视口且不变形如果你的波浪背景只在桌面端展示那用 1440 模板就够。但真实页面一定会遇到手机、平板、宽屏SVG 背景的响应式有三件事要做。第一SVG 元素本身要display:block。SVG 默认是 inline 元素会和文字一样在底部留下几像素空隙两层波浪叠在一起时会出现一条细细的“漏光”缝隙。给.wave-layer svg加上display:block能消掉这条缝隙如果还有残影给 svg 加margin-bottom:-1px。第二preserveAspectRationone是背景波浪的默认选项因为它允许 SVG 被拉伸到任意宽高比。但如果你的波浪 SVG 里加了云朵、船只这类有具体形象的图形就不能用 none否则图形会随屏幕变成椭圆。这时改成xMidYMid slice让 SVG 保持宽高比铺满容器多余部分裁掉。波浪背景本身没有可识别的物体所以一般不用纠结。第三移动端高度单位。.hero { height: 100vh }在手机浏览器上经常被地址栏挤来挤去底部一会儿多 60px 一会儿少 60px。常见做法是写成height: 100vh; height: 100dvh;最后还是用min-height: 600px兜底。这样至少画面不会因为地址栏收放而突然抖动波浪的位置也能稳定在可视范围底部。4. 性能与边界什么场景该用 SVG什么场景要换 Canvas 或 WebGL把两层波浪跑顺之后你的下一个问题大概率是再多加几层会不会卡能不能在低端安卓机上跑为什么我的页面滚动的时候波浪有顿挫感这些问题本质是没搞清楚 SVG 动画的渲染成本边界。这一章专门讲边界以及踩到边界后怎么退。4.1 SVG 图层多时的渲染成本重绘、合成层与 GPU 加速CSS transform 动画这几年被推崇核心原因是浏览器不需要重新计算布局也不需要重新绘制路径只需要把整个已经画好的图层在合成层上移动。transform 的每一帧只需要移动一张位图成本低这也是我坚持用 transform 而不是改 path 的原因。但成本低不等于免费每多一个.wave-layer就是多一张合成层浏览器要额外维护一张和容器一样大的纹理低端设备的 GPU 显存和带宽都有限。我见过一个很典型的翻车项目页面底部放了 6 层波浪每层都给了will-change: transform结果在骁龙 660 的手机上动画只有 20 帧左右。原因不是 transform 慢而是 6 张 1440 宽、220 高的 SVG 纹理叠加每一帧都要做透明混合像素填充量太大。那次调优最后砍到 3 层并把第二、第三层的宽度从 200% 降到 150%观感几乎没变帧率稳定了。所以我的建议是波浪图层超过 3 层之前SVGCSS 方案都还算舒服超过 3 层先思考是不是一定要那么多层而不是急着换技术方案。will-change: transform也不是加得越多越好它其实是在告诉浏览器“这个元素要动提前给我合成层”。给两三个关键元素加没问题给页面一半元素加反而会耗尽合成层资源。另外SVG 里的filter是性能杀手。比如给 path 加drop-shadow(0 0 10px)视觉上是浪花边缘发光实际上每一帧合成时都要重新计算滤镜。如果你非要光影效果优先用半透明的 fill 颜色模拟或者给整个图层加一个静态的模糊背景而不是实时滤镜。4.2 和 Canvas 波浪的对比多少层、多少屏宽之后 SVG 不划算我从一个实际项目里总结的判断标准波浪层数 ≤3、页面不涉及浪上的可点击物体、只做无限循环时SVG 一定是最省事的。需要动态响应鼠标、触摸、音频输入去“搅动”波浪或者波浪层数超过 4 层我会换成 Canvas再用 requestAnimationFrame 逐帧画。维度SVG CSSCanvas实现难度低CSS 控制中要写绘制循环动态交互弱需要通过 JS 改路径或类名强每帧可读取鼠标坐标复用性DOM 可审查、可复制画布上的内容拿不到节点适合规模2~3 层背景粒子、实时水波、复杂扰动如果你只是想要“波浪翻滚背景”Canvas 是多余的。很多教程会让新手用 Canvas 画十几个点连成波形再逐帧更新等你想加点击事件时会发现无从下手——因为 Canvas 没有 DOM 节点你只能自己记录每个点的位置再计算命中。SVG 的 path 天生是 DOM甚至可以在波浪上放一个透明的圆形按钮直接绑事件。4.3 移动端适配与功耗把动画时长拉长减少同步重绘手机屏幕小的同时GPU 带宽通常也弱。我在移动端会把动画时长压得比桌面端普遍长一些桌面 waveMove 用 5s移动端改用 7s 或 9s。不是玄学是同样的位移在每帧上移动的距离更小重绘压力更小。还有一点容易被忽略如果两层波浪的帧率不一致会在视觉上形成“水波纹打架”的摩尔纹感。浏览器渲染时两层 transform 动画如果 duration 太接近比如 6.2s 和 6.3s经常会出现周期性卡顿。我一般会把 duration 差开到 3s 以上并让第二层的 animation-delay 设置负值比如animation-delay: -2s这样页面一打开两层就已经处在不同相位不需要用户等两个周期才能看到完整效果。如果想进一步降低功耗可以响应系统的“减弱动态”设置。检测到用户开了这个选项就把动画停掉保留一个静止的波浪形状media (prefers-reduced-motion: reduce) { .wave-layer { animation: none; transform: translateX(-25%); } }这里的translateX(-25%)是让两层在静止状态下错开不会所有层都从同一个位置开始。参数 -25% 等于 viewBox 半周期偏移停在波浪的中间位置视觉上比停在起点自然。4.4 用 Performance 面板验证掉帧三步定位瓶颈不要凭感觉判断卡不卡。我常用的方法是打开 DevTools 的 Performance 面板录制 5 秒在录制期间滚动页面并让波浪保持运动。然后看两件事一是 Frames 时间线里有没有红色长帧二是火焰图里有没有大块的紫色渲染任务。如果紫色任务集中在 Paint 阶段说明有滤镜正在强制重绘如果掉帧但没有明显渲染任务问题往往出在合成层过多或移动端 GPU 带宽上。验证修改是否有效时只动一个变量。比如先关掉will-change再录一次然后把 filter 去掉再录一次。每改一次记录 CPU 和 GPU 的占用。不要同时改三个参数否则你永远不会知道是哪一步救回了性能。确认稳定后再逐步把无关调试代码清理掉避免把监视器留在正式环境。5. 避坑 / 常见问题排查SVG 波浪背景最容易翻车的 5 个细节很多人做波浪背景最大的困惑不是画不出来而是画出来之后明明照着教程写的却总有一些奇怪的小毛病底部漏白、动画跳变、解压后文件打不开。下面这几条都是从真实项目里踩出来的常见问题每条按现象、原因、解决列出来。5.1 动画循环时断层像扇子一样“啪”地弹回来现象波浪向左流动流到某个位置后突然跳回起点不是无缝循环而是带有明显卡顿或闪断。原因动画位移量与路径周期对不上。最常见的写法是 path 里只画了一个周期但translateX(-50%)移动的是半个周期或者是 path 首尾 y 值不一致导致循环点处曲线高度跳变。还有人是直接从别人的模板里复制 path没注意到 viewBox 宽度是 2880这两个值必须配套。解决先确认 path 的起点和终点 y 相等。比如M0,120 ... 1440,120首尾都在 120。再看 path 在 viewBox 中包含了几个周期位移设为 1/N 的百分比。N2 时用 -50%N3 时用 -33.33%。调试时打开 DevTools 的 Animation 面板暂停动画把进度拉到 0% 和 50% 对比如果两帧画面一致说明循环没问题。注意这里的一致性是指波浪形貌一致不是每个控制点位置一致。5.2 波浪底部露出一条白边或页面底部有一根线现象波浪和页面背景之间有一条 1~3px 的缝隙白色或页面底色从缝隙里透出来页面缩放到不同宽度时缝隙时有时无。原因SVG 默认是 inline 元素底部会被当作文字基线留下空隙另一个原因是 path 的底部封闭线L0,220 L1440,220没有真正贴到 viewBox 的下边缘或者容器高度与 viewBox 高度不匹配。preserveAspectRationone 会把 viewBox 拉伸到容器尺寸如果 L 的 y 坐标不是 viewBox 高度填充区域就碰不到容器底部。还有可能是两层 wave-layer 叠加时上层半透明导致的混合缝隙。解决给.wave-layer svg加display:block确认 path 最后收尾的 L 指令中 y 值等于 viewBox 的高度例如 viewBox 高 220 就写L0,220 L1440,220 Z容器加overflow:hidden兜底。如果还是有一根线把.wave-layer { margin-bottom: -2px; }或给 svg 加vertical-align: bottom二选一即可不要同时用否则可能把波纹裁掉一截。5.3 页面滚动时波浪卡顿帧率掉到 20 帧现象平时波浪在首屏很流畅一旦上下滚动或旁边有图片懒加载波浪就像被拖住一样一卡一卡。原因滚动时浏览器要重新绘制大量区域如果波浪占用的合成层太多滚动线程与动画线程会互相抢资源。还有可能是动画用了 left 或 margin-left 作为位移属性这两个属性会触发主线程布局而不是走合成层。另一种常见误用是给 wave 的父级加了background-attachment: fixed导致整个背景层每帧都要重新绘制。解决确认位移用的是transform: translateX不是 left把多余的合成层去掉保留 2~3 层检查 CSS 里有没有 filter/backdrop-filter有就先去掉测试。打开 DevTools Performance 录制看帧火焰图里是否有大量紫色渲染任务。如果确认是合成层过多把不太重要的那一层直接用display:none临时关闭对比帧率变化。5.4 从 zip 压缩包里解压后 SVG 文件显示乱码或预览空白现象下载下来的「HTML5 SVG波浪翻滚背景动画特效.zip」用 Windows 右键解压后得到一个 html 和几个 svg 文件双击打开 html页面空白单独看 svg 显示乱码有些压缩包甚至提示“文件损坏”或要求输入密码。原因这种资源包多数是别人制作的作业或模板打包时用了非 UTF-8 文件名编码或者用了 zip 伪加密。zip 伪加密的特征是文件目录里标记了加密但实际数据并没有真正加密解压工具会误判成“需要密码”。文件名乱码则是因为系统默认用 GBK 解 UTF-8 文件名导致 html 里引用的 svg 路径对不上。解决先用 7-Zip 打开压缩包如果它要求密码但你没有可以试试直接拖拽文件到文件夹很多伪加密文件能拖出来。文件名乱码的情况下在 7-Zip 的菜单里切换编码再解压。解压后不要改目录层级html 里引用的 svg 路径通常是相对路径一旦挪到别的文件夹就空了。这也是我在前面把 SVG 直接内联进 HTML 的原因之一内联不存在外部引用失败的问题。5.5 不同浏览器下波浪高度不一样Safari 里显得特别扁现象同一份代码Chrome 里波浪高度正常Safari 或 Firefox 里波浪被压得很扁或者位置偏上/偏下。原因viewBox 比例与 CSS 高度不一致时preserveAspectRationone 会按各自浏览器的舍入方式拉伸。更常见的原因是height: 100%的父级高度没算对——Safari 对 flex 容器内的百分比高度比较严格如果父级没有明确高度SVG 会塌缩到默认高度。另一个隐蔽原因是 CSS 里同时设了svg { max-width: 100% }这个全局样式会覆盖 SVG 的宽度导致 viewBox 被压缩。解决给 wave-layer 设置固定高度比如height: 180px; max-height: 22vh;让高度不依赖父级计算避免对 svg 使用max-width:100%换成.wave-layer svg { width:100%; height:100% }。跨浏览器验证时至少看一下 Chrome、Safari、Edge 三个内核再决定是否需要给 Safari 单独覆盖。6. 进阶技巧让波浪跟随滚动和鼠标移动再做一层可交互的浪尖前面的方案是纯 CSS适合固定背景。但如果你想在活动页里让波浪对用户的操作有反应有一个不破坏原动画的低成本做法外层容器做交互位移内层保留无限循环动画。这样交互和循环互不覆盖也不会把动画重写到 JS 里。6.1 用 CSS 变量把鼠标位置传给两层波浪给第 3 章的 HTML 做一点改动把每个 wave-layer 改成两层结构外层负责“视差偏移”内层负责“循环滚动”。监听 pointermove 时把鼠标相对屏幕中心的位置归一化到 -0.5 ~ 0.5赋值给 CSS 变量 --driftconst hero document.querySelector(.hero); hero.addEventListener(pointermove, (e) { const drift (e.clientX / window.innerWidth - 0.5).toFixed(3); hero.style.setProperty(--drift, drift); });CSS 里分别让两层按不同比例响应.wave-layer { transform: translateX(calc(var(--drift, 0) * 40px)); } .layer2 { transform: translateX(calc(var(--drift, 0) * -60px)); } .wave-inner { width: 200%; animation: waveMove 7s linear infinite; }这里的 40px 和 -60px 是视差幅度。鼠标越往右第一层向右偏移 40px第二层向左偏移 60px两层速度方向相反会产生背景和前景分离的立体感。数值不宜太大超过 100px 会露出波浪层边缘因为 wave-layer 本身已经铺满再偏移就可能露出容器边界。如果希望鼠标只在容器内有效把监听事件绑到 hero 上即可。6.2 让波浪高度随滚动变化一个极简的视差背景除了鼠标位置滚动也能驱动波浪。思路是监听 scroll 事件把页面滚动的比例映射成 wave-layer 的 translateY让波浪在滚动时比内容移动慢半拍形成视差。注意对 scroll 监听要做节流或请求帧否则移动端会很卡let ticking false; window.addEventListener(scroll, () { if (!ticking) { window.requestAnimationFrame(() { const y window.scrollY * 0.2; hero.style.setProperty(--scroll-y, y.toFixed(1)); ticking false; }); ticking true; } });配合 CSS.hero .wave-layer { transform: translateY(calc(var(--scroll-y, 0) * -1px)); }。这个参数 0.2 表示波浪移动速度约是页面滚动速度的 1/5看起来像远景。别用超过 0.5否则波浪会抢视线。做完这些交互后我总会做一次“静态验收”把所有动画暂停看波浪首尾能否无缝衔接再把浏览器窗口从 320px 拉到 1920px看有没有露边最后录一段 Performance确认帧率稳定。这套流程走完一个 SVG 波浪背景才算真正可以交付。希望这些踩坑经验能帮你省下几个小时的调试时间也希望这个方向能成为你以后给页面加质感时一个顺手就能用的选择。本文还有配套的精品资源点击获取
返回列表