SVG描边动画(stroke animation
),也可以叫做"路径绘制动画"(path drawing animation
),路径描边动画或路径动画(path animation
)。其实现的效果类似描边一样,将图形绘制出来,即沿着线条绘制,因此也被称为"线条动画"(line animation
)
SVG是一种XML格式的图形,和DOM类似,可以使用CSS动画(关键帧keyframes
)实现各种动效。
使用图片或css可能也可以实现一些动画效果,但是,svg图形可以无限放大缩小,动画效果的实现相较也比较简单。比图片灵活,而结合CSS动画又比纯CSS动画强大。
描边动画原理(重要)
SVG描边动画相关的3个属性:
-
stroke:用来定义边框的颜色。
-
stroke-dasharray:定义
dash
和gap
的长度,分隔实线和间隔的值。实现边框虚线的效果,和CSS中dash
的效果一样。如:stroke-dasharray:5, 5
表示,按照实线为5,间隔为5的排布重复下去。如下图:stroke-dasharray:5
同样表示实线5,间隔5的虚线排列下去。stroke-dasharray:5, 3, 10
表示实线5,间隔3,实线10,然后间隔5,实线3,间隔10,实线5...不断的循环填满线条。 -
stroke-dashoffset:设置
dasharray
定义的dash
线条开始的位置。值为number
||percentage
。百分数是相对于SVG的viewport
。通常结合dasharray
可以实现描边动画效果。
SVG线条描边动画是通过stroke-dashoffset
和stroke-dasharray
实现的,分为两个步骤:
-
通过
dasharray
将实线部分增加至全长。比如:一条 path 的长度为300,如果把 SVG 中 path 的stroke-dasharray
的值设置为300,300,即表示这条 path 将会按照实线为 300,间隔为 300 的排布重复下去。所以默认的情况下,我们只会看到一条300长度的实线,间隔300的线段由于已经在画布外,所以是不可见的。 -
同时,通过
stroke-dashoffset
移动新增的实线部分,造成线段移动的效果。比如由:stroke-dashoffset:500
变为stroke-dashoffset:0
。
如下,通过一个示例了解描边的实现过程:
一条长200的线条。
<svg x="0px" y="0px" width="300px" height="100px" viewBox="0 0 300 100" class="svg1">
<line x1="20" y1="50" x2="220" y2="50" stroke="#000" stroke-width="1" ></line>
</svg>
.svg1{
border: 2px solid red;
}
.svg1 line {
stroke-dasharray:200,200;
stroke-dashoffset:200;
}
因为 stroke-dasharray
和 stroke-dashoffset
的值都是200,所以这条线段显示的是长为200的gap,没有线条。把 stroke-dashoffset
的值设置为0,dash线段就显示了出来,如下动图所示:
实际上,只要gap的长度大于线段的长度,并且dashoffset与gap长度相同即可,当然dash的长度最好与线段长度相同。最好的是四者均相同。
间隔的距离要为实线的长度
描边的动画的另一个实现思路是,通过调整,递增虚线中实线的长度,使其逐渐占满整条线段。这样就不需要涉及stroke偏移的调整。
通过这个原理,结合CSS的transition
或animation
就可以轻松实现一个描边动画:
.svg1{
border: 2px solid red;
}
/* js toggle class */
.svg1 line {
stroke-dasharray:200,200;
stroke-dashoffset:200;
transition:all 2s linear;
}
.svg1.move line{
stroke-dashoffset:0;
}
let body=document.getElementsByTagName('body')[0];
body.addEventListener('click',()=>{
var svg1=document.querySelector('.svg1');
svg1.classList.toggle('move');
});
通过点击切换class,实现描边的变化。
- css animation
/* css animation */
.svg1 line {
stroke-dasharray:200,200;
stroke-dashoffset:200;
animation: moveline 2s linear infinite;
}
@keyframes moveline {
50% {
stroke-dashoffset:0;
}
100%{
stroke-dashoffset:-200;
}
}
注:
对于描边动画,虽然要描边的总长度不变,但是可以通过改变虚线的长短实现绘制的快慢,这需要实线和时间的配合。原因在于同一时间内,线段短的肯定要比线段长的移动的快才能绘制完成(offset偏移的大小)。
为了优化效果,多个图形的路径动画,还可以通过延迟、调整执行时长等,实现更好的效果。
SVG CSS 描边动画
描边动画:
stroke animation
。或者路径绘制动画:path drawing animation
,有时也称为路径描边动画,路径动画path animation
描边动画示例
完成一个如下的动画效果:
这个动画由一个颜色填充动画、描边线条动画和放大动画三个动画组成。
首先看一下svg图片的代码,由一个圆和对勾path组成,首先默认看一下效果
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" stroke="black" />
<path class="checkmark__check" fill="none" stroke="black" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
</svg>
viewBox的作用在于以完整,或以设置的视野,将图像呈现到svg中,方便调整svg的大小,而不影响看到的(显示的)图形。这一点可以从测试使用中体会到
原动画的初始状态为空(各个元素不显示),需要描边的动画是圆圈和中间的对勾。
初始需要把circle的fill为none,没有填充颜色,stroke-dasharry
和 stroke-dashoffset
的值设置为 circle 的长度值。把 path 元素的 stroke-dasharry
和 stroke-dashoffset
的值设置为 path 的长度值。关于长度值的获取可以使用getTotalLength()
,下一小部分有介绍
/* 定义svg */
.checkmark {
width: 56px;
height: 56px;
border-radius: 50%;
stroke-width: 2;
stroke: #fff;
box-shadow: inset 0px 0px 0px #7ac142;
}
/* 初始化时圆 */
.checkmark__circle {
stroke-dasharray: 157;
stroke-dashoffset: 157;
stroke: #7ac142;
fill: none;
}
/* 初始化时对勾 */
.checkmark__check {
transform-origin: 50% 50%;
stroke-dasharray: 34;
stroke-dashoffset: 34;
}
圆和对勾的线条描边动画,关键帧中目标状态把 stroke-dashoffset
设置为0:
@keyframes stroke {
100% {
stroke-dashoffset: 0;
}
}
填充和缩放的动画:
@keyframes scale {
0%, 100% {
transform: none;
}
50% {
transform: scale3d(1.1, 1.1, 1);
}
}
@keyframes fill {
100% {
box-shadow: inset 0px 0px 0px 30px #7ac142;
}
}
完整代码如下:
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" />
<path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8" />
</svg>
/* 定义svg */
.checkmark {
width: 56px;
height: 56px;
border-radius: 50%;
stroke-width: 2;
stroke: #fff;
box-shadow: inset 0px 0px 0px #7ac142;
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
}
.checkmark__circle {
stroke-dasharray: 157;
stroke-dashoffset: 157;
stroke: #7ac142;
fill: none;
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}
.checkmark__check {
transform-origin: 50% 50%;
stroke-dasharray: 34;
stroke-dashoffset: 34;
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
}
@keyframes stroke {
100% {
stroke-dashoffset: 0;
}
}
@keyframes scale {
0%, 100% {
transform: none;
}
50% {
transform: scale3d(1.1, 1.1, 1);
}
}
@keyframes fill {
100% {
box-shadow: inset 0px 0px 0px 30px #7ac142;
}
}
使用getTotalLength()
获取svg图形元素的长度
可以使用JavaScript 的 API获取整条 path 的长度值,只需两行代码:
let path = document.querySelector('path');
let p_length = path.getTotalLength();
同样,circle的长度也可以使用getTotalLength()
获取
let circle = document.querySelector('circle');
let c_length = circle.getTotalLength();
关于svg图形元素的长度,可以在AI的"文档信息"面板中看到:
path多路径的描边动画的处理
先看一下如下六边形的svg:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 300 200" xml:space="preserve">
<path class="st0" fill="green" stroke="#000" d="M181.5,111l-32,18l-31.6-18.7l0.4-36.7l32-18l31.6,18.7L181.5,111z M181.7,111.4l0.3-0.1
l0.4-37.3l-32.1-19l-32.5,18.3l-0.4,37.3l32.1,19L181.7,111.4z M118.8,73.9l31.5-17.8l31.1,18.4l-0.4,36.1l-31.5,17.8l-31.1-18.4
L118.8,73.9z"/>
</svg>
添加如下css,实现描边动画:
.st0{
stroke-dasharray: 670 670;
stroke-dashoffset:670;
animation: st0move 3s linear;
}
@keyframes st0move {
0%{
stroke-dashoffset:0;
}
100%{
stroke-dashoffset: 670;
}
}
可以看到动画效果实现的"很乱",
这是因为path路径中使用了多个图形路径,这样就不能控制路径的起始位置,使用描边动画会多条线一起变化,不能控制顺序。导致变化时很乱。
要控制path路径的顺序,解决办法就是拆分path中的多条线(d
属性中设置的多路径分别拆分为多个单条路径)。
操作办法一个是通过代码直接拆开;或者在AI中,用钢笔工具一笔一笔照着轮廓勾画出路径,然后导出为path元素。
修改后如下:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 300 200" xml:space="preserve">
<path class="st0" fill="green" stroke="#000" d="M181.5,111l-32,18l-31.6-18.7l0.4-36.7l32-18l31.6,18.7L181.5,111z"/>
<path class="st0" fill="green" stroke="#000" d="M181.7,111.4l0.3-0.1
l0.4-37.3l-32.1-19l-32.5,18.3l-0.4,37.3l32.1,19L181.7,111.4z"/>
<path class="st0" fill="green" stroke="#000" d="M118.8,73.9l31.5-17.8l31.1,18.4l-0.4,36.1l-31.5,17.8l-31.1-18.4
L118.8,73.9z"/>
</svg>
.st0{
stroke-dasharray: 670 670;
stroke-dashoffset:670;
animation: st0move 2s linear forwards;
}
@keyframes st0move {
0%{
stroke-dashoffset:0;
}
100%{
stroke-dashoffset: 670;
}
}
.st0:nth-child(2) {
animation-delay: 1s;
}
.st0:nth-child(3) {
animation-delay: 2s;
}
关于path中多个路径的情况,在实际使用中会比这个复杂的多,此处只是个说明。处理起来也比较烦人,可能需要结合其他动画原理,重点还是图形中的实现元素要合理,便于操作控制。
比如此处例子,中间的六边形不用变,围绕六边形的边只要一条闭合线条就可以,但是原svg代码用了很多条线,这样在移动和控制时,效果就会很不好看。