前言
在網站頁面載入以及表單提交時,常使用進度條表達載入過程來優化使用者體驗,常見的進度條有矩形進度條和圓形進度條,如下圖所示:
我們經常使用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行左右並可封裝複用,已經非常精煉了,在自定義圖表開發上非常有優勢。
對於進度條的衍生版儀表盤圖表,相比基礎進度條增加了刻度描述和指標計算,但萬變不離其宗,只要掌握插值原理和使用,處理類似圖表都將得心應手。