前言
最近看了看SVG基础教程,复刻一个边框流动效果练练手🧐。
SVG实现
制作坐标系
为了更加直观及方便绘制,现使用两层嵌套patterns绘制5*5 × 5*5坐标系:
<svg width="400" height="200">
<defs>
<pattern
id="gridUnit"
x="0"
y="0"
width="5"
height="5"
patternUnits="userSpaceOnUse"
>
<path
d="M 0 5 L 0 0 5 0"
stroke="rgba(0, 0, 0, 0.1)"
stroke-width="1"
fill="none"
></path>
</pattern>
<pattern
id="grid"
x="0"
y="0"
width="25"
height="25"
patternUnits="userSpaceOnUse"
>
<rect width="25" height="25" x="0" y="0" fill="url(#gridUnit)"></rect>
<path
d="M 0 25 L 0 0 25 0"
stroke="rgba(0, 0, 0, 0.2)"
stroke-width="1"
fill="none"
></path>
</pattern>
</defs>
<rect
stroke="#aaa"
width="400"
height="200"
x="0"
y="0"
fill="url(#grid)"
></rect>
</svg>
效果如下:
画一个边框
绘制一个边框:
<svg width="400" height="200">
<defs>
<!--grid-pattern-->
<g id="box">
<path
id="boxPath"
fill="transparent"
d="M 395 195 L 15 195 L 5 185 L 5 10 L 10 5 h 30 L 45 10 L 385 10 L 395 20 Z"
></path>
<line
stroke-width="3"
stroke-linecap="round"
stroke-dasharray="6,4"
x1="12"
y1="10"
x2="38"
y2="10"
></line>
</g>
</defs>
<!--grid-rect-->
<use stroke="#6586ec" stroke-width="1" xlink:href="#box"></use>
</svg>
效果如下:
新增描边
为了使描边与现有的边框互不影响,这里需要再绘制一次路径:
<svg>
...
<use
stroke="#4fd2dd"
stroke-linecap="round"
stroke-width="3"
xlink:href="#box"
></use>
</svg>
为了使效果更明显,将背景色调成暗色,效果如下:
实现遮罩
绘制一个circle元素作为遮罩,为了使流动效果不生硬,还需制作一个渐变效果:
<svg>
<defs>
...
<radialGradient id="radialGradient" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#fff" stop-opacity="1"></stop>
<stop offset="1" stop-color="#fff" stop-opacity="0"></stop>
</radialGradient>
<mask id="mask">
<circle cx="0" cy="0" r="150" fill="url(#radialGradient)"></circle>
</mask>
</defs>
...
<use
stroke="#4fd2dd"
stroke-linecap="round"
stroke-width="3"
xlink:href="#box"
mask="url(#mask)"
></use>
</svg>
mask元素的内容是一个单一的circle元素,它填充了一个白色到透明的渐变。作为应用mask的目标对象继承mark内容的alpha值(透明度)的结果,效果如下:
添加动画
现在只需让mask元素随着边框路径动起来就行了,animateMotion 元素定义了一个元素如何沿着运动路径进行移动。
备注: 为了复用一个已经定义的路径,就有必要使用一个mpath元素嵌入到animateMotion中,而不是使用 path
<mask id="mask">
<circle cx="395" cy="195" r="150" fill="url(#radialGradient)">
<animateMotion dur="4s" repeatCount="indefinite" rotate="auto">
<mpath xlink:href="#boxPath"></mpath>
</animateMotion>
</circle>
</mask>
效果如下:
可以看到效果基本就实现了,但是现在由于遮罩整个区域内都存在描边,流动部分透明度变化是0->1->0,如果想实现类似拖尾效果,则需要同时添加与遮罩动画同步的描边动画,这样一来遮罩区域就只会存在运动的描边了,添加如下代码:
<svg>
...
<use
stroke="#4fd2dd"
stroke-linecap="round"
stroke-width="3"
xlink:href="#box"
mask="url(#mask)"
>
<animate
attributeName="stroke-dasharray"
from="0,1143"
to="1143,0"
dur="4s"
repeatCount="indefinite"
></animate>
</use>
</svg>
描边动画通常使用stroke-dasharray及stroke-dashoffset实现,这里没有使用stroke-dashoffset,而是直接对stroke-dasharray属性添加动画,从最开始只有gap到gap长度为0的过渡过程就形成了描边动画,注意这里的1143是根据SVGPathElement.getTotalLength()获取的,最终效果:
整体代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<style>
body {
margin: unset;
display: grid;
place-content: center;
height: 100vh;
background-color: #282c34;
}
</style>
<svg width="400" height="200">
<defs>
<pattern
id="gridUnit"
x="0"
y="0"
width="5"
height="5"
patternUnits="userSpaceOnUse"
>
<path
d="M 0 5 L 0 0 5 0"
stroke="rgba(0, 0, 0, 0.1)"
stroke-width="1"
fill="none"
></path>
</pattern>
<pattern
id="grid"
x="0"
y="0"
width="25"
height="25"
patternUnits="userSpaceOnUse"
>
<rect width="25" height="25" x="0" y="0" fill="url(#gridUnit)"></rect>
<path
d="M 0 25 L 0 0 25 0"
stroke="rgba(0, 0, 0, 0.2)"
stroke-width="1"
fill="none"
></path>
</pattern>
<g id="box">
<path
id="boxPath"
fill="transparent"
d="M 395 195 L 15 195 L 5 185 L 5 10 L 10 5 h 30 L 45 10 L 385 10 L 395 20 Z"
></path>
<line
stroke-width="3"
stroke-linecap="round"
stroke-dasharray="6,4"
x1="12"
y1="10"
x2="38"
y2="10"
></line>
</g>
<radialGradient id="radialGradient" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#fff" stop-opacity="1"></stop>
<stop offset="1" stop-color="#fff" stop-opacity="0"></stop>
</radialGradient>
<mask id="mask">
<circle cx="0" cy="0" r="150" fill="url(#radialGradient)">
<animateMotion dur="4s" repeatCount="indefinite" rotate="auto">
<mpath xlink:href="#boxPath"></mpath>
</animateMotion>
</circle>
</mask>
</defs>
<rect
stroke="#aaa"
width="400"
height="200"
x="0"
y="0"
fill="url(#grid)"
></rect>
<use stroke="#235fa7" stroke-width="1" xlink:href="#box"></use>
<use
stroke="#4fd2dd"
stroke-linecap="round"
stroke-width="3"
xlink:href="#box"
mask="url(#mask)"
>
<animate
attributeName="stroke-dasharray"
from="0,1143"
to="1143,0"
dur="4s"
repeatCount="indefinite"
></animate>
</use>
</svg>
</body>
</html>