Smashing Animations Part 4: Optimising SVGs
开始清洁设计并考虑优化
保持简单是创建优化且准备动画的SVG的关键。像Adobe Illustrator这样的工具会将位图图像转换为矢量,但输出通常包含太多无关的组、层和蒙版。相反,我在Sketch中开始清理,使用参考图像,并使用钢笔工具创建路径。
提示:Affinity Designer(英国)和Sketch(荷兰)是Adobe Illustrator和Figma的替代品。两者都是独立的,总部位于欧洲。自从Adobe停止Fireworks后,Sketch一直是我的默认设计应用。
从轮廓开始
对于这些Toon Titles插图,我首先使用钢笔工具绘制黑色轮廓,尽可能少地使用锚点。形状的点越多,文件就越大,因此简化路径和减少点数可以使SVG小得多,通常没有明显的视觉差异。
考虑到Yogi插图的某些部分最终会被动画化,我将这个Bewitched Bear的身体、头部、衣领和领带的轮廓分开,以便可以独立移动它们。头部可能会点头,领带可能会飘动,就像那些经典卡通一样,Yogi的衣领会隐藏它们之间的连接。
绘制简单的背景形状
轮廓就位后,我再次使用钢笔工具绘制新形状,用颜色填充区域。这些颜色位于轮廓后面,因此不需要完全匹配。锚点越少,文件大小越小。
遗憾的是,Affinity Designer和Sketch都没有可以简化路径的工具,但如果你有Adobe Illustrator,可以使用它来减少这些背景形状的几千字节。
优化代码
不仅仅是元数据会使SVG变得臃肿。从设计应用导出的方式也会影响文件大小。
默认情况下,从Adobe Illustrator导出这些简单的背景形状包括不必要的组、蒙版和臃肿的路径数据。Sketch的代码也好不了多少,即使在其SVGO Compressor代码中也有很大的改进空间。我依赖Jake Archibald的SVGOMG,它使用SVGO v3,并始终提供最佳优化的SVG。
分层SVG元素
我为动画准备SVG的过程远远超出了绘制矢量和优化路径的范围——还包括我如何构建代码本身。当每个视觉元素都塞进一个SVG文件中时,即使是优化的代码也可能难以导航。定位特定的路径或组通常感觉像大海捞针。
这就是为什么我分层开发SVG,一次导出一组元素并优化——总是按照它们在最终文件中出现的顺序。这让我可以通过粘贴每个清理过的部分来逐步构建主SVG。例如,我从这样的渐变和标题图形背景开始。
现在,我可以轻松识别背景渐变的路径及其相关的linearGradient,并查看包含标题图形的组,而不是面对一堵SVG代码墙。我借此机会向代码添加注释,这将使将来编辑和添加动画更容易:
1
2
3
4
5
6
7
8
9
10
11
|
<svg ...>
<defs>
<!-- ... -->
</defs>
<path fill="url(#grad)" d="…"/>
<!-- TITLE GRAPHIC -->
<g>
<path … />
<!-- ... -->
</g>
</svg>
|
接下来,我添加Yogi空中扫帚的模糊轨迹。这包括定义高斯模糊滤镜并将其路径放置在背景和标题层之间:
1
2
3
4
5
6
7
8
9
10
|
<svg ...>
<defs>
<linearGradient id="grad" …>…</linearGradient>
<filter id="trail" …>…</filter>
</defs>
<!-- GRADIENT -->
<!-- TRAIL -->
<path filter="url(#trail)" …/>
<!-- TITLE GRAPHIC -->
</svg>
|
然后是魔法星星,以相同的顺序方式添加:
1
2
3
4
5
6
|
<svg ...>
<!-- GRADIENT -->
<!-- TRAIL -->
<!-- STARS -->
<!-- TITLE GRAPHIC -->
</svg>
|
为了保持一切有序并准备动画,我创建了一个空组来容纳Yogi的所有部分:
然后我从地面开始构建Yogi——从背景道具开始,比如他的扫帚:
接着是他的身体、头部、衣领和领带的分组元素:
1
2
3
4
5
6
7
|
<g id="yogi">
<g id="broom">…</g>
<g id="body">…</g>
<g id="head">…</g>
<g id="collar">…</g>
<g id="tie">…</g>
</g>
|
由于我从相同大小的画板导出每个层,我不需要担心以后的对齐或定位问题——它们都会自动就位。通过这种方式分层元素,我保持代码清洁、可读且逻辑有序。这也使动画更流畅,因为每个组件更容易识别。
使用
当重复形状被重复使用时,SVG文件会迅速变得臃肿。我对“Bewitched Bear”标题卡的重现包含80颗三种大小的星星。将所有形状合并为一个优化路径会将文件大小减少到3KB。但我想动画单个星星,这几乎会将其增加到5KB:
1
2
3
4
5
6
|
<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>
|
将星星的填充属性值移动到它们的父组会稍微减少总重量:
1
2
3
4
5
6
|
<g id="stars" fill="#eae3da">
<path class="star-small" d="…"/>
<path class="star-medium" d="…"/>
<path class="star-large" d="…"/>
<!-- ... -->
</g>
|
但更高效和可管理的选项是将每个星星大小定义为可重用模板:
1
2
3
4
5
|
<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>
|
通过这种设置,更改星星的设计只需要更新其模板一次,每个实例都会自动更新。然后,我使用
1
2
3
4
5
6
7
8
9
10
11
|
<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更易于管理、加载更轻、迭代更快,尤其是在处理数十个重复元素时。最重要的是,它保持了标记的清洁,而不影响灵活性或性能。
添加动画
Yogi偷来的扫帚后面的星星为动画带来了很多个性。我希望它们在深蓝色背景上以看似随机的模式闪烁,所以我首先定义了一个关键帧动画,循环不同的不透明度级别:
1
2
3
4
|
@keyframes sparkle {
0%, 100% { opacity: .1; }
50% { opacity: 1; }
}
|
接下来,我将这个循环动画应用到我星星组中的每个use元素:
1
2
3
|
#stars use {
animation: sparkle 10s ease-in-out infinite;
}
|
创建令人信服的闪烁效果的秘诀在于变化。我使用nth-child选择器在星星之间错开动画延迟和持续时间,从最快和最频繁的闪烁效果开始:
1
2
3
4
5
|
/* Fast, frequent */
#stars use:nth-child(n + 1):nth-child(-n + 10) {
animation-delay: .1s;
animation-duration: 2s;
}
|
从那里,我分层添加了额外的计时来混合事物。一些星星缓慢而戏剧性地闪烁,其他更随机,有各种节奏和暂停:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/* 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和重用元素,我可以构建看起来复杂的动画,而没有臃肿的代码,使即使像改变不透明度这样的简单效果也能闪烁。
然后,为了增加真实感,我让Yogi的头部摇晃:
1
2
3
4
5
6
7
8
|
@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;
}
|
他的领带飘动:
1
2
3
4
5
6
7
8
9
10
|
@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;
}
|
他的扫帚摆动:
1
2
3
4
5
6
7
8
9
10
11
|
@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;
}
|
最后,Yogi自己在魔法扫帚上飞行时轻轻旋转:
1
2
3
4
5
6
7
8
9
|
@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;
}
|
所有这些细微的动作使Yogi栩栩如生。通过开发结构化的SVG,我可以创建充满个性的动画,而无需编写一行JavaScript。
自己尝试:
参见CodePen上的Bewitched Bear CSS/SVG animation [forked] by Andy Clarke。
结论
无论你是重现经典标题卡还是为界面动画图标,原则都是相同的:
SVG提供了令人难以置信的创作自由,但只有在保持精简和可管理的情况下。当你像生产单元一样规划过程——逐层、逐元素——你将花更少的时间解开代码,更多的时间使你的作品生动起来。