纯css实现的svg旋转加载动画

原文: Building a pure CSS animated SVG spinner

创建svg circle

我们从创建一个圆形开始

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="50"/>
</svg>

<svg>viewBox是100x100,圆的半径是50,圆心坐标为(50,50),给图形添加以下的样式

svg {
    max-width: 100px;
}
circle {
    fill: #2f3d4c;
}

如果你之前没有给svg设置过样式,fill属性的作用很像background-color

添加stroke

添加stroke-width并设定值为10,去掉填充颜色

circle {
    fill: transparent;
    stroke: #2f3d4c;
    stroke-width: 10;
}

10是相对于整个svg元素在viewBox属性中定义的尺寸值,效果看起来是这样:

并不是很理想,<circle>的轮廓渲染到了<svg>元素的外面,并且大部分浏览器会设置svg的overflow的默认值为hidden

可以给<svg>设置overflow: visible,但是更好的做法是将圆形限制在父svg元素的大小范围内,可以把半径改为45

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="45"/>
</svg>

直接修改半径的方式并不理想,但是这里没有可以通过css来实现的方法。
不过至少在改变父元素svg的大小时,它可以按比例缩放,如下

一半大小

两倍大小

两个例子中stroke-width依然是10,轮廓也随着svg尺寸的变化而缩放。

加粗stroke

当图形缩小时轮廓会显得有些细,减小半径同时加大stroke-width让轮廓变粗一些

改变stroke长度

使用SVG属性stroke-dasharraystroke-dashoffset改变stroke的长度,这两个属性都可以通过css来设置,因此能被利用在css动画中。

Stroke dashes

stroke-dasharray用于给stroke添加不同长度的划线,它和border-style:dashed有些像,但是功能更强大。它接受由逗号或者空格分割的值来定义短划线以及空白间隔的长度,这里使用一个值将线与间隔设置为同样长度。

stroke-dasharray: 5

stroke-dasharray: 24

stroke-dasharray: 283

值是圆周长时和之前的看起来效果一样,但是现在可以用来做更多的事情。但是首先还是需要给圆形添加一些样式,设置路径两端的形状和旋转中心

circle {
  stroke-linecap: round;
  transform-origin: 50% 50%;
}

Stroke dash offset

通过stroke-dashoffset改变线的起始位置

stroke-dashoffset: 75

stroke-dashoffset: 280

75和280将是下面css动画中使用的两个点

计算dash array和dash offset的值

你可能好奇stroke-dasharray的刚好覆盖整个圆周的值283如何得到,利用C = 2πR,圆半径是45时的周长是2 x 3.1416 x 45,得到282.74,向上取整到283。

svg圆形的stroke-dasharraystroke-dashoffset会根据圆半径覆盖在圆周的不同部分,当圆半径改变时,圆周长也会改变,即使stroke-dasharray的值不变它也会同时改变划线覆盖的范围。

stroke-dasharray都是157,使用不同的半径值得到的效果:

半径50

半径45

stroke-dasharray: 157将是r="50"的圆周50%的长度,但对r="45"的圆是55.4%的长度。请注意这一点

还有一个需要提示的是stroke-dasharraystroke-dashoffset都可以只用百分比单位,如果stroke-dasharray: 100%是覆盖圆周的长度就好了,但实际这个值相对于svg的viewBox,这里的100%等同于100

Sass函数

如果使用Sass,可以写个函数来做计算

@function get-dash-value($radius, $percentage) {
  // 使用 $radius 计算周长
  $circumference: 2 * 3.1415927 * $radius;
  
  // 百分比转小数
  // i.e. 50% = 0.5.
  $percentage-as-decimal: $percentage / 100%;
  
  // 返回值
  @return $circumference * $percentage-as-decimal;
}

添加动画

<circle>添加帧动画,让stroke-dashoffset的值在75和280之间变换。

// 改变stroke-dashoffset值的动画
@keyframes circle--animation {
  0% {
    stroke-dashoffset: 75;
  }
  
  50% {
    stroke-dashoffset: 280;
  }
}

// 完整的动画属性
circle {
  animation-duration: 1.4s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-fill-mode: both;
  animation-name: circle--animation;

}

// 可以使用属性简写
circle {
  animation: 1.4s ease-in-out infinite both circle--animation;
}

结合多个动画

接下来旋转<svg>元素

@keyframes svg--animation {
  0% {
    transform: rotateZ(0deg);
  }
  100% {
    transform: rotateZ(360deg)
  }
}

svg {
    animation: 2s linear infinite svg--animation;
}

<svg>元素的动画比较简单,循环每两秒旋转360度

两个动画都是一直循环,0.6秒的差异让它们各自交错进行,每14秒相遇一次

添加暂停与旋转

离最终效果很接近,但现在还不是我们想要的样子。

@keyframes circle--animation {
  0%,
  25% {
    stroke-dashoffset: 280;
    transform: rotate(0);
  }

  // 从25%的地方开始边框的“头”远离尾部画出长线
  50%,
  75% {
    stroke-dashoffset: 75;
    transform: rotate(45deg);
  }

// 描边重新缩短并旋转到开始的位置
  // 这里是边框的“尾巴”赶上了头部
  // 边框逆时针移动,同时圆圈圆圈顺时针转动回到起始位置
  100% {
    stroke-dashoffset: 280;
    transform: rotate(360deg);
  }
}

circle {
  animation: 1.4s ease-in-out infinite both circle--animation;
}

加上svg元素的动画

要更好的感受两个动画的如何协作,可以鼠标悬浮在svg上看它如何转动。(有一个灰色的底色)

圆形的描线依然会向后摆,但是同时添加的旋转效果补偿了向后的移动。也就是说圆圈向前转动的速度足够快,以至于感受不到线在向后走。

把动画拆分开不同的部分可以看到:

只有dashoffsets:

增加circle元素的旋转:

增加svg元素的旋转:

完整代码

HTML代码

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="45"/>
</svg>

CSS部分

// SVG 样式.
svg {
  animation: 2s linear infinite svg-animation;
  max-width: 100px;
}

// SVG 动画.
@keyframes svg-animation {
  0% {
    transform: rotateZ(0deg);
  }
  100% {
    transform: rotateZ(360deg)
  }
}

// Circle 样式.
circle {
  animation: 1.4s ease-in-out infinite both circle-animation;
  display: block;
  fill: transparent;
  stroke: #2f3d4c;
  stroke-linecap: round;
  stroke-dasharray: 283;
  stroke-dashoffset: 280;
  stroke-width: 10px;
  transform-origin: 50% 50%;
}

// Circle 动画.
@keyframes circle-animation {
  0%,
  25% {
    stroke-dashoffset: 280;
    transform: rotate(0);
  }
  
  50%,
  75% {
    stroke-dashoffset: 75;
    transform: rotate(45deg);
  }
  
  100% {
    stroke-dashoffset: 280;
    transform: rotate(360deg);
  }
}

说明

简单起见使用了元素选择器,自己代码中建议使用class name;另外注意动画属性浏览器前缀的补全。

浏览器支持

IE11及以下版本不支持stroke-dasharraystroke-offset属性动画

Comments
Write a Comment