animation-fill-mode: forwards的作用是让动画结束后保持最后一帧样式,但需配合完整的100%关键帧、避免样式覆盖、正确使用简写语法,并注意兼容性和JS设置时机。

直接用 CodePen、JSFiddle 这类在线环境做创意网页动画,完全可行,但必须避开三个高频翻车点:CSS 动画没加 animation-fill-mode 导致结束帧丢失、requestAnimationFrame 在无 canvas 上误用、以及忘记设 will-change: transform 引发卡顿。
为什么 CodePen 比本地开浏览器更适合作动画原型
在线编辑器默认启用实时渲染管线,DOM 更新 + CSS 层合成(compositing)都在同一帧内调度,比本地手动刷新 index.html 少了文件 I/O 和缓存校验延迟。尤其对依赖 transform 和 opacity 的动画,预热快、帧率稳。
- CodePen 自动注入
prefers-reduced-motion: reduce检测逻辑,避免用户端强制关闭动画却没 fallback - JSFiddle 的 “No wrap” 模式下,
document.addEventListener('DOMContentLoaded', ...)不会因执行时机错位而漏绑事件 - 所有平台都默认启用
translateZ(0)强制 GPU 加速 —— 但仅限于有 transform 属性的元素,纯left/top位移仍走主线程
@keyframes 动画必须配 animation-fill-mode
写完 @keyframes slideIn { from { opacity: 0; } to { opacity: 1; } } 后,只加 animation: slideIn 0.6s ease-out; 是不够的。动画播完瞬间,元素会“弹回”初始样式(即 opacity 回到未声明值),视觉上像闪一下。
- 正确写法是补上
animation-fill-mode: forwards;,让最终帧样式保留 - 若动画需循环且首尾衔接自然,用
animation-fill-mode: both; - 注意:Safari 旧版(≤15.6)对
forwards在transform上的支持有 bug,可加transform: translateZ(0);强制触发层合成兜底
Canvas 动画别在 mousemove 里直接绘图
鼠标移动事件每秒可能触发 60+ 次,如果每次都在 mousemove 回调里调用 ctx.fillRect(),等于每秒强行重绘 60+ 次,CPU 占用飙升,动画反而掉帧。
立即学习“前端免费学习笔记(深入)”;
- 正确做法:只在
mousemove中更新粒子坐标数组,绘图统一交给requestAnimationFrame循环处理 - 务必用
ctx.clearRect(0, 0, canvas.width, canvas.height)清屏 —— 不能靠canvas.width = canvas.width,后者会重置整个上下文状态(包括 lineCap、shadow 等) - 粒子数量超过 200 个时,考虑用
ctx.globalAlpha控制整体透明度,比逐个设particle.alpha更省性能
transition 触发要靠 class 切换,不是直接改 style
想让一个 div 点击后平滑缩放,写 el.style.transform = 'scale(1.2)'; 是无效的 —— 浏览器检测不到“状态变化”,transition 不会启动。
- 必须通过添加/移除 class 来触发:先定义
.zoomed { transform: scale(1.2); transition: transform 0.3s ease; },再用el.classList.toggle('zoomed') - 如果需要 JS 动态控制参数(比如缩放倍数随数据变),用 CSS 自定义属性更可靠:
el.style.setProperty('--scale', '1.3');,然后在 CSS 里写transform: scale(var(--scale)); - 慎用
transition: all 0.3s;—— 它会监听所有可动画属性,连height、width这种触发布局(layout)的属性也会参与过渡,极易引发重排卡顿
真正难的不是写出第一个跳动爱心或飘落花瓣,而是让 20 个动画元素在低端安卓机上不掉帧、不抢主线程、不和屏幕阅读器冲突 —— 这些细节在线环境里一眼就能暴露,别绕开它。



















