前言
在网站页面加载以及表单提交时, 常使用进度条表达加载过程来优化用户体验, 常见的进度条有矩形进度条和圆形进度条, 如下图所示:
我们经常使用 svg 或 canvas 来实现动态图形的绘制, 但绘制过程相对较繁琐对于直观漂亮的进度条, 社区也有提供成熟的方案例如 highcharts/ECharts 等等, 但基于配置的开发方式终究无法实现 100% 的自定义绘制本文将带你使用 D3.js 从零一步一步实现动态进度条, 并分享代码逻辑原理
基础要求
了解 svg 如何绘制基础图形
了解 D3.js v4 版本
了解如何使用 D3.js (v4)绘制 svg 的基础图形
绘制圆形进度条
对于一个圆形进度条, 我们先对其进行任务拆分:
绘制嵌套圆弧
圆心处的实时数据展示
展现动画
美化
1. 绘制嵌套圆弧
对于圆形, svg 提供现成的 circle 标签供使用, 但是其劣势在于, 对于圆形进度条使用 circle 可以满足, 但对图形进一步扩展时比如绘制半圆, circle 的处理就棘手了 D3.js 提供 arc 相关 API 对圆形的绘制方法进行了封装:
- 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 > 上的路径数据完整代码如下:
- <!--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 个步骤:
1. 生成将 0 度作为起点的圆弧构造器 arcGenerator
2. 设置 transform 图形偏移量, 令图形在画布中央
目前画布上还没有任何元素, 接下来我们实际图形的绘制
- 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, 所谓的进度条其实就是覆盖在第一层圆弧之上的第二层圆弧同理可得:
- var upperGround = picture.append('path')
- .datum({endAngle:Math.PI / 2})
- .style('fill','#FFC125')
- .attr('d',arcGenerator)
代码运行后可得:
2. 圆心处的实时数据展示
第一部分我们已经实现了基于两个 path 的嵌套圆第二部分我们来实现圆心处的实时数据展示 在进度条进行加载时, 我们在圆心处添加数据来表达当前的加载进度, 使用 < text > 标签做展示即可:
- var dataText = g.append('text')
- .text(12)
- .attr('text-anchor','middle')
- .attr('dominant-baseline','middle')
- .attr('font-size','38px')
暂时将数据设置为 12, 并设置水平居中和垂直居中, 效果如下图:
3. 展现动画
通过 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)
最终效果如下:
4. 美化
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 行左右并可封装复用, 已经非常精炼了, 在自定义图表开发上非常有优势
对于进度条的衍生版仪表盘图表, 相比基础进度条增加了刻度描述和指针计算, 但万变不离其宗, 只要掌握插值原理和使用, 处理类似图表都将得心应手
来源: https://juejin.im/post/5a81a8946fb9a06334266f05