最近在項目中遇到一個需求:繪制圓形進度條倒計時功能
UI設計圖如下:
對于圖片中數字的顯示實現起來是比較容易的,這里就直接忽略掉,主要是想把繪制動態圓的整個思路寫下來
動畫就是由一幅幅靜態圖片以極快的速度連續播放而產生的效果
canvas實現動畫的原理亦是如此
每隔一段時間重新繪制圖形隨后清除圖形,模擬動畫的過程
分析
有了對動畫的理解,我們先分析一下應該如何實現
- 最底下先畫一個顏色為藍色的靜態圓(我們先稱其為基礎圓)
- 最上面是一個動態的圓,效果是每隔一定的時間圓走過一定的角度
- 循環采用定時器(setTimeout 或者 setInterval)
實現
根據上面的分析就可以著手實現了,有關canvas的api不了解的請參考w3school
繪制基礎圓
- 定義canvas畫布
<canvas id="canvas" width="200px" height="200px"></canvas>
- 畫基礎圓
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d'); //返回對象,該對象提供畫圖用到的所有方法和屬性三繪制路徑
funcation drawBase(){
ctx.beginPath(); //定義一天起始路徑
ctx.lineWidth = 10; //設置線條的寬度
ctx.strokeStyle = '#f6f6f6'; //設置筆觸顏色
ctx.arc(x, y, r, start. end, false); //x,y為圓的圓心坐標,start為開始角度,end為結束角度,false是設置繪制圓以順時針方向
ctx.arc(0, 0, 50, 0, Math.PI * 2, false);
ctx.stroke(); //繪制定義好的路徑 (該函數是繪制圖形的關鍵哦)
}
有了上面的代碼,設計圖中灰色的圓就畫好了
繪制動態圓
// 動態圓
function drawValue(srcDegree, targetDegree) {
ctx.beginPath();
ctx.lineWidth = 12;
ctx.lineCap = 'butt'; //設置線條結束時端點的樣式
ctx.strokeStyle = this.config.lineColor;
ctx.arc( 0, 0, 50, degreeToRadian(srcDegree - 90), degreeToRadian(targetDegree - 90), false); //這里需要把度數處理為弧度,由于圓是默認從0度開始畫,所以需要把角度減掉90度,從正上方的位置開始
ctx.stroke();
}
drawValue這個函數其實與drawBase沒有區別,都是實現畫圓的功能,差別在于一個的目標角度是變的,目前有利于理解先這么寫李,之后可以重構代碼合為一個函數會更加簡潔。
// 處理角度
function degreeToRadian(degree){
return degree * Math.PI / 180;
}
現在我們要計算執行一次畫圓函數角度應該增加多少?
如果讓瀏覽器1s中渲染50次,那么一次需要20ms,需求是120s畫完360度,120s等于120000ms, easing = 360/(120000/20)
// 清除畫布
function clearAll(){
ctx.clearRect(0, 0, 200, 200);
}
//圓形進度條
function draw(srcDegree, targetDegree){
clearAll();
drawBase();
drawValue(srcDegree, targetDegree);
}
現在定義好的兩個畫圓函數被draw調用,已經實現了我們分析過程的一大部分,還需要對每次的目標角度進行計算。
let targetDegree = 0; //全局變量
function drawFrame(){
let easing = 360 / 6000;
//當目標角度達到滿圓時就可以清空計時器對象和畫布
if(Math.round(targetDegree) >= Math.PI * 2){
clearInterval(timer);
clearAll();
}else{
targetDegree += easing; //實現勻速
draw(srcDegree, targetDegree);
}
}
現在所有的功能已經實現,只差調用,哈哈哈!
render(){
let timer = setInterval(drawFrame, 20);
}
render(); //哇歐!
是不是感覺還挺容易的,其實只要理解了動畫的原理還是很容易上手的,But問題還有完。。。
問題:如果說用戶一直停留在倒計時這個頁面,那么整個過程是沒有問題出現的,但是當用戶在倒計時的過程中想隱藏頁面或者tab切換頁簽,這個時候你就會發現在離開的過程中畫圓的速度跟你定義好的是不一樣的,那么問題出在哪里呢?
這個現象的出現是定時器導致的,由于
HTML5標準規定,為了節電,對不處于當前窗口的頁面,瀏覽器會將時間間隔擴大到1000ms
,因此單位時間內的畫圓角度就變了,以至于不能同步。
出現這個好像
解決不了的bug,真的是很惱人唉!多虧,多虧我有小眼睛,哈哈,我們兩個一起討論出了,是否能判斷用戶離開當前頁面,然后在離開期間擴大角度的變化速率,這樣單位時間內的角度是不變的,當用戶再回到頁面時也已經渲染到正確的位置。
查找資料后發現,瀏覽器支持visibilitychange
事件,窗口隱藏或tab頁簽切換都屬于該事件,根據document.isHideen屬性可以知道頁面是否被隱藏。
document.addEventListener('visibilitychange', this.eventHandler.bind(this));
eventHandler() {
let isHidden = document.hidden;
if (isHidden) {
this.easing = 360 / 5950 * 50;
} else {
this.easing = 360 / 5950;
}
}
我想對于這個需求或許還有其他更好的實現方法,后面會繼續查找資料, 若這篇文章哪里寫的不夠好的,請大家不吝賜教!