0

记一次CSS3和SVG

 7 months ago
source link: https://xieyufei.com/2024/01/17/Turn-Round-Arrow.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

记一次CSS3和SVG实现箭头拐弯动画 - 谢小飞的博客

记一次CSS3和S_

2024年1月17日 中午

3.2k 字

40 分钟

  这不最近我司的设计师又给笔者整活了,在数据大屏页面的中间位置,做了一个效果图,不过需要做一个箭头沿着路径实现拐弯的动画效果;这可咋整呢,本文就结合CSS3的特性和svg,看一下实现的思路。

  我们先来看一下最终的实现效果:

箭头拐弯效果

箭头拐弯效果

  面对这种复杂的(奇奇怪怪)动画需求,作为老前端人了,上来肯定就是质问设计师:

你能不能做个GIF图?

  直接贴个GIF图不就完事了,不过,设计给出了自己理由,最后出图的文件太大以及GIF图容易模糊,另一个原因其实是我们的设计做动画不专业;因此,在得到肯定不能的答复之后,设计给出的方案是:使用蒙版动画

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  所谓的蒙版动画,也就是;类似遮罩层动画,将一个遮罩层首先覆盖在想要动画的物体上方,比如我们这里的箭头;然后随着时间的推移,逐渐的抽离遮罩层,实现动画的目的。

  不过这种的动画效果用在这里感觉有点low,不是很合适;因此我们还是来一点点实现吧。

offset-path

  就在不知怎么做的时候,好在上网查资料,一眼看到了CSS新的属性offset-path给了我一丝希望;传统的CSS3动画只能实现平移、缩放、拉伸等规则的路径动画。

  而offset-path这个属性就比较强大了,可以让元素沿着指定的路径实现不规则路径,可以是任何的形状;我们看下其浏览器的支持程度:

浏览器支持度

浏览器支持度

  它的语法很强大,支持画circle(圆)、ellipse(椭圆)或者polygon(折线)等多种图形的函数,不过我们这里就使用自定义的path函数即可,传入一个path路径:

offset-path: path('<path-url>')

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  对路径语法不熟悉的小伙伴可以学习一下MDN路径的这篇文章;此外它还有两个重要的属性,一个是offset-distance,表示元素移动的距离,可以是px单位,也可以是百分比单位:

offset-distance: <length> | <percentage>

我们可以控制offset-distance属性来实现元素的动画效果,一般动画时设置从0%到100%。

  另一个属性就是offset-rotate,定义了元素运动时的角度和方向,可以是某个具体的角度,也可以是auto,也可以组合起来一起使用,auto表示让元素运动时自己根据轨迹调整角度即可:

offset-rotate: [ auto | reverse ] || <angle>             

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  因此有了上面的属性,我只要画一个div,给它一个线性过渡的背景CSS,然后让它沿着指定路径移动不就行了,说干就干。

<div class="page">
<div class="box"></div>
</div>
<style lang="scss" scoped>
.box {
width: 200px;
height: 20px;
background: linear-gradient(to left, #15db30, rgba(#15db30, 0.2));
offset-path: path("M 10 80 C 80 10, 130 10, 190 80 S 300 150, 360 80");
animation: move 4000ms infinite linear;
}
@keyframes move {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
</style>

  上面的path路径看着很复杂,其实就是一个简单的S型曲线的运动,这里我们让div沿着S型曲线运动;虽然想法很美好啦,不过现实也十分的现实,最后的效果如下:

offset-path效果

offset-path效果

  因为我们的div是长条的,并不是流体,所以我们看到的效果就是一长条沿着固定的轨道晃晃悠悠的移动,这肯定不行了,不能用div了。

  那怎么办呢,笔者又思考了很久,突然一个想法又冒出来了,想起了微积分的思路;同理,既然一个div太长了,那我把它切割成多个不就行了,只要我们的刀把div切得够细,再拼接起来,用户就看不出来是拼接的,说干就干。

<template>
<div class="page">
<div :class="['box', `box${item}`]" v-for="item in 20" :key="item"></div>
</div>
</template>
<style lang="scss" scoped>
.box {
width: 20px;
height: 20px;
position: absolute;
left: 0;
top: 0;
}

@for $i from 1 through 20 {
.box#{$i} {
background: linear-gradient(
to left,
rgba(#15db30, 1 - ($i - 1) * 0.05),
rgba(#15db30, 1 - $i * 0.05)
);
offset-path: path("M 10 80 C 80 10, 130 10, 190 80 S 300 150, 360 80");
offset-rotate: auto;
animation: move 10000ms infinite linear;
animation-delay: 500ms * $i;
}
}
</style>

  这里代码看着很复杂,其实很简单,首先我们循环了20个div,然后用scss的@for语法给20个div设置样式,这里我们给每个div一个背景颜色的从右到左线性过渡,逐渐透明,最后使用animation-delay设置一个延迟时间,效果如下:

线性效果

  最后的效果就像一条贪吃蛇一样,从头连到尾;不过这样的实现方式可能会有问题,如果delay的间隔太久,div之间就会有空隙,如果间隔太小,就会导致div之间有颜色重叠的部分,导致颜色排布不是很均匀。

  不过我们可以通过将div设置成宽只有几个px的长方形条状,同时把div切割的数量拉大,切成100或者200的div,这样就会好很多。

SVG动画

  箭头拐弯的效果我们已经实现的差不多了,只要再给它加上合适的路径即可;下面那么我们再来考虑一下,如何把箭头动画结合到背景中去;在切图时,我们肯定是需要将背景中的建筑元素切到单独的一张图片,但是箭头的轨道如果我们自己用代码实现会非常棘手。

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  因此笔者的想法是将轨道单独让设计切图出来,导出成svg,我们就可以参考svg中的代码,能够知道轨道实现的逻辑了;因此我们将整体放到SVG中实现起来会比较方便一点。

stroke-dasharray

  首先我们看一下轨道的实现方式,轨道环的实现很简单,直接使用path,设置stroke颜色和一个opacity就可以:

<path
d="M152.444292,118.156631 L492.312457,209.128474 C501.980784,211.716376 509.532541,219.268132 512.120443,228.936459 L603.092286,568.804625 C607.090738,583.742725 598.2224,599.093834 583.2843,603.092286 C578.541228,604.361855 573.547697,604.361855 568.804625,603.092286 L228.936459,512.120443 C219.268132,509.532541 211.716376,501.980784 209.128474,492.312457 L118.156631,152.444292 C114.158179,137.506192 123.026516,122.155083 137.964617,118.156631 C142.707689,116.887062 147.70122,116.887062 152.444292,118.156631 Z"
transform="rotate(-45) translate(-315.5, 114)"
fill="none"
stroke="#2A73E1"
stroke-width="8"
opacity="0.2"
></path>

  我们重点来看下轨道中间的点,这里就不得不说到svg的stroke-dasharray属性,这个属性可以用来控制实现描边的点的图案样式;它的语法很简单,就是传入一个数列:

stroke-dasharray="none | <dasharray>"

  这个数列中的数用逗号和空格间隔开,比如"5, 3, 2"这种形式,那么每个数字代表什么意思呢?我们先从简单的一个数字和两个数字开始:

<path
d="M 0,0 L 300, 0"
stroke-dasharray="10"
></path>
<path
d="M 0,0 L 300, 0"
stroke-dasharray="20 10"
></path>

stroke-dasharray

stroke-dasharray

  stroke-dasharray中的每个数字依次来表示短划线和缺口的长度,当一个数字10的时候,表示短划线和缺口的长度都是10,因此我们就会看到它的距离是比较均匀的;而两个数字的时候,第一个数字表示短划线,第二个数字表示缺口,因此我们看到短划线较长,而缺口较短。

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  理解了上面数字的含义,我们再扩展到三个数字和四个数字来看一下;如果是奇数数字的话会比较特殊,根据MDN文档上的解释:

如果提供了奇数个值,则这个值的数列重复一次,从而变成偶数个值。因此,5,3,2 等同于 5,3,2,5,3,2。

  由于奇数个数字在循环的时候会有一个位置衔接不上,因此这个属性定义的时候就将奇数个自动扩展到了偶数个,我们看下具体代码理解一下:

<path
d="M 0,0 L 300, 0"
stroke-dasharray="60, 30, 40"
></path>
<path
d="M 0,0 L 300, 0"
stroke-dasharray="60, 30, 40, 50"
></path>

  我们在每个短划线和缺口处用数字标记一下:

stroke-dasharray

stroke-dasharray

  我们发现,3个数字的时候,在第一次排列之后,第二次排列的时候,60所在的位置自动变成了缺口位,而不是短划线,这样自动进行了一次顺序扩展,这就是这个属性将奇数个自动扩展到了偶数个的效果;而4个数字则是照常循环排列。

  理解了stroke-dasharray属性,我们的轨道也可以在svg下来最终完成了。

animateMotion

  下面的轨道有了,我们就需要将切好的箭头放到svg中,在svg中,我们使用rect来代替div;那么,如何让rect动起来呢?

  svg也有自己的动画元素,这里使用animateMotion元素,它的作用就是让一个元素如何沿着运动路径进行移动。它的用法也很简单,在元素下层嵌入animateMotion元素,最重要的属性就是path,相当于CSS3中的offset-path属性:

<rect
width="3"
height="6"
:fill="`url(#orangeGradient${item})`"
v-for="item in 20"
:key="item"
transform="translate(-3, -3)"
>
<animateMotion
path="M583.2843,603.092286 C598.2224,599.093834 607.090738,583.742725 603.092286,568.804625 L568.5,440"
:begin="10 * item + 'ms'"
dur="3s"
repeatCount="indefinite"
rotate="auto"
></animateMotion>
</rect>

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  rotate属性也相当于CSS3中的offset-rotate,因此我们理解了上面CSS中的offset-*等一系列属性,animateMotion也就很好理解了。

viewBox自适应

  通过width/height属性,我们可以设置svg画布的固定大小:

<svg width="200" height="200"></svg>

  但是,在实际的场景中,我们经常需要让画布自适应外部div的宽高,以实现画布呈现大小适应页面的缩放;这里就要用到svg另一个属性viewBox了,我们看下mdn上对这个属性的介绍:

viewBox属性允许指定一个给定的一组图形伸展以适应特定的容器元素。

  它的属性值是一个包含四个参数的列表:min-x,min-y,width,height,四个值可以用空格或者逗号分隔开;

<svg viewBox="min-x, min-y, width, height">

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  viewBox顾名思义就是视图盒子,把它理解成截图工具呈现的效果就行;简单理解,min-x和min-y就是截图的右上角的x和y坐标,width和height就是截图区域的宽度。

  我们看下它的具体效果,首先,不带viewBox的情况下展示svg下的内容:

<svg
width="200"
height="200"
style="border: 1px solid red"
>
<rect x="0" y="0" width="50" height="40" />
<circle cx="10" cy="20" r="4" fill="white" />
</svg>

没有带viewBox属性

没有带viewBox属性

  元素正常大小显示,这时候我们给它加一个viewBox:

<svg
width="200"
height="200"
viewBox="0 0 60 60"
>
<!-- 其他元素 -->
</svg>

  我们会发现两个元素放大显示了,这个也很好理解,我们在200200的画布上截出一个6060的区域,然后就会等比例放大呈现出来。

带viewBox属性

带viewBox属性

  因此回到我们的箭头svg,为了实现自适应的效果,我们去掉svg的宽高,加上viewBox等于我们的画布宽高即可:

<svg
viewBox="0 0 730 561"
>
<!-- 其他元素 -->
</svg>

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

  点击查看本文的实现效果

  我们从CSS3的offset-*属性入手,了解了如何让一个元素实现非规则路径下的动画效果;然后我们为了方便,将动画效果迁移到了svg中去实现,对svg中的关键属性stroke-dasharray进行了详细的学习;最后为了实现自适应效果,使用了viewBox属性。

svg viewBox与自适应


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK