L
← 返回技术文章
约 8 分钟

我一直以为 SVG 角色动画高不可攀,直到我尝试去做

从拆解独角兽 IP 开始,记录如何把复杂角色降维成少量 SVG 图层,再用 GSAP 完成眨眼、摆尾与跳跃动画。

SVGGSAP角色动画设计工程

过去我一直觉得,纯 SVG 的角色动画是一件非niche不可为的事情。

尤其是看到网页里那些会眨眼、走路、挥手、跳跃的小角色时,我很容易脑补出一整套复杂工作流:

要非常熟 SVG,要会画复杂 Path,要懂动画,还要花很多时间一点一点调。

所以虽然我一直很喜欢这种效果,但心理上会自然把它归类为:

“很好看,但成本应该挺高,以后再研究。”

最近看到一篇文章,作者用 SVG 和 GSAP 逆向重做了 Claude 的吉祥物动画。

看完以后我最大的感受倒不是“GSAP 真厉害”。

而是:

原来第一步可以这么简单。

于是我拿独角兽的 IP 试了一遍(并把它贡献给Squady😃

结果发现,真正开始动手以后,SVG 角色动画没有我以前想象得那么吓人。

想要做成svg动画,与其想着“怎么把这张图做成动画”,不如先问:它能被拆成什么?

这是这次尝试里,我觉得最值得分享的一点。

一开始面对的是一张完整的角色图。

正常人的直觉很容易是:

我要怎么让“这张图”动起来?

但如果沿着这个思路走,很快就会变得复杂。

更好的问题其实是:

这个角色有哪些东西值得单独动?

比如我的小马。

虽然视觉上是一整个角色,但仔细看,真正可能产生动作的地方没有几个:

小马 ├── 身体 ├── 左耳 ├── 右耳 ├── 眼睛 ├── 独角 └── 尾巴

到这里,问题已经发生变化了。

原来是:

我要做一个角色动画。

现在变成:

我要让 6 个图形按照一定顺序移动、缩放和旋转。

一下就没那么可怕了。

SVG 很适合干这件事情,因为它本来就是“图形的集合”

普通 PNG 对程序来说基本是一整张图片。

SVG 不一样。

它本质上可以由很多图形组成。

比如眼睛可以只是两个矩形:

<g id="eyes">
  <rect x="245" y="257" width="39" height="52" />
  <rect x="373" y="257" width="39" height="52" />
</g>

尾巴可以是一条独立 Path:

<g id="tail">
  <path d="..." />
</g>

身体又是一条:

<g id="body">
  <path d="..." />
</g>

这里的 < g >,可以简单理解成一个“组”或者“图层”。

于是整个角色就变成:

<svg>
  <g id="tail">...</g>
  <g id="left-ear">...</g>
  <g id="right-ear">...</g>
  <g id="body">...</g>
  <g id="eyes">...</g>
  <g id="horn">...</g>
</svg>

这其实已经是一套非常简单的二维角色结构了。

没有骨骼。

没有 rig。

没有复杂的动画软件。

只是:

哪些东西以后要动,就把哪些东西单独留出来。

甚至不需要非常会“写 SVG”

这一点也和我之前想象得不太一样。

以前看到:

<path d="M153 172 L481 172 L481 225 ..." />

这种东西,我会本能觉得 SVG 有很高的手写门槛。

实际上完全没必要自己一个坐标一个坐标敲。

更正常的流程是:

Figma / Illustrator ↓ 画好角色 ↓ 导出 SVG ↓ 整理需要活动的部件 ↓ 给它们加 id

也就是说,如果本身已经有一个比较简洁的 IP 形象,甚至可以直接从现有设计开始。

真正需要做的不是“重新学习怎么画 SVG”。

而是做一次动画视角的简化。

例如:

原设计里头和身体可以是同一块。

如果你不需要摇头,就完全没必要为了“专业”硬拆。

眼睛需要眨,就拆出来。

尾巴需要摇,就拆出来。

耳朵需要动,就拆出来。

只拆你需要控制的部分。

这件事情很重要,因为拆得越细并不代表越高级。这个有点类似骨骼/图钉动画青春版,但是这里基本上只要关注“图钉”,如果你尝试过在ae或者其他动画工具中制作角色的mg动画就能轻易明白我想表达的意思。

拆成几十个零件,只会让未来的自己拥有几十个新的麻烦。

然后才轮到“驱动”

一旦 SVG 被整理成这种结构,代码其实突然变得非常普通。

比如:

让耳朵动一下:

gsap.to("#left-ear", { rotation: -10, duration: 0.2 })

让尾巴摆:

gsap.to("#tail", { rotation: 15, duration: 0.3 })

让角色整体往上:

gsap.to("#character", { y: -80, duration: 0.3 })

眨眼甚至只是:

gsap.to("#eyes", { scaleY: 0.1, duration: 0.08 })

再恢复:

gsap.to("#eyes", { scaleY: 1, duration: 0.1 })

也就是说,从程序实现来看,并没有突然出现什么特别诡异的新技术。

仍然是我们非常熟悉的:

translate scale rotate opacity

只不过对象从 HTML 元素变成了角色身体的一部分。

一个完整 IP,其实可以先被降维成几个“可动画元素”

这是我这次最喜欢的一种理解方式。

设计师看到的是:

一只小马。

动画实现的时候,可以暂时把它忘掉。

把它看成:

一个大白色形状 + 三个蓝色形状 + 两个黑色矩形 + 几个可以旋转的 group

听起来非常没有感情。

但反而容易做。

因为一旦先把复杂的“角色”降维成几个基础元素,技术问题就开始变得具体:

  • 这个东西需要位移吗?
  • 这个东西需要旋转吗?
  • 它的旋转中心在哪里?
  • 它需要和哪个东西一起动?
  • 它需不需要单独成一个 group?

回答完这些问题,一个原本看起来很完整、很复杂的 IP,已经变成了一棵非常普通的 SVG DOM。

这其实和写 UI 很像。

看到一个完整页面时觉得复杂。

真正实现的时候,最后还是:

Container ├── Header ├── Content └── Footer

只不过这次 Component 变成了耳朵和尾巴。

软件工程师毕生的超能力可能就是把任何东西拆成树。

我后来甚至把它做成了 Loading

做到这里以后,我发现自己一开始对工时的判断也明显高估了。

比如这样一个简单角色,如果只是做:

  • 眨眼
    • 耳朵动
    • 尾巴摆
    • 小幅跳跃

基础版本其实很快就能出来。

于是我继续把小马做成了一个 loading:

蹲一下 ↓ 跳起来 ↓ 落下来 ↓ 回到原位 ↓ 重复

动画本身当然还有很多可以打磨的地方。

比如动作要不要更有重量、落地要不要压一下、尾巴应该什么时候跟上。

这些属于动画设计。

但至少到了这一步,我已经不再担心:

“这东西到底能不能实现?”

而是在想:

“怎么让它更好看?”

这两个阶段带来的心理压力完全不同。

这次最大的收获其实是重新理解“技术门槛”

如果只看最终结果,一个会活动的 SVG IP 很容易给人一种:

制作成本很高。

的感觉。

我以前也是这样。

但亲手尝试以后发现,这里面有一个很容易被忽略的问题:

我们会把不了解的实现过程,自动脑补成复杂的实现过程。

看到角色动画:

不知道怎么实现 ↓ 肯定需要很多专业知识 ↓ 肯定需要很多工时 ↓ 以后再做

但实际拆一次:

先简化 IP ↓ 找到可活动部位 ↓ 拆成几个 SVG group ↓ 给 group 命名 ↓ 用代码移动 / 旋转 / 缩放

至少一个基础版本,很快就出现了。

当然,再复杂一点的角色、Path Morph、逐帧动画、遮罩等,成本照样会迅速提高。

但问题在于:

不是每一个效果都需要从最复杂的方案开始。

有时候你的 IP 本来就只需要 5 个可以活动的部件。

那就先做 5 个。

所以,现在我会更愿意“先试一下”

这是这次实践给我留下最有用的东西。

以后再看到一种:

“感觉应该挺难的。”

的视觉效果,我会更愿意先做一个最小实验。

不是先看十篇教程。

也不是先决定是不是要系统学习 SVG 动画。

而是:

拿一个现成 IP ↓ 简化 ↓ 拆 3~6 个部位 ↓ 让其中一个先动起来

耳朵能动,就已经成功了一步。

眼睛能眨,再加一步。

等几个东西真的在屏幕上动起来以后,再决定值不值得继续投入。

这种方式可能不够“系统”。

但至少比站在门外研究这扇门到底有多难推要有效得多。

有些技术真正的入门门槛并不是知识。

只是第一次动手之前,人会习惯性地把它想得比实际更难。

而这个门槛,最好的解决方案通常不是再看一篇“从零开始”。

是先把自己的 IP 拆成几块。

然后让其中一块动起来。