最近在項(xiàng)目中遇到一個(gè)需求:繪制圓形進(jìn)度條倒計(jì)時(shí)功能
UI設(shè)計(jì)圖如下:
對(duì)于圖片中數(shù)字的顯示實(shí)現(xiàn)起來(lái)是比較容易的俊扳,這里就直接忽略掉菊值,主要是想把繪制動(dòng)態(tài)圓的整個(gè)思路寫(xiě)下來(lái)
動(dòng)畫(huà)就是由一幅幅靜態(tài)圖片以極快的速度連續(xù)播放而產(chǎn)生的效果
canvas實(shí)現(xiàn)動(dòng)畫(huà)的原理亦是如此
每隔一段時(shí)間重新繪制圖形隨后清除圖形,模擬動(dòng)畫(huà)的過(guò)程
分析
有了對(duì)動(dòng)畫(huà)的理解刻剥,我們先分析一下應(yīng)該如何實(shí)現(xiàn)
- 最底下先畫(huà)一個(gè)顏色為藍(lán)色的靜態(tài)圓(我們先稱其為基礎(chǔ)圓)
- 最上面是一個(gè)動(dòng)態(tài)的圓遮咖,效果是每隔一定的時(shí)間圓走過(guò)一定的角度
- 循環(huán)采用定時(shí)器(setTimeout 或者 setInterval)
實(shí)現(xiàn)
根據(jù)上面的分析就可以著手實(shí)現(xiàn)了,有關(guān)canvas的api不了解的請(qǐng)參考w3school
繪制基礎(chǔ)圓
- 定義canvas畫(huà)布
<canvas id="canvas" width="200px" height="200px"></canvas>
- 畫(huà)基礎(chǔ)圓
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d'); //返回對(duì)象造虏,該對(duì)象提供畫(huà)圖用到的所有方法和屬性三繪制路徑
funcation drawBase(){
ctx.beginPath(); //定義一天起始路徑
ctx.lineWidth = 10; //設(shè)置線條的寬度
ctx.strokeStyle = '#f6f6f6'; //設(shè)置筆觸顏色
ctx.arc(x, y, r, start. end, false); //x,y為圓的圓心坐標(biāo)御吞,start為開(kāi)始角度,end為結(jié)束角度漓藕,false是設(shè)置繪制圓以順時(shí)針?lè)较? ctx.arc(0, 0, 50, 0, Math.PI * 2, false);
ctx.stroke(); //繪制定義好的路徑 (該函數(shù)是繪制圖形的關(guān)鍵哦)
}
有了上面的代碼魄藕,設(shè)計(jì)圖中灰色的圓就畫(huà)好了
繪制動(dòng)態(tài)圓
// 動(dòng)態(tài)圓
function drawValue(srcDegree, targetDegree) {
ctx.beginPath();
ctx.lineWidth = 12;
ctx.lineCap = 'butt'; //設(shè)置線條結(jié)束時(shí)端點(diǎn)的樣式
ctx.strokeStyle = this.config.lineColor;
ctx.arc( 0, 0, 50, degreeToRadian(srcDegree - 90), degreeToRadian(targetDegree - 90), false); //這里需要把度數(shù)處理為弧度,由于圓是默認(rèn)從0度開(kāi)始畫(huà)撵术,所以需要把角度減掉90度背率,從正上方的位置開(kāi)始
ctx.stroke();
}
drawValue這個(gè)函數(shù)其實(shí)與drawBase沒(méi)有區(qū)別,都是實(shí)現(xiàn)畫(huà)圓的功能嫩与,差別在于一個(gè)的目標(biāo)角度是變的寝姿,目前有利于理解先這么寫(xiě)李,之后可以重構(gòu)代碼合為一個(gè)函數(shù)會(huì)更加簡(jiǎn)潔划滋。
// 處理角度
function degreeToRadian(degree){
return degree * Math.PI / 180;
}
現(xiàn)在我們要計(jì)算執(zhí)行一次畫(huà)圓函數(shù)角度應(yīng)該增加多少饵筑?
如果讓瀏覽器1s中渲染50次,那么一次需要20ms处坪,需求是120s畫(huà)完360度根资,120s等于120000ms, easing = 360/(120000/20)
// 清除畫(huà)布
function clearAll(){
ctx.clearRect(0, 0, 200, 200);
}
//圓形進(jìn)度條
function draw(srcDegree, targetDegree){
clearAll();
drawBase();
drawValue(srcDegree, targetDegree);
}
現(xiàn)在定義好的兩個(gè)畫(huà)圓函數(shù)被draw調(diào)用架专,已經(jīng)實(shí)現(xiàn)了我們分析過(guò)程的一大部分,還需要對(duì)每次的目標(biāo)角度進(jìn)行計(jì)算玄帕。
let targetDegree = 0; //全局變量
function drawFrame(){
let easing = 360 / 6000;
//當(dāng)目標(biāo)角度達(dá)到滿圓時(shí)就可以清空計(jì)時(shí)器對(duì)象和畫(huà)布
if(Math.round(targetDegree) >= Math.PI * 2){
clearInterval(timer);
clearAll();
}else{
targetDegree += easing; //實(shí)現(xiàn)勻速
draw(srcDegree, targetDegree);
}
}
現(xiàn)在所有的功能已經(jīng)實(shí)現(xiàn)部脚,只差調(diào)用,哈哈哈裤纹!
render(){
let timer = setInterval(drawFrame, 20);
}
render(); //哇歐委刘!
是不是感覺(jué)還挺容易的,其實(shí)只要理解了動(dòng)畫(huà)的原理還是很容易上手的鹰椒,But問(wèn)題還有完锡移。。漆际。
問(wèn)題:如果說(shuō)用戶一直停留在倒計(jì)時(shí)這個(gè)頁(yè)面淆珊,那么整個(gè)過(guò)程是沒(méi)有問(wèn)題出現(xiàn)的,但是當(dāng)用戶在倒計(jì)時(shí)的過(guò)程中想隱藏頁(yè)面或者tab切換頁(yè)簽奸汇,這個(gè)時(shí)候你就會(huì)發(fā)現(xiàn)在離開(kāi)的過(guò)程中畫(huà)圓的速度跟你定義好的是不一樣的,那么問(wèn)題出在哪里呢施符?
這個(gè)現(xiàn)象的出現(xiàn)是定時(shí)器導(dǎo)致的,由于
HTML5標(biāo)準(zhǔn)規(guī)定茫蛹,為了節(jié)電操刀,對(duì)不處于當(dāng)前窗口的頁(yè)面烁挟,瀏覽器會(huì)將時(shí)間間隔擴(kuò)大到1000ms
婴洼,因此單位時(shí)間內(nèi)的畫(huà)圓角度就變了,以至于不能同步撼嗓。
出現(xiàn)這個(gè)好像
解決不了的bug柬采,真的是很惱人唉!多虧且警,多虧我有小眼睛粉捻,哈哈,我們兩個(gè)一起討論出了斑芜,是否能判斷用戶離開(kāi)當(dāng)前頁(yè)面肩刃,然后在離開(kāi)期間擴(kuò)大角度的變化速率,這樣單位時(shí)間內(nèi)的角度是不變的杏头,當(dāng)用戶再回到頁(yè)面時(shí)也已經(jīng)渲染到正確的位置盈包。
查找資料后發(fā)現(xiàn),瀏覽器支持visibilitychange
事件醇王,窗口隱藏或tab頁(yè)簽切換都屬于該事件呢燥,根據(jù)document.isHideen屬性可以知道頁(yè)面是否被隱藏。
document.addEventListener('visibilitychange', this.eventHandler.bind(this));
eventHandler() {
let isHidden = document.hidden;
if (isHidden) {
this.easing = 360 / 5950 * 50;
} else {
this.easing = 360 / 5950;
}
}
我想對(duì)于這個(gè)需求或許還有其他更好的實(shí)現(xiàn)方法寓娩,后面會(huì)繼續(xù)查找資料叛氨, 若這篇文章哪里寫(xiě)的不夠好的呼渣,請(qǐng)大家不吝賜教!