SVG动画优化全攻略:从设计到性能提升

Smashing Animations Part 4: Optimising SVGs

SVG动画让我回想起童年观看的汉纳-巴伯拉卡通片,如《疯狂大赛车》《佩内洛普·皮斯托普的冒险》和《瑜伽熊》。这些动画启发我使用CSS、SVG和SMIL动画精心重现了一些经典卡通标题。

但让动画快速加载并流畅运行需要的不仅仅是怀旧情怀。它需要简洁的设计、精简的代码以及使复杂SVG更易于动画化的流程。以下是我的方法。

现在有一个网站可以查看我所有的卡通标题。(大型预览)

无论是个人项目还是商业工作,良好的SVG准备能确保其可访问性。优化能确保快速加载(尤其在移动设备上),而仔细考虑结构则使维护更轻松。我开发了一个平衡视觉效果、可访问性和性能的流程,使复杂SVG更易处理。

为解释我的流程,我选择了1960年1月首播的《瑜伽熊秀》剧集“Bewitched Bear”。在这个故事中,瑜伽熊偷了一把女巫的扫帚来帮助他抓“野餐篮”。

“嘿,嘿,嘿!”

《瑜伽熊秀》©️ 华纳兄弟娱乐公司。(大型预览)

从简洁开始并设计时考虑优化

保持简洁是制作优化并准备动画的SVG的关键。像Adobe Illustrator这样的工具将位图转换为矢量,但输出通常包含过多无关的组、图层和蒙版。相反,我在Sketch中开始清理,使用参考图像,并用钢笔工具创建路径。

提示:Affinity Designer(英国)和Sketch(荷兰)是Adobe Illustrator和Figma的替代品。两者都是独立的欧洲公司。自Adobe停止Fireworks后,Sketch一直是我的默认设计应用。

从轮廓开始

对于这些卡通标题插图,我首先使用钢笔工具绘制黑色轮廓,尽可能减少锚点。形状的点越多,文件越大,因此简化路径和减少点数可以显著减小SVG大小,通常视觉上没有明显差异。

左:160个锚点。右:80个点。(大型预览)

考虑到瑜伽熊插图的某些部分最终会动画化,我将这个“Bewitched Bear”的身体、头部、衣领和领带的轮廓分开,以便独立移动。头部可能点头,领带可能飘动,就像那些经典卡通一样,瑜伽熊的衣领将隐藏它们之间的连接。

身体、头部、衣领和领带以及扫帚的单独轮廓。(大型预览)

绘制简单的背景形状

轮廓就位后,我再次使用钢笔工具绘制新形状,用颜色填充区域。这些颜色位于轮廓后面,因此不需要完全匹配。锚点越少,文件大小越小。

左:原始矢量 artwork,8 KB。右:使用Adobe Illustrator简化后,2 KB。(大型预览)

遗憾的是,Affinity Designer和Sketch都没有简化路径的工具,但如果你有Adobe Illustrator,可以进一步减少这些背景形状的大小。

Adobe Illustrator:对象 → 路径 → 简化。(大型预览)

优化代码

不仅仅是元数据会使SVG臃肿。从设计应用导出的方式也会影响文件大小。

准备优化的矢量 artwork。(大型预览)

从Adobe Illustrator导出这些简单的背景形状默认包括不必要的组、蒙版和臃肿的路径数据。Sketch的代码也好不了多少,即使在其SVGO Compressor代码中也有很大的改进空间。我依赖Jake Archibald的SVGOMG,它使用SVGO v3,始终提供最佳优化的SVG。

Jake Archibald的SVGOMG在线优化工具。(大型预览)

分层SVG元素

我为动画准备SVG的流程远不止绘制矢量和优化路径——还包括如何结构代码本身。当每个视觉元素都塞进一个SVG文件时,即使优化后的代码也难以导航。定位特定路径或组通常感觉像大海捞针。

瑜伽熊标题卡设计 by Lawrence Goble (1958)。卡通标题重现。(大型预览)

这就是为什么我分层开发SVG,一次导出和优化一组元素——始终按照它们在最终文件中出现的顺序。这让我通过粘贴每个清理过的部分逐步构建主SVG。例如,我从背景如渐变和标题图形开始。

渐变背景和标题图形。(大型预览)

现在,我可以轻松识别背景渐变的路径及其相关的linearGradient,并查看包含标题图形的组,而不是面对一堵SVG代码墙。我借此机会向代码添加注释,这将使将来编辑和添加动画更容易:

<svg ...>
  <defs>
    <!-- ... -->
  </defs>
  <path fill="url(#grad)" d="…"/>
  <!-- TITLE GRAPHIC -->
  <g>
    <path … />
    <!-- ... --> 
  </g>
</svg>

复制

高斯模糊的轨迹。(大型预览)

接下来,我添加瑜伽熊飞行扫帚的模糊轨迹。这包括定义高斯模糊过滤器并将其路径放置在背景和标题层之间:

<svg ...>
  <defs>
    <linearGradient id="grad" …>…</linearGradient>
    <filter id="trail" …>…</filter>
  </defs>
  <!-- GRADIENT -->
  <!-- TRAIL -->
  <path filter="url(#trail)" …/>
  <!-- TITLE GRAPHIC -->
</svg>

复制

瑜伽熊的魔法星星。(大型预览)

然后是魔法星星,以相同的顺序方式添加:

<svg ...>
  <!-- GRADIENT -->
  <!-- TRAIL -->
  <!-- STARS -->
  <!-- TITLE GRAPHIC -->
</svg>

复制

为保持一切有序并准备动画,我创建一个空组来容纳瑜伽熊的所有部分:

<g id="yogi">...</g>

复制

顺序添加瑜伽熊的组件部分。(大型预览)

然后我从地面开始构建瑜伽熊——从背景道具如他的扫帚开始:

<g id="broom">...</g>

复制

followed by grouped elements for his body, head, collar, and tie:

<g id="yogi">
  <g id="broom">…</g>
  <g id="body">…</g>
  <g id="head">…</g>
  <g id="collar">…</g>
  <g id="tie">…</g>
</g>

复制

瑜伽熊标题卡设计 by Lawrence Goble (1958)。卡通标题重现。(大型预览)

由于我从相同大小的画板导出每个层,我不需要担心对齐或定位问题 later on——它们都会自动就位。通过这种方式分层元素,我保持代码清洁、可读和逻辑有序。这也使动画更流畅,因为每个组件更容易识别。

使用重用元素

当重复形状被反复重用时,SVG文件会迅速臃肿。我的“Bewitched Bear”标题卡重现包含80颗星星,有三种大小。将所有形状合并为一个优化路径会将文件大小减少到3KB。但我想动画单个星星,这几乎会加倍到5KB:

<g id="stars">
 <path class="star-small" fill="#eae3da" d="..."/>
 <path class="star-medium" fill="#eae3da" d="..."/>
 <path class="star-large" fill="#eae3da" d="..."/>
 <!-- ... -->
</g>

复制

将星星的填充属性值移动到父组稍微减少总重量:

<g id="stars" fill="#eae3da">
 <path class="star-small" d="…"/>
 <path class="star-medium" d="…"/>
 <path class="star-large" d="…"/>
 <!-- ... -->
</g>

复制

瑜伽熊的闪烁星星。(大型预览)

但更高效和可管理的选项是将每个星星大小定义为可重用模板:

<defs>
  <path id="star-large" fill="#eae3da" fill-rule="evenodd" d="…"/>
  <path id="star-medium" fill="#eae3da" fill-rule="evenodd" d="…"/>
  <path id="star-small" fill="#eae3da" fill-rule="evenodd" d="…"/>
</defs>

复制

通过这种设置,更改星星设计只需更新其模板一次,每个实例都会自动更新。然后,我使用引用每个星星,并用x和y属性定位它们:

<g id="stars">
  <!-- Large stars -->
  <use href="#star-large" x="1575" y="495"/>
  <!-- ... -->
  <!-- Medium stars -->
  <use href="#star-medium" x="1453" y="696"/>
  <!-- ... -->
  <!-- Small stars -->
  <use href="#star-small" x="1287" y="741"/>
  <!-- ... -->
</g>

复制

这种方法使SVG更易于管理、加载更轻、迭代更快,尤其是在处理数十个重复元素时。最重要的是,它保持标记清洁而不牺牲灵活性或性能。

添加动画

瑜伽熊偷来的扫帚后面的星星为动画带来了很多个性。我希望它们在深蓝色背景上以看似随机的模式闪烁,所以我首先定义了一个关键帧动画,循环不同的不透明度级别:

@keyframes sparkle {
  0%, 100% { opacity: .1; }
  50% { opacity: 1; }
}

复制

接下来,我将这个循环动画应用到星星组中的每个use元素:

#stars use {
  animation: sparkle 10s ease-in-out infinite;
}

复制

创建逼真闪烁的秘密在于变化。我使用nth-child选择器在星星之间错开动画延迟和持续时间,从最快和最频繁的闪烁效果开始:

/* Fast, frequent */
#stars use:nth-child(n + 1):nth-child(-n + 10) {
  animation-delay: .1s;
  animation-duration: 2s;
}

复制

从那里,我分层添加额外的时间来混合事物。一些星星缓慢而戏剧性地闪烁,其他更随机,有各种节奏和暂停:

/* Medium */
#stars use:nth-child(n + 11):nth-child(-n + 20) { ... }

/* Slow, dramatic */
#stars use:nth-child(n + 21):nth-child(-n + 30) { ... }

/* Random */
#stars use:nth-child(3n + 2) { ... }

/* Alternating */
#stars use:nth-child(4n + 1) { ... }

/* Scattered */
#stars use:nth-child(n + 31) { ... }

复制

通过深思熟虑地结构SVG和重用元素,我可以构建看起来复杂而不臃肿的动画,即使像改变不透明度这样的简单效果也能闪烁。

细微的动作赋予瑜伽熊生命。(大型预览)

然后,为了增加真实感,我让瑜伽熊的头摆动:

@keyframes headWobble {
  0% { transform: rotate(-0.8deg) translateY(-0.5px); }
  100% { transform: rotate(0.9deg) translateY(0.3px); }
}

#head {
  animation: headWobble 0.8s cubic-bezier(0.5, 0.15, 0.5, 0.85) infinite alternate;
}

复制

他的领带飘动:

@keyframes tieWave {
  0%, 100% { transform: rotateZ(-4deg) rotateY(15deg) scaleX(0.96); }
  33% { transform: rotateZ(5deg) rotateY(-10deg) scaleX(1.05); }
  66% { transform: rotateZ(-2deg) rotateY(5deg) scaleX(0.98); }
}

#tie {
  transform-style: preserve-3d;
  animation: tieWave 10s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite;
}

复制

他的扫帚摆动:

@keyframes broomSwing {
  0%, 20% { transform: rotate(-5deg); }
  30% { transform: rotate(-4deg); }
  50%, 70% { transform: rotate(5deg); }
  80% { transform: rotate(4deg); }
  100% { transform: rotate(-5deg); }
}

#broom {
  animation: broomSwing 4s cubic-bezier(0.5, 0.05, 0.5, 0.95) infinite;
}

复制

最后,瑜伽熊本人在他的魔法扫帚上飞行时轻轻旋转:

@keyframes yogiWobble {
  0% { transform: rotate(-2.8deg) translateY(-0.8px) scale(0.998); }
  30% { transform: rotate(1.5deg) translateY(0.3px); }
  100% { transform: rotate(3.2deg) translateY(1.2px) scale(1.002); }
}

#yogi {
  animation: yogiWobble 3.5s cubic-bezier(.37, .14, .3, .86) infinite alternate;
}

复制

所有这些细微的动作赋予瑜伽熊生命。通过开发结构化的SVG,我可以创建充满个性的动画,而无需编写一行JavaScript。

自己尝试:见Pen Bewitched Bear CSS/SVG animation [forked] by Andy Clarke。

结论

无论你是重现经典标题卡还是为界面动画图标,原则都是相同的:

  • 从简洁开始,
  • 早期优化,以及
  • 一切结构时考虑动画。

SVG提供令人难以置信的创作自由,但只有在保持精简和可管理的情况下。当你像生产单元一样计划你的流程——层层叠叠,元素 by 元素——你将花更少的时间解缠代码,更多的时间赋予你的作品生命。
(gg, yk)
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
公众号二维码

posted @ 2025-09-15 16:18  qife  阅读(11)  评论(0)    收藏  举报