本文作者:ivweb villainthr
接SVG 动画精髓(上)
线条动画
SVG 中的线条动画常常用作过渡屏(splash screen)中。例如:
或者,一些比较炫酷的 LOGO 中,比如 AllowTeam 的:
看到这些炫酷的效果,大家有没有动心想学一学,看看自己到底能否做的这么好呢?
OK,我们现在来正式介绍一下线条动画。在 SVG 中,最长用到的线条标签就是 Path。这里我前面一篇文章已经做了介绍,我这里就不赘述了。
而在具体变化当中用到的是关于stroke
的相关属性:(下面的属性都可以直接用在 CSS 当中!)
- stroke*:定义笔触的颜色。例如:
stroke="green"
- stroke-dasharray:定义 dash 和 gap 的长度。它主要是通过使用 , 来分隔 实线 和 *间隔 的值。例如:
stroke-dasharray="5, 5"
表示,按照 实线为 5,间隔为 5 的排布重复下去。如下图:
放大看有:
另外,stroke-dasharray 并不局限于只能设置两个值,要知道,它本身的含义是设置最小重复单元,即,dash,gap,dash,gap...。
比如,我定义 stroke-dasharray="15, 10, 5"
则相当于,[15,10,5] 为一段。则有:
放大看则有:
- stroke-dashoffset*: 用来设置 dasharray 定义其实 dash 线条开始的位置。值可以为
number || percentage
。百分数是相对于 SVG 的 viewport。通常结合 dasharray 可以实现线条的运动。 - stroke-linecap: 线条的端点样式。
- stroke-linejoin: 线条连接的样式
- stroke-miterlimit: 一个比较复杂的概念,如果我们只是画一些一般的线段,使用上面 linejoin 即可。如果涉及对边角要求比较高的,则可以使用该属性进行定义。它的值,其实就是角长度比上线宽:
而实际理解的话,就是假设当 width 为 1。此时比例为 2。那么 miter = 2。那么超过 2 的 miter 部分则会被 cut 掉。可以参照:
他主要是配合linejoin
一起使用。因为 linejoin 默认取值就是 miter
。所以,默认情况下就可以使用该标签属性。它默认值为 4。其余的大家下去实践一下即可。详细可以参考: miter
- stroke-opacity:线段的透明度
- stroke-width:线的粗细。
OK,介绍完关于 path 的所有 stroke 属性之后,我们就要开始动手写一下让线条动起来的代码。简单来说,就是通过stroke-dashoffset
和 stroke-dasharray
来做。整个动画可以分为两个过程:
- 通过 dasharray 将实线部分隐藏,空余为全线段长。然后,将实线部分增加至全长。比如:
dasharray: 0,1000
变为dasharray: 1000,1000
。 - 同时,通过 dashoffset 来移动新增的实线部分,造成线段移动的效果。有:
dashoffset:0
,变为dashoffset:1000
。
不过,这里我们不打算使用 Path 来做啥复杂的动画,这主要考虑到手头没有一些 SVG 生成工具。所以,这里我们就以 Text 来做吧(因为做起来真的简单)。
这里,先以 IV-WEB 这段文字来做动画。
先给大家看一下最终结果:
那么这种动画是怎么做的呢?
这里,我主要介绍一下关于 CSS 相关,SVG 就一个 Text 我直接贴代码了:
代码语言:javascript复制 <svg viewBox="0 0 1320 300">
<!-- Symbol -->
<symbol id="s-text">
<text text-anchor="middle"
x="50%" y="50%" dy=".35em">
IV-WEB
</text>
</symbol>
<!-- Duplicate symbols -->
<use xlink:href="#s-text" class="text"
></use>
<use xlink:href="#s-text" class="text"
></use>
<use xlink:href="#s-text" class="text"
></use>
</svg>
上面是通过创建一个居中定位的字体,然后使用 3 个 text 重叠。具体 CSS 我们下面来说一下。首先,我们营造的效果是从无到有,就需要使用dasharray
将 gap 设置的足够大。这里我取 300 即可。
stroke-dasharray: 0 300;
然后,通过nth-child
选择器,给每一个文字使用不同的颜色值:
.text:nth-child(3n 1) {
stroke: #F60A0A;
}
.text:nth-child(3n 2) {
stroke: #F2FF14;
}
.text:nth-child(3n 3) {
stroke: #FB9505;
}
下面才是重点内容。此时,这 3 个 text 的起始点重合。我现在既要他们在运行时不完全重合,又要他们的线条能进行滚动。不啰嗦了,直接看代码吧:
代码语言:javascript复制@keyframes stroke {
100% {
stroke-dashoffset: 1000;
stroke-dasharray: 80 160;
}
}
@keyframes stroke1 {
100% {
stroke-dashoffset: 1080;
stroke-dasharray: 80 160;
}
}
@keyframes stroke2 {
100% {
stroke-dashoffset: 1160;
stroke-dasharray: 80 160;
}
}
这就是上面 3 个不同的 text 运用的动画。dashoffet
由 0 到 1000。这完成了滚动的目的。同时,为了让字体不重合,我还需要在对应字体的 dashoffset
上,加上不同的间隔距离。比如,第一个字体 offset 为 1000。那么第二个字体,我需要加上前一个字体 dash 的长度,即,80。所以,第二个字体就变为 1080
。那么第三个就是加上前两个的dash
长度,即 1160。
大致过程就是这样,详情可以查看: IVWEB 线条动画。
这里再给大家布置一个练习作业,如何实现无线连续的分段动画呢?
具体效果如图:
给点提示:
将多个文字重叠,取不同的 offset 和 array 即可。动画的终止位置一般取一个 gap dash 的周期长即可。
后面看看这篇文章反响如何,到时候再决定是否再写一篇续集,介绍该作业的原理。
SVG 文字
在 SVG 中定义文字直接使用text
标签即可。关于文字来说,一般而言需要注意的点就那么即可,文字的排列,间距等等。这些都可以直接使用 CSS 进行控制。不过,有几个属性比较特殊,这里需要额外提一下。
text-anchor
用来定义参考点和实际字符之间的定位关系。格式为:
- text-anchor: start | middle | end | inherit
直接看代码解释吧:
代码语言:javascript复制 <!-- Anchors in action -->
<text text-anchor="start"
x="60" y="40">A</text>
<text text-anchor="middle"
x="60" y="75">A</text>
<text text-anchor="end"
x="60" y="110">A</text>
第一个 A,参考的是 (60,40) 的点,定义为start
,那么参考点应该在字符的前面。
而剩下两个也是同样的道理:
tspan
现在,假如我们想在 text 里面添加一些特殊的字符效果,比如斜体,加粗等。由于,text 标签不能实现嵌套,所以,为了解决这个痛点,提出了 tspan 的标签。它其实就是一个可以嵌套的 text 标签。
代码语言:javascript复制<text x="10" y="30" style="font-size:12pt;">
Switch among
<tspan style="font-style:italic">italic</tspan>, normal,
and <tspan style="font-weight:bold">bold</tspan> text.
</text>
tspan 里面同样可以自定义相关的自身属性。详细的可以参考 tspan 我这里就不详述了。
在 Path 展示 text
Text 一般可以横放,竖放。那有没有啥办法让文字可以按照一定的路径任意排放呢?
有的,这里可以使用textPath
标签,来定义具体参考路径。
<path id="sharp-corner"
d="M 30 110 100 110 100 160"
style="stroke: gray; fill: none;"/>
<text>
<textPath xlink:href="#sharp-corner">
Making a quick turn
</textPath>
</text>
如图:
具体细节我这里就不多说了。
Clip
在 DOM 中如果想展示一个图片的部分,或者以某种形状展示图片的部分,一般是通过一个 cover div 来实现的。不过,如果涉及到不规则图形的话,那么 DOM 就有天生缺陷了(当然使用 CSS 里的clip-path
可以完成,不过兼容性不太好)。而在 SVG 中,提供了clipPath
标签,能够让我们自定义裁剪图片的范围和形状。
clipPath 里面可以接任何图形,比如,path,rect 甚至是 text。使用的时候,直接在 style 中,指定 clip-path
即可,或者直接使用 clip-path
属性指定。
<defs>
<clipPath id="textClip">
<text id="text1" x="20" y="20" transform="rotate(60)"
style="font-family: 'Liberation Sans';
font-size: 48pt; stroke: black; fill: none;">
CLIP
</text>
</clipPath>
</defs>
<use transform="translate(100, 0)"
xlink:href="#shapes" style="clip-path: url(#textClip);"/>
<use transform="translate(100, 0)"
xlink:href="#shapes" clip-path="url(#textClip);"/>
或者说,如果我们想画一个圆的裁剪区域的话:
代码语言:javascript复制<defs>
<clipPath id="circularPath" clipPathUnits="objectBoundingBox">
<circle cx="0.5" cy="0.5" r="0.5"/>
</clipPath>
</defs>
<use xlink:href="#shapes" style="clip-path: url(#circularPath);" />
Appendix 参考标签
g
分组标签应该毫无意外排第一,因为其实作为绘制图形中最常和最基本的标签。前面一篇文章也主要介绍过了,这里做点补充。
每一个分组标签都带有 id 属性,唯一标识该分组,为什么呢?
因为,后面我们可以使用该 id 标签添加动画,重用该分组等。
代码语言:javascript复制 <g id="demo" stroke="green" fill="white" stroke-width="5">
<circle cx="25" cy="25" r="15"/>
<circle cx="40" cy="25" r="15"/>
<circle cx="55" cy="25" r="15"/>
<circle cx="70" cy="25" r="15"/>
</g>
每个分组里面可以含有一些描述标签,比如 desc。 这些描述内容是不会被渲染的。
代码语言:javascript复制<g id="demo" stroke="green" fill="white" stroke-width="5">
<desc>Just Demo</desc>
<circle cx="25" cy="25" r="15"/>
</g>
use
该标签就是结合 g 标签一起使用,作用是可以复用 g 分组的样式。
代码语言:javascript复制<g id="Port">
<circle style="fill: inherit;" r="10"/>
</g>
<use x="50" y="30" xlink:href="#Port" class="classA"/>
里面使用xlink:href
加上指定 group 的 id,然后通过x
,y
属性指定副本放置的位置。不过,有一个限制,use 标签的 style 属性,并不能覆盖点原始的 group style 样式。而且,有时候,我们只是想使用一些模板,即,图形并未被解析,只有代码存在。这时候,就需要使用defs
来包裹了。
defs
用来保存一些代码,使其不会被浏览器解析。并且里面的分组可以被 use 属性的 style 样式所覆盖。
代码语言:javascript复制<defs>
<g id="Port">
<circle style="fill: inherit;" r="10"/>
</g>
</defs>
<use x="50" y="50" xlink:href="#Port" style="fill: blue;"/>
symbol
该标签和g
标签类似,也是用来进行分组。不过,它有个特点,即,不会被浏览器所渲染。那它不和 defs
差不多吗?
恩,确实。不过,defs 是官方推荐,用来包裹一些模板 svg 代码而创造出来,用来增加可读性的标签。而 symbol 是存粹的作为一个模板。它可以独立于 svg 的 viewbox 来自定义子 viewbox 和 preserveAspectRatio。
代码语言:javascript复制 <symbol id="sym01" viewBox="0 0 150 110">
<circle cx="50" cy="50" r="40" stroke-width="8"
stroke="red" fill="red"/>
<circle cx="90" cy="60" r="40" stroke-width="8"
stroke="green" fill="white"/>
</symbol>
<use href="#sym01"
x="0" y="0" width="100" height="50"/>
同样使用该模板,也是使用 use 标签来完成。
image
既然use
可以重用 SVG 代码,那么 SVG 里面能不能重用已经画好的 png/jpg 的图片呢?
这时候,就需要用到image
标签。其可以用来加载外部的 PNG, JPEG 图片,注意,官方规定是前两种,其它图片支持不支持官方没做答复。即,如果你使用 GIF 图片,并不能保证所有的浏览器都能正常显示。
<image xlink:href="kwanghwamun.jpg"
x="72" y="92"
width="160" height="120"/>
</svg>
同样,该 image
标签也具有自定义 preserveAspectRatio 的效果。
- x: 定义水平位置
- y: 定义垂直位置
- width: 图片渲染的宽度,必须有。
- height: 图片渲染的高度,必须有。
- preserveAspectRatio: 控制图片的缩放
marker
marker 一般是用来画箭头或者线段始末的标识图形。
代码语言:javascript复制 <defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5"
markerWidth="6" markerHeight="6" orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
</defs>
<polyline points="10,90 50,80 90,20" fill="none" stroke="black"
stroke-width="2" marker-end="url(#Triangle)" />
如图:
这里我们只需要里了解即可,因为在实际画的时候,直接使用相关工具生成更加方便。
a
这里的 a 标签和我们直接在 HTML 使用的超链接 a 标签类似。也是用来定义一个外链的。
代码语言:javascript复制 <a xlink:href="https://developer.mozilla.org/en-US/docs/SVG"
target="_blank">
<rect height="30" width="120" y="0" x="0" rx="15"/>
<text fill="white" text-anchor="middle"
y="21" x="60">SVG on MDN</text>
</a>
更多内容,可以关注我的公众号:前端小吉米。
分享吉米的前端路,后面也会定期推出当前热门的前端技术~ 比如,直播,VR 原文链接:http://www.ivweb.io/topic/59072cf006f26845b620dd87