在网站页面加载以及表单提交时,常使用进度条表达加载过程来优化用户体验,常见的进度条有矩形进度条和圆形进度条,以下图所示:javascript
咱们常用svg或canvas来实现动态图形的绘制,但绘制过程相对较繁琐。对于直观漂亮的进度条,社区也有提供成熟的方案例如highcharts/ECharts等等,但基于配置的开发方式终究没法实现100%的自定义绘制。本文将带你使用D3.js从零一步一步实现动态进度条,并分享代码逻辑原理。css
对于一个圆形进度条,咱们先对其进行任务拆分:html
对于圆形,svg提供现成的circle
标签供使用,可是其劣势在于,对于圆形进度条使用circle
能够知足,但对图形进一步扩展时好比绘制半圆,circle
的处理就棘手了。D3.js提供arc
相关API对圆形的绘制方法进行了封装:java
var arc = d3.arc()
.innerRadius(180)
.outerRadius(240)
//.startAngle(0)
//.endAngle(Math.PI)
arc(); // "M0,-100A100,100,0,0,1,100,0L0,0Z"
复制代码
上述代码实现了对两个嵌套圆的绘制逻辑,d3.arc()
返回一个圆弧构造函数,并经过链式调用设置内圆与外圆的半径大小,起始角度与结束角度。执行arc()
构造函数便可得到用于绑定在<path>
上的路径数据。完整代码以下:git
<!--html-->
<svg width="960" height="500"></svg>
<script> var arcGenerator = d3.arc().innerRadius(80).outerRadius(100).startAngle(0); var picture = d3.select('svg').append('g').attr('transform','translate(480,250)'); </script>
复制代码
上述代码实现了2个步骤:github
arcGenerator
transform
图形偏移量,令图形在画布中央目前画布上尚未任何元素,接下来咱们实际图形的绘制。canvas
var backGround = picture.append("path")
.datum({endAngle: 2 * Math.PI})
.style("fill", "#FDF5E6")
.attr("d", arcGenerator);
复制代码
咱们对画布picture
添加<path>
元素,依据endAngle()
特性,使用datum()
方法将{endAngle:Math.PI}
也就是终点角度2π
绑定到<path>
元素上,并将圆弧构造器赋值给path
路径d
。这样就生成了指定背景颜色的圆弧,实际图形以下:数组
第一个圆弧画好了,那么依据svg的层级关系z-index
,所谓的进度条其实就是覆盖在第一层圆弧之上的第二层圆弧。同理可得:app
var upperGround = picture.append('path')
.datum({endAngle:Math.PI / 2})
.style('fill','#FFC125')
.attr('d',arcGenerator)
复制代码
代码运行后可得:dom
第一部分咱们已经实现了基于两个path
的嵌套圆。第二部分咱们来实现圆心处的实时数据展现。 在进度条进行加载时,咱们在圆心处添加数据来表达当前的加载进度,使用<text>
标签作展现便可:
var dataText = g.append('text')
.text(12)
.attr('text-anchor','middle')
.attr('dominant-baseline','middle')
.attr('font-size','38px')
复制代码
暂时将数据设置为12,并设置水平居中和垂直居中,效果以下图:
经过1,2两部份内容咱们已经知道了:
2π
时为整圆,当弧度是π
时为半圆2π
的百分比综上咱们只要改变弧度值和数值同时设定改变过程所需时长便可实现所谓"动画"。在ECharts提供的官方实例中,经过setInterval
来实现每隔固定一段时间进行数据更新,其实在D3.js中一样提供了相似方法来实现相似setInterval
的功能:
d3.interval(function(){
foreground.transition().duration(750).attrTween('d',function(d){
var compute = d3.interpolate(d.endAngle,Math.random() * Math.PI * 2);
return function(t){
d.endAngle = compute(t);
return arcGenerator(d);
}
})
},1000)
复制代码
对这段代码进行拆解:
d3.interval()
方法提供了setInterval()
的功能selection.transition.duration()
设置了当前DOM属性过渡变化为指定DOM属性的过程所需时间,毫秒为单位transation.attrTween
为插值功能API,那么何谓插值? 归纳来讲,在给定的离散数据中补插函数,可使这条连续函数经过所有数据点。举个例子,给定一个div,想实现其背景颜色的从左边红(red)到右边绿(green)的线性渐变,每一区域的色值该如何计算呢?只需:
var compute = d3.interpolate(d3.rgb(255,0,0),d3.rgb(0,255,0));
复制代码
compute
即为插值函数,参数范围为[0,1],只要你输入该范围内的数字,那么compute
函数将返回对应的颜色值。这样的插值有什么用呢?可看下图:
假设上图的div长度width为100,那么将[0,100]依比例关系转化为[0,10]的范围数据并输入compute
函数中,便可获得某一区域对应的颜色。固然,对于线性面积的处理咱们不该该使用离散数据做为输入和输出,因此D3.js提供更方便的线性渐变APId3.linear
等,这里就不展开描述了。
言归正传,代码d3.interpolate(d.endAngle,Math.random() * Math.PI * 2);
实现了以下插值范围:
["当前角度值","随机角度值"] //表达区间而非数组
复制代码
然后返回一个参数为t
的函数,那么该函数的做用是什么呢?
t
参数与d
相似,是D3.js内部实现的插值,其范围为[0,1]。t
参数根据设置的duration()
时长自动计算在[0,1]内合适的插值数量,并返回插值结果,实现线性平稳的过渡动画效果。
完成滚动条的动画加载效果,咱们接下来写圆心实时数据的变化逻辑,只要实现简单的赋值便可,完整代码以下:
d3.interval(function(){
foreground.transition().duration(750).attrTween('d',function(d){
var compute = d3.interpolate(d.endAngle,Math.random() * Math.PI * 2);
return function(t){
d.endAngle = compute(t);
var data = d.endAngle / Math.PI / 2 * 100;
//设置数值
d3.select('text').text(data.toFixed(0) + '%');
//将新参数传入,生成新的圆弧构造器
return arcGenerator(d);
}
})
},2000)
复制代码
最终效果以下:
1,2,3部分咱们实现了最基本的进度条样式和功能,但样式看起来仍是很单调的,咱们接下来咱们对进度条进行线性渐变处理。咱们使用D3.js提供的线性插值API:
var colorLinear = d3.scaleLinear().domain([0,100]).range(["#EEE685","#EE3B3B"]);
复制代码
colorLinear
一样是一个插值函数,咱们输入[0,100]区间中的数值,就会返回对应["#EEE685","#EE3B3B"]区间内的颜色值。好比当进度条显示进度为"80%"时:
var color = colorLinear(80);
//color即为"80%"对应的色值
复制代码
实现了颜色取值后,咱们只需在进度条变化时,将原有颜色改变便可:
d3.interval(function(){
foreground.transition().duration(750).attrTween('d',function(d){
var compute = d3.interpolate(d.endAngle,Math.random() * Math.PI * 2);
return function(t){
d.endAngle = compute(t);
var data = d.endAngle / Math.PI / 2 * 100;
//设置数值
d3.select('text').text(data.toFixed(0) + '%');
//将新参数传入,生成新的圆弧构造器
return arcGenerator(d);
}
})
.styleTween('fill',function(d){
return function(t){
var data = d.endAngle / Math.PI / 2 * 100;
//返回数值对应的色值
return colorLinear(data);
}
})
},2000)
复制代码
styleTween
与attrTween
相似,是实现改变样式的插值函数。采用链式调用的形式同时对进度条数值和颜色的设置便可。最终实现的效果以下:
综上咱们实现了在不一样数值下颜色变化的圆形进度条,可经常使用于告警,提醒等业务场景。
矩形进度条相比圆形进度条简单了不少,一样基于插值原理,平滑改变矩形的长度便可。直接上代码:
<head>
<style> #slider { height: 20px; width: 20px; background: #2394F5; margin: 15px; } </style>
</head>
<body>
<div id='slider'></div>
<script> d3.interval(function(){ d3.select("#slider").transition() .duration(1000) .attrTween("width", function() { var i = d3.interpolate(20, 400); var ci = d3.interpolate('#2394F5', '#BDF436'); var that = this; return function(t) { that.style.width = i(t) + 'px'; that.style.background = ci(t); }; }); },1500) </script>
</body>
复制代码
实现的效果以下:
基于D3.js绘制进度条的关键点在于插值,从而正确地使图形平滑过渡。若是必定要使用svg或纯css实现矩形和圆形的进度条固然也是可行的,但对于路径和动画的处理,以及css的书写要求都复杂了很多。咱们观察到使用D3.js绘制上述两种进度条的逻辑代码几乎彻底使用js实现,同时代码量能够控制在20行左右并可封装复用,已经很是精炼了,在自定义图表开发上很是有优点。
对于进度条的衍生版仪表盘图表,相比基础进度条增长了刻度描述和指针计算,但万变不离其宗,只要掌握插值原理和使用,处理相似图表都将驾轻就熟。