SVG学习-stroke-dashoffset和stroke-dasharray
起因
周末玩游戏的时候发现游戏内有一个按钮的交互挺有趣,类似于这种效果
点击后边框有一个进度条,鼠标长按进度会增加,抬起后回到起始位置
咦,有点意思
于是就想着能不能使用前端的一些技术实现🤔
于是开始一系列的尝试(此处省略若干字),发现触及到知识盲区了😭,根本无从下手
转机
突然不知道怎么就想到SVG了,想着要不试试?没准能行,然后一顿搜索,眼前一亮
有大佬使用stroke
和stroke-dashoffset
及stroke-dasharray
做出进度条,同时又想到SVG可以和JavaScript交互,貌似可以实现我的需求
stork-dashoffset和stork-dasharray
那么stroke
和stroke-dashoffset
以及stroke-dasharray
是何方神圣腻?🤔
stroke
: 描边,接受一个颜色值。可作用于大部分SVG元素
stroke-dasharray
: 用于创建虚线描边
// 表示:虚线长10,间距10,然后重复 虚线长10,间距10
stroke-dasharray = '10'
// 表示:虚线长10,间距5,然后重复 虚线长10,间距5
stroke-dasharray = '10, 5'
// 当然还有更复杂的设置这里就不细讲了
stroke-dashoffset
:字如其意,表示stroke的偏移。这个属性是相对于起始点的偏移,正数偏移x值的时候,相当于往左移动了x个长度单位,负数偏移x的时候,相当于往右移动了x个长度单位
需要注意的是,不管偏移的方向是哪边,要记得dasharray 是循环的,也就是 虚线-间隔-虚线-间隔。
stroke-dashoffset
要搭配stroke-dasharray
才能看得出来效果,非虚线的话,是无法看出偏移的。
概念有点抽象,来看一个MDN的例子,图中红线段是偏移的距离
上图效果分别是:
- 没有虚线
stroke-dasharray="3 1"
,虚线没有设置偏移,也就是stroke-dashoffset值为0stroke-dashoffset="3"
,偏移正数,虚线整体左移了3个单位,图中3后面的红线段,就是起始线段,线段之后是1个单位的间隔,我们可见区域从这个间隔开始,然后循环 3-1,3-1的虚线-间隔-虚线-间隔stroke-dashoffset="-3"
,偏移负数,虚线整体右移动了3个单位,由于dasharray 是循环的,前面偏移的位置会有dasharray 填充上stroke-dashoffset="1"
,偏移正数,虚线整体左移了1个单位,最终呈现出来的效果跟 线段4 一样
利用这两个属性,我们可以做出好看的动画效果
设置
stroke-dasharray
为图形边长设置
stroke-dashoffset
为图形边长动态减少
stroke-dashoffset
到0
简析:第一步后就有一个长度为图形边长的长条,第二步由于设置了stroke-dashoffset
也为图形边长,因此长条会被推到不可见的位置,再通过第三步中动态减少stroke-dashoffset
,第一步中绘制的长条就会慢慢增长并显示出来
再次挑战
刚开始是想使用rect
来做的,但发现游戏中的按钮是有一定的圆角的,rect
在设置圆角之后再添加storke
有点丑,遂放弃,改用path
实现
观察原图,实现思路如下:
边框可以使用
path
绘制出来边框背景也使用
path
绘制(同一套d
属性),设置一下opacity
即可背景色使用SVG的
fill
填充出来文字使用
text
绘制
实现代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SVG</title>
<style>
#svg:active {
opacity: 0.9;
}
#path, #background {
stroke-width: 3;
stroke: #f00;
cursor: pointer;
/**
* 修正位置,设置水平垂直居中
*/
transform: translate(5px, 5px);
}
#background {
opacity: 0.2;
}
#text {
user-select: none;
cursor: pointer;
fill: #000;
/**
* 设置水平垂直居中
*/
dominant-baseline: middle;
text-anchor: middle;
}
</style>
<script>
let rafId = -1;
const WIDTH = 70;
const HEIGHT = 30;
const RADIUS = 6;
const DEFAULT_OFFSET = 15;
const STORK_LENGTH = (WIDTH + HEIGHT) << 1;
const STEP = 5;
/**
* @description: 生成带有圆角的path
*/
function roundedRect(w, h, tlr, trr, brr, blr) {
return `M 0 ${tlr} A ${tlr} ${tlr} 0 0 1 ${tlr} 0 L ${w - trr} 0 `
+ `A ${trr} ${trr} 0 0 1 ${w} ${trr} L ${w} ${h - brr} `
+ `A ${brr} ${brr} 0 0 1 ${w - brr} ${h} L ${blr} ${h} `
+ `A ${blr} ${blr} 0 0 1 0 ${h - blr} Z`;
}
function animation() {
const path = document.querySelector('#path');
const preOffset = path.getAttribute('stroke-dashoffset');
const newOffset = preOffset - STEP;
path.setAttribute('stroke-dashoffset', newOffset);
if (newOffset >= 0) {
rafId = window.requestAnimationFrame(animation);
} else {
path.setAttribute('stroke-dashoffset', STORK_LENGTH - DEFAULT_OFFSET);
alert('success');
}
}
function onMouseDown() {
rafId = window.requestAnimationFrame(animation);
}
function onMouseUp() {
window.cancelAnimationFrame(rafId);
path.setAttribute('stroke-dashoffset', STORK_LENGTH - DEFAULT_OFFSET);
}
window.addEventListener('load', () => {
const path = document.querySelector('#path');
const background = document.querySelector('#background');
path.setAttribute('stroke-dashoffset', STORK_LENGTH - DEFAULT_OFFSET);
path.setAttribute('stroke-dasharray', STORK_LENGTH);
const roundedPath = roundedRect(WIDTH, HEIGHT, RADIUS, RADIUS, RADIUS, RADIUS);
path.setAttribute('d', roundedPath);
background.setAttribute('d', roundedPath);
});
</script>
</head>
<body>
<svg id="svg" width="80" height="40" fill="#ccc" onmousedown="onMouseDown()" onmouseup="onMouseUp()"
xmlns="http://www.w3.org/2000/svg">
<path id="path"></path>
<path id="background"></path>
<text id="text" x="40" y="20">click me</text>
</svg>
</body>
</html>
最终效果
参考: