利用SVG的
SVG是那些既优雅又有时令人恼火的Web技术之一。在这篇文章中,先驱作者和网页设计师Andy Clarke解释了他对隐藏在Shadow DOM中的SVG元素进行动画处理的技术。
理解Shadow DOM障碍
当你使用<use>引用symbol内容时,浏览器会在Shadow DOM中创建其副本。每个<use>实例都成为被引用<symbol>的封装副本,这意味着来自外部的CSS无法突破障碍直接样式化任何元素。
例如,正常情况下,这个轻敲值会触发CSS动画:
1
2
3
|
<g class="outlaw-1-foot tapping">
<!-- ... -->
</g>
|
1
2
3
|
.tapping {
animation: tapping 1s ease-in-out infinite;
}
|
但当相同的动画应用于同一只脚的<use>实例时,什么也不会发生:
1
2
3
4
5
|
<symbol id="outlaw-1">
<g class="outlaw-1-foot"><!-- ... --></g>
</symbol>
<use href="#outlaw-1" class="tapping" />
|
这是因为<symbol>元素内的<g>位于受保护的影子树中,CSS级联在<use>边界处完全停止。
CSS自定义属性来救援
虽然常规CSS值无法跨越边界进入Shadow DOM,但CSS自定义属性可以。尽管你不能直接样式化<symbol>内的元素,但可以向它们传递自定义属性值。
我在应用于<symbol>内容的内联样式中添加了rotate:
1
2
3
4
5
6
7
8
|
<symbol id="outlaw-1">
<g class="outlaw-1-foot" style="
transform-origin: bottom right;
transform-box: fill-box;
transform: rotate(var(--foot-rotate));">
<!-- ... -->
</g>
</symbol>
|
然后,定义脚部轻敲动画并将其应用于元素:
1
2
3
4
5
6
7
8
9
10
|
@keyframes tapping {
0%, 60%, 100% { --foot-rotate: 0deg; }
20% { --foot-rotate: -5deg; }
40% { --foot-rotate: 2deg; }
}
use[data-outlaw="1"] {
--foot-rotate: 0deg;
animation: tapping 1s ease-in-out infinite;
}
|
向Symbol传递多个值
一旦设置了symbol使用CSS自定义属性,我就可以向任何<use>实例传递任意数量的值。例如,我可以为fill、opacity或transform定义变量。
1
2
3
4
5
6
7
|
<g class="eyelids" style="
fill: var(--eyelids-colour, #f7bea1);
opacity: var(--eyelids-opacity, 1);
transform: var(--eyelids-scale, 0);"
>
<!-- etc. -->
</g>
|
1
2
3
4
5
6
7
8
9
|
use[data-outlaw="1"] {
--eyelids-colour: #f7bea1;
--eyelids-opacity: 1;
}
use[data-outlaw="2"] {
--eyelids-colour: #ba7e5e;
--eyelids-opacity: 0;
}
|
多色图标系统
当我需要维护一组图标时,我可以在<symbol>中定义一次图标,然后使用自定义属性应用颜色和效果。
例如,我为这个Bluesky图标中的<path>的默认填充颜色应用了--icon-fill自定义属性:
1
2
3
|
<symbol id="icon-bluesky">
<path fill="var(--icon-fill, currentColor)" d="..." />
</symbol>
|
然后,每当我需要改变该图标的外观时,我可以向每个实例传递新的填充颜色值:
1
2
3
4
5
6
7
8
9
10
11
|
<header>
<svg xmlns="http://www.w3.org/2000/svg">
<use href="#icon-bluesky" style="--icon-fill: #2d373b;" />
</svg>
</header>
<footer>
<svg xmlns="http://www.w3.org/2000/svg">
<use href="#icon-bluesky" style="--icon-fill: #590d1a;" />
</svg>
</footer>
|
使用CSS自定义属性的数据可视化
我们可以在更多实际方式中使用<symbol>和<use>。它们也有助于创建轻量级的数据可视化。
我首先为这些项目创建了symbols:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="career-bar">
<rect
height="10"
width="var(--career-length, 100)"
fill="var(--career-colour, #f7bea1)"
/>
</symbol>
<symbol id="arrests-badge">
<path
fill="var(--arrest-color, #d0985f)"
transform="scale(var(--arrest-scale, 1))"
/>
</symbol>
<symbol id="kills-icon">
<path fill="var(--kill-colour, #769099)" />
</symbol>
</svg>
|
每个symbol接受一个或多个值:
--career-length调整职业条的长度
--career-colour改变该条的填充颜色
--arrest-scale控制逮捕徽章的大小
--kill-colour定义击杀图标的填充颜色
环境动画
为了减少复杂性并使代码更轻量、更易维护,我需要定义每个角色一次并在多个SVG中重复使用它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!-- Symbols library -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="outlaw-1">[…]</symbol>
<!-- ... -->
</svg>
<!-- Large screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-large">
<use href="outlaw-1" />
<!-- ... -->
</svg>
<!-- Small screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-small">
<use href="outlaw-1" />
<!-- ... -->
</svg>
|
眨眼效果
我通过将SVG组放在不法之徒的眼睛上,然后改变其不透明度来实现眨眼效果。
为此,我向组添加了带有CSS自定义属性的内联样式:
1
2
3
4
5
|
<symbol id="outlaw-1" viewBox="0 0 712 2552">
<g class="eyelids" style="opacity: var(--eyelids-opacity, 1);">
<!-- ... -->
</g>
</symbol>
|
然后,通过改变--eyelids-opacity来定义眨眼动画:
1
2
3
4
5
6
|
@keyframes blink {
0%, 92% { --eyelids-opacity: 0; }
93%, 94% { --eyelids-opacity: 1; }
95%, 97% { --eyelids-opacity: 0.1; }
98%, 100% { --eyelids-opacity: 0; }
}
|
脚部轻敲效果
一些角色会轻敲他们的脚,所以我也向那些组添加了带有CSS自定义属性的内联样式:
1
2
3
4
5
6
7
|
<symbol id="outlaw-1" viewBox="0 0 712 2552">
<g class="outlaw-1-foot" style="
transform-origin: bottom right;
transform-box: fill-box;
transform: rotate(var(--foot-rotate));">
</g>
</symbol>
|
定义脚部轻敲动画:
1
2
3
4
5
|
@keyframes tapping {
0%, 60%, 100% { --foot-rotate: 0deg; }
20% { --foot-rotate: -5deg; }
40% { --foot-rotate: 2deg; }
}
|
抖动效果
最后通过描述他的胡须如何变换的CSS自定义属性的内联样式使角色的胡须抖动:
1
2
3
4
5
6
7
|
<symbol id="outlaw-1" viewBox="0 0 712 2552">
<g class="outlaw-1-tashe" style="
transform: translateX(var(--jiggle-x, 0px));"
>
<!-- ... -->
</g>
</symbol>
|
定义抖动动画:
1
2
3
4
5
6
7
|
@keyframes jiggle {
0%, 100% { --jiggle-x: 0px; }
20% { --jiggle-x: -3px; }
40% { --jiggle-x: 2px; }
60% { --jiggle-x: -1px; }
80% { --jiggle-x: 4px; }
}
|
陷阱和解决方案
尽管这种技术可能看起来无懈可击,但最好避免一些陷阱:
- CSS自定义属性只有在
<symbol>内使用var()引用时才起作用
- 最好包含一个回退值以及自定义属性
- 通过style属性设置的内联样式优先
- 你始终可以使用DevTools检查自定义属性值
结论
<symbol>和<use>元素是SVG中最优雅但有时令人沮丧的方面之一。Shadow DOM障碍使它们动画化更加棘手,但CSS自定义属性充当了桥梁。它们让你可以跨越那个无形的边界传递颜色、动作和个性,从而产生更清晰、更轻量,以及最重要的,更有趣的动画。