使用SVG制作进度条之二

2018-01-27 10:27:44来源:https://www.w3cplus.com/svg/create-progress-bar-with-svg-and作者:W3CPlus人点击

分享

在上一节中,学习了怎么利用SVG的 stroke-dasharray 和 stroke-dashoffset 来制作进度条。记得在文章末尾留了一个悬念,说这一节中,要聊聊怎么用Vue来把这个SVG的进度封装成组件。


咱们先不聊Vue怎么把这个封装成组件(我搜索了一下,有现在所这方面组件,而且做得蛮好的,接下来先学习一下)。今天接着聊上一节中的进度条怎么来实现。不过略有不同。不同点来自于网上一位朋友向我提的一个问题。问题是这样的:


用SVG做一个环形的进度条一样的东西,这个很好做(用的 stroke-dasharray ),但是,我需要再做一个小圆,随着环形慢慢变成一个圆时一直和头部在一起移动(这个不懂)。这个能稍微指点一下我吗?



说实话,我也很好奇!但我想在以前的基础上添加一个 <circle> ,让这个新添加的 circle 跟着内圈做位移(旋转),应该是可以的。也正因为出于好奇,给他找了两个Demo( Demo1 、 Demo2 ) 。原本应该能满足其需求。但并不是这样,他希望不借用第三方的库。后来回来自己写了一个 Demo。简单的记录一下这个过程。


简单的分析一下

不管是水平的还是圆形的,其都具有三层,比如下图所示:



灰色表示进度条的底层(总共的长度)
红色表示进度条的进度 (已完成长度)
绿色表示起点 (跟随圆点)

在SVG中的话,对应的就是:


水平进度条,灰色和红色是由 <line> 元素构建,绿色的由 <circle> 构建
圆形进度条由三个 <circle> 构建

知道其中的原委,那就好办的得多了。咱们可以先绘制一个静态的图出来:


<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 水平进度条 -->
<line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#666" stroke-linecap="round" />
<line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#FC4D04" stroke-dasharray="170" stroke-dashoffset="90" stroke-linecap="round" id="lineInner" />
<circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" />
<!-- 圆形进度条 -->
<circle cx="100" cy="120" r="74" fill="none" stroke="#666" stroke-width="12" />
<circle cx="100" cy="120" r="74" fill="none" stroke="#FC4D04" stroke-width="12" stroke-dasharray="465" stroke-dashoffset="400" stroke-linecap="round" />
<circle cx="100" cy="120" r="74" fill="none" stroke="green" stroke-width="12" stroke-dashoffset="464.5" stroke-dasharray="465" stroke-linecap="round" />
</svg>

这个时候看到的效果如下:


从效果中,可以看出来,现在绿色的圆点都在默认的起点。并没有跟随进度条红色的部分。在SVG中并没有直接的方法或者API来让绿色点保持在红色点的终点。这个时候咱们需要借助CSS的特性来完成。这也离不开一些数学的计算。具体怎么来计算呢?先来看水平进度条。


通过前面的学习,知道使用 .getTotalLength() 可以知道其长度。在这个示例中,通过:


document.querySelectorAll('line')[0].getTotalLength() // => 170

可以知道整个水平进度条的总长度是 170 ,而红色的进度是通过 stroke-dashoffset 值来控制的,在这个示例中是 90 。有了这两个值,就很好的控制绿色的值了。使用CSS的 translateX() 来做对应的位移,其位移的值是 170 - 90 ,即 80 。


<circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" id="circle" style="transform: translateX(80px);"/>

在行内添加了 transform:translateX(80px) ,当然你也可以在CSS样式中写。这个时候你看到的效果如下:


上面完成了水平进度条的效果,接下来看圆形进度条。对于圆形的进度条,我们同要要知道其长度,同样可以能过 .getTotalLength() 来获取:


document.querySelectorAll('circle')[2].getTotalLength(); // =>464.2044677734375

建议上向上取整,此时我们的周长是 465 。除此之外,还可以通过 2π * r 来计算。


Math.PI * 2 * 74; // => 464.9557127312894

因为圆形进度条是一个圆,那么通过 transltate() 是无法实现的,这个时候,需要使用的是 transform 中的 rotate() 来旋转。既然需要旋转就需要一个角度值。那又得数学公式了。简单的回忆一下:


弧度 = 角度 * Math.PI / 180 => rad = (π / 180) * deg
角度 = 弧度 * 180 / Math.PI => deg = (rad * 180) / π

其实就是角度( deg )和弧度( rad )之间的转换。一个完整的圆的弧度是 2π ,所以 2π rad = 360° , 1 π rad = 180° , 1°=π/180 rad , 1 rad = 180°/π (约 57.29577951° )。以度数表示的角度,把数字乘以 π/180 便转换成弧度;以弧度表示的角度,乘以 180/π 便转换成度数。


角所对的弧长是半径的几倍,那么角的大小就是几弧度


平时我们常看到的各种弧度如下:



通过JavaScript可以来这样进行角度和弧度之间的换算:


rad = (Math.PI * deg) / 180
deg = (rad * 180) / Math.PI

下图展示了常见的角度和弧度之间的换算:



回到我们的示例当中来。通过 .getTotalLength() 可以获取圆角的长度约为 465 ,对应红色圆的 stroke-dashoffset 值为 400 。那么就能获取到红色进度的弧长为 65 。前面说过, 角所对的弧长是半径的几倍,那么角的大小就是几弧度 :


65 / 74 = .87rad

另外也可以将 .87rad 换成 deg 。根据前面的计算公式,可以计算出来:


65 / 74 * 180 / Math.PI = 50.32737389662636

大约 50deg 。那么在 circle 元素中添加:


transform:rotate(.8783783783783784rad); transform-origin: 100px 120px;

这个时候看到的效果如下:


特别声明:SVG的坐标系统和Web中的坐标系统是不一样的,所以需要对元素做坐标变换,也就有了 transform-origin: 100px 120px 的设置。


添加动画效果

SVG中改变 stroke-dashoffset 的值,可以实现线条自画的效果。这并不是什么新东西,也不是复杂的东西。在我们今天的示例当中的关键点是怎么当绿色的点跟着移动。


通过前面的了解,在水平进度条中,需要给 translateX( ) 一个值,而这个值是:


.getTotalLength() - stroke-dashoffset

对于圆形的稍为复杂一点,其要给 rotate() 传一个值:


(.getTotalLength() - stroke-dashoffset) / r; // => 弧度值

或者:


(.getTotalLength() - stroke-dashoffset) / r * 180 / Math.PI; =>角度值

既然知道其中原理,我们就来换成Vue的环境。把红色的 stroke-dashoffset 绑定一个数据,然后使用 input[type="range"] 来动态修改 stroke-dashoffset 的值。并且动态修改绿色圆点的位置或者旋转值。比如:


<div id="app">
<svg width="200" height="200" viewBox="0 0 200 200">
<line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#666" stroke-linecap="round" />
<line x1="10" y1="10" x2="180" y2="10" fill="none" stroke-width="12" stroke="#FC4D04" stroke-dasharray="170" :stroke-dashoffset="dashOffsetLine" stroke-linecap="round" id="lineInner" />
<circle cx="10" cy="10" r="3" fill="none" stroke="green" stroke-width="6" id="circle" style="transform: translateX(80px);" id="circle"/>
<circle cx="100" cy="120" r="74" fill="none" stroke="#666" stroke-width="12" />
<circle cx="100" cy="120" r="74" fill="none" stroke="#FC4D04" stroke-width="12" stroke-dasharray="465" :stroke-dashoffset="dashOffsetCircle" stroke-linecap="round" />
<circle cx="100" cy="120" r="74" fill="none" stroke="green" stroke-width="12" stroke-dashoffset="464.5" stroke-dasharray="465" stroke-linecap="round" id="circleInner" style="transform:rotate(.8783783783783784rad); transform-origin: 100px 120px;"/>
</svg>
<div class="action">
<div class="line">
<label for="">line: stroke-dashoffset = "{{dashOffsetLine}}"</label>
<input type="range" name="points" min="0" value="{{dashOffsetLine}}" v-model="dashOffsetLine" max="170" step="1" @input="moveLine">
</div>
<div class="circle">
<label>circle:stroke-dashoffset = "{{dashOffsetCircle}}"</label>
<input type="range" name="points" min="0" value="{{dashOffsetCircle}}" v-model="dashOffsetCircle" max="465" step="1" @input="moveCircle">
</div>
</div>
</div>

对应的Vue代码:


let app = new Vue({
el: '#app',
data () {
return {
dashOffsetCircle: 400,
dashOffsetLine: 90,
}
},
methods: {
moveLine: function (e){
let circleLine = document.getElementById('circle')
let lineInnerLen = document.getElementById('lineInner').getTotalLength()
circleLine.style.transform="translateX(" + (lineInnerLen - e.target.value) + "px)"
},
moveCircle: function(e) {
let circleInner = document.getElementById('circleInner')
let circleInnerLen = Math.ceil(document.getElementById('circleInner').getTotalLength())
let arcLen = circleInnerLen - this.dashOffsetCircle
let rad = arcLen / 74
circleInner.style.transform="rotate(" + rad + "rad)"
}
}
})

Vue代码写得比较拙逼,路过的大婶请多多指点。这个时候,你拖动 input ,改变其值时,就可以看到对应的效果。


事实上除了使用 line 元素和 circle 元素之外,还可以使用 path 元素来实现类似的效果。感兴趣的同学,不仿使用 path 来做一个。如果做了,记得在下面的评论中与一起分享您的成果。


总结

这篇文章主要在上一节的基础上添加了一个网友想要的效果。就是在进度条上有一个圆点,能跟随进度条一起移动。其实现原理并不复杂。对于水平进度条,通过 translateX() 来改变其位移的位置,对于圆形进度条,则通过旋转来改变其位置。不管是哪一种,都需要动态的计算出他们的值,然后修改对应元素的样式。很多时候CSS和SVG的结合能让我们做出一些意想不到的效果。如果你感兴趣的话,不仿一试。




大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。



如需转载,烦请注明出处: https://www.w3cplus.com/svg/create-progress-bar-with-svg-and-css.html


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台