← CSS Animation · 能力速览 / 番外:CSS 之外,网页动画还有哪些做法? 待审核 22 / 22
番外 · 动图 / video / Lottie / SMIL / canvas

番外:CSS 之外,网页动画还有哪些做法?

本系列把 CSS 动画讲完了,但网页上的「会动」远不止 CSS 一条路:聊天里的 GIF 表情、产品页的实拍视频、App 里的 Lottie 插画动效、可视化里的 canvas 粒子……每种技术的本质载体不同——有的是逐帧位图文件(动图)、有的是视频编码(video)、有的是矢量关键帧数据 + 运行时渲染器(Lottie / SMIL)、有的干脆每帧用代码重画(canvas / WebGL)。这页番外逐个盘点,每种配一个现场 demo。

1 · 全景对比

技术 本质 缩放 / 透明 JS 可控性 典型场景
CSS / WAAPI 浏览器插值 DOM 样式 矢量级 完全 UI 过渡、micro-interaction
动图 GIF / APNG / WebP / AVIF 逐帧位图文件 位图;GIF 仅 1-bit alpha 几乎没有 表情、贴纸、录屏
video 视频编码 + 硬件解码 位图;alpha 需特殊编码 currentTime / rate 完全可控 实拍、3D 渲染成片
Lottie JSON 矢量关键帧 + 渲染器 矢量 完全(变速 / 分段 / 换色) 插画动效、动态 icon
SMIL SVG 内置声明式动画 矢量 beginElement SVG 属性动画;img 里也能动
canvas / WebGL 每帧用代码重画像素 自己画,随意 完全(就是代码) 可视化、游戏、粒子

2 · 动图格式:GIF / APNG / WebP / AVIF

最古老的网页动画——「把每一帧塞进同一个图片文件」,浏览器当普通 <img>(或 background-image)循环播放,零代码。

格式 要点
GIF89a(1989) 每帧最多 256 色调色板,透明只有「全透 / 不透」1-bit(没有半透明,所以白底 GIF 放深色页面会有白边);delay 以 10ms 为单位,多数浏览器把过小的 delay 钳到 100ms;压缩是上古 LZW,体积大。
APNG PNG 的动画扩展:在普通 PNG 里插入 acTL / fcTL / fdAT 数据块。真彩 + 8-bit alpha;不认识动画块的旧解码器会当静态 PNG 显示第一帧,天然向后兼容。现已全浏览器支持。
动图 WebP VP8 / VP8L 帧 + alpha,有损 / 无损皆可,同画质体积通常比 GIF 小一大截,是「替换 GIF」的主力格式。
动图 AVIF 本质是 AV1 image sequence,压缩比最好;较新,上线前查兼容。

动图的共同天生短板:没有任何 JS 控制接口——不能 pause / seek、没有进度事件,「暂停」只能整个换 src 成静态帧。逐帧位图让时长 × 分辨率直接乘进体积,也没有音轨;几秒以上的内容应改用 video(见下文「video」一节)。

Demo:APNG 的「数据块」结构简单到可以在浏览器里现场拼一个——用 canvas 画 28 帧弹跳球,每帧 toBlob('image/png'),再把各帧的 IDAT 块拼成带 acTL / fcTL / fdAT 的 APNG,交给左边的 <img>。右边是同一段动画的 canvas 实时绘制版作对照。

生成后按 demo 里的「⏸ 暂停 canvas」:canvas 版停了,img 版照转——动图一旦发出去,播放节奏就完全交给解码器,页面无从插手(prefers-reduced-motion 也不会自动停掉它)。这就是动图「零代码」的代价。

3 · video:把动画当视频播

超过几秒的位图动画,正确答案几乎都是 <video>:H.264 / VP9 / AV1 是按「帧间差分」压缩的,体积远小于逐帧动图,且解码走硬件、不占主线程。换来的是一整套 JS 接口:currentTime 拖进度、playbackRate 变速、requestVideoFrameCallback 精确到帧的回调。

场景 做法
当 GIF 用:autoplay 三件套 <video autoplay muted loop playsinline>——自动播必须 muted,iOS 不全屏必须 playsinline
透明通道 主流 H.264 / MP4 没有 alpha;要透明视频得 VP9 / AV1 带 alpha 的 WebM(Chromium / Firefox)+ HEVC with alpha 的 MP4(Safari),用两个 <source> 各管各的。
滚动驱动(Apple 产品页那种) 监听滚动改 video.currentTime 即可,但 seek 流畅度受关键帧密度限制(全 I-frame 编码,或干脆换图片序列 + canvas)。

Demo 不引外部视频文件,而是现场录一个:canvas.captureStream() 把左边 canvas 的实时画面变成 MediaStream,MediaRecorder 录 3 秒编码成 WebM,喂给右边的 <video>。再用 JS 接口逐项控制它——这些正是动图不具备的能力。

底部状态行由 video.requestVideoFrameCallback() 驱动——每呈现一帧回调一次,带 mediaTime 等元数据,是做「逐帧字幕 / 视频上叠 canvas 特效」的正确接口(比拿 rAF 去猜帧准得多)。「+1 帧」= 暂停后 currentTime += 1/30:动图连「停在某帧」都做不到,video 可以精确定位到任意时刻。

4 · Lottie:用 JSON 描述的矢量动画

设计师在 After Effects(Bodymovin 插件)或 Lottielab 这类工具里做动画,导出一份 JSON——里面是图层、贝塞尔形状和关键帧,不含任何像素;前端用渲染器(lottie-web 渲到 SVG / canvas)按 60fps 现场插值画出来。矢量 → 任意分辨率不糊、体积常常只有几 KB;数据 → 运行时可变速、播某一段、甚至改颜色。打包格式 dotLottie(.lottie) 就是 zip 起来的 JSON + 资源。

「换个颜色」做的事:深拷贝动画 JSON、改掉 fill 的 c.k 颜色值、重新 loadAnimation()——动画是数据,所以能被程序改写;这是 GIF / video 这类已栅格化定型的像素做不到的。代价:渲染发生在主线程(SVG renderer 每帧改一堆 DOM attribute),复杂动画在低端机上比 video 费电;AE 的高级特效(粒子、模糊等)多数导不出来。同类竞品 Rive 走得更远:自带状态机和输入绑定,运行时是 .riv 二进制 + WASM 渲染器,适合可交互的角色 / 游戏 UI。

5 · SMIL:SVG 自带的声明式动画

SVG 1.1(2001)就内置了一套动画标签:<animate> / <animateTransform> / <animateMotion> / <set>,统称 SMIL——比 CSS animation 早了整整十年。Chrome 2015 年曾宣布要废弃它,后来撤回,如今依然全浏览器可用。它至今仍有两个独特能力:能动 CSS 够不到的 SVG attribute(下面的 path 形变、gradient stop 等),以及<img> / background-image 引用的 SVG 里照样播放(那里 JS 是被禁用的)。

橙色方块的旋转是 begin="click" 声明出来的,整页没有一行 JS 监听它——事件触发、延时(begin="click+0.3s")、链式(begin="other.end")都能纯声明表达。JS 想插手也有口子:animateElement.beginElement() / endElement()。左边的形变是 <animate attributeName="d">:两段 path 命令结构相同时逐点插值——CSS 近年才追上这能力,而 gradient stop、textPathstartOffset 这类 attribute 至今仍是 SMIL / JS 的领地。

6 · canvas / WebGL:每帧自己画

前面所有方案都是「描述动画,浏览器替你播」;canvas 反过来——requestAnimationFrame 循环里每帧把整个画面重画一遍,想怎么动全凭代码。数据可视化、游戏、粒子系统、音频频谱这类「元素数量大 / 形态由数据驱动」的动画,DOM 和 CSS 根本表达不了,canvas 是唯一解。

这一百多个粒子若用 DOM 元素 + CSS 动画实现,数百个节点的布局与绘制开销会立刻成为瓶颈;canvas 里只是一次循环里的几百次 arc()。再往上走:WebGL(shader 在 GPU 上算,three.js / PixiJS 封装)、WebGPU(新一代,compute shader)、OffscreenCanvas 把绘制整个挪进 Worker 脱离主线程。代价也直白:无障碍、文本选择、事件命中全没了,得自己造;并且它跑在主线程(见性能与取舍那页的线程故事)。

7 · JS 动画库,以及没赶上正文的杂项

库 / 技术 要点
GSAP / anime.js rAF 时间轴引擎:编排(timeline)、SVG morph、文字逐字,生态最厚;注意主要跑主线程。
Motion(原 Framer Motion) 底层尽量走 WAAPI,让动画能下合成线程;React 系交互动画首选之一。
spring 物理(react-spring 等) 没有 duration,每帧按弹簧方程算下一步——所以能被手势随时打断并自然接管,这是时间轴模型最难做的。
Web Animations API 浏览器原生 JS 动画接口,本系列动画事件页已专门讲过——它是上面这些库的「正确地基」。
历史遗留:<marquee> / <blink> HTML 标签本身就会动的年代。<blink> 已被移除,<marquee> 至今仍被浏览器支持,但不应再使用。

怎么选(经验法则): UI 过渡 / 图标微动 → CSS / WAAPI;设计师产的插画级动效 → Lottie(要状态机交互则 Rive);实拍或离线渲染好的内容 → video(短小表情才用动图,且优先 WebP / APNG 而非 GIF);SVG 内部属性、或要塞进 <img> 的单文件动画 → SMIL;数据驱动 / 海量元素 / 游戏 → canvas / WebGL;手势驱动、可打断的交互动画 → JS 库(WAAPI / spring)

8 · 延伸阅读