平臺:React
相關(guān)技術(shù): Canvas API ,?requestAnimationFrame API
剛?cè)肼毑痪孟丝兀拥揭粋€需求,關(guān)于重構(gòu)課程列表,撇去蛋疼的看源碼修改原有的顯示邏輯之外磷斧,當時最令我印象深刻的是這個圓形進度條的制作過程动分。
在看到這個需求時毅糟,我首先想到能不能用css3進行實現(xiàn),畢竟從性能來說css3是較好的刺啦,但是發(fā)現(xiàn)這樣最后尾巴的小點沒辦法解決留特,而且技術(shù)難度貌似不小,最終部門小伙伴說你可以用canvas嘗試一下,并直接給我一個靜態(tài)demo蜕青,感恩~苟蹈,我也趁機研究了一下canvas api的使用。
首先我們拿到這個圖案先拆分為4個部分右核,分別說外圓灰色細圈慧脱,外圓橘色細圈,內(nèi)部橘色圈贺喝,以及尾巴的小點, 下面針對一些核心繪制API進行分析
這里講一下比較蛋疼的最外側(cè)的圓弧和那個點的開發(fā)菱鸥。
最外側(cè)的圓弧核心代碼如下:(取context這種基礎(chǔ)咱就不寫了)
????????context.clearRect(0, 0,直徑, 直徑);
? ? ? ? context.beginPath();? ?<=開始繪制 ,包工頭說開始搬磚了
? ? ? ? context.lineWidth = 任意粗細躏鱼;?<=定義繪制粗細?
? ? ? ? context.strokeStyle = '定義畫圈的顏色';
? ? ? ? context.arc(直徑 / 2,? ?直徑 / 2,? ? 直徑 / 2 - 2*傳入半徑,? ?0,? ? ?傳入需要的百分比數(shù)值 * 0.02 * Math.PI -?0.5 * Math.PI? ? ?false);?<=重要氮采,繪制圓形路徑
? ? ? ? context.stroke(); <=針對strokeStyle部分結(jié)束繪制
? ? ? ? context.closePath();<=結(jié)束路徑 - 包工頭說下班了
這里主要講一下這里運用context.arc的繪制思路,這里請大家跟我回憶w3school上關(guān)于這個API的用法定義
前兩個不用說染苛,確定中心點鹊漠,第三個通過傳入一個半徑,通過減去茶行,切分的做法躯概,"漏"出一個傳入值的弧圈,可謂比較討巧畔师,原理看圖好懂點娶靡。
而起始角和結(jié)束腳我們需要通過下面這個圖(w3school參考圖(2))說明,起始點0(-0.5 * Math.PI)
結(jié)束點:傳入需要的百分比數(shù)值 * 0.02 * Math.PI -0.5 * Math.PI 看锉,先別急問0.02怎么來的姿锭,分析一下。
其實可以理解為-0.5 * Math.PI(也就是1.5 * Math.PI所在位置)就是起點度陆,因為最大值也就是1.5*PI艾凯,所以這里的增值最多為-0.5 + x = 1.5? ?x = 2,那么? 2/100 = 0.02份/1%懂傀,那么我們上面的公式就是這么來的趾诗。
那么上面的圓弧算是開發(fā)完畢了,我們來做一個更難的蹬蚁,那個點的跟蹤計算恃泪。
首先先上核心代碼
context.beginPath();
context.strokeStyle= "#FF9C00";
context.lineWidth= 2;
context.fillStyle= "#FF9C00";
let radian= 傳入進度 / 100 * 2 * Math.PI - 0.5 * Math.PI;
let x= Math.cos(radian)* (大圓弧直徑 / 2 - 2*裁去的半徑)+ 直徑 / 2(圓心x點);
let y= Math.sin(radian)* (大圓弧直徑 / 2 - 2*裁去的半徑)+ 直徑 / 2(圓心y點);;
context.arc(x, y, 0.8 * r, 0, 2 * Math.PI, false);
context.stroke();
context.fill();
context.closePath();
前四行為起手式級別,上面解釋過犀斋,這里不再進行解釋贝乎,我們主要分析第五行到第七行代碼。
這里最難的是怎么找到這個點的位置(x,y)進行擺放叽粹,而繪制這個圓點方法如上如法炮制即可览效,已經(jīng)比較簡單了却舀。
而我們知道數(shù)學(xué)公式里面求圓上的一點的公式
到了JS的Math.cos和Math.sin函數(shù),我們查閱文檔可以知道這兩個函數(shù)中的傳入值都是指的“弧度”而非“角度”锤灿,弧度的計算公式為: 2*PI/360*角度挽拔;那么你會說這樣最高豈不是有2*Math.PI ? 還記得我們上面的圖嗎,最高也就是1.5*Math.PI,所以我們這里需要手動減去0.5*Math.PI 但校。因為我們在最上面的需求已經(jīng)可以得到弧度 :傳入進度 / 100 * 2 * Math.PI- 0.5 * Math.PI的公式螃诅,所以通過它,我們可以得到一個類似xx Math.PI的弧度值状囱。
如果要求算出(x1术裸,y1)在(x0,y0)為圓心的圓上亭枷,其所在坐標袭艺,我們可以使用如下的公式得到圓點的位置。
x1 = x0 + r * cos(弧度值)
y1 = y0 + r * sin(弧度值)
那么到此我們終于可以繪制出靜態(tài)的demo圖了叨粘,慢著匹表,你以為就結(jié)束了嗎,naive
UI大佬:"要不咱加個動效?"
好吧~向大佬勢力屈服
那么怎么讓它動起來呢宣鄙?如果你仔細觀察會發(fā)現(xiàn)我們剛剛實現(xiàn)的繪制過程,都是傳入最終參數(shù)percent來讓它完成繪制的默蚌,如果我每一次 precent+1冻晤,會有什么情況?不用你猜绸吸,對的鼻弧,他會一次一次的繪制進度+1,這時候我突然想到"判斷渲染的條件锦茁,動畫攘轩,每次+1",腦海里浮現(xiàn)出面試復(fù)習時經(jīng)常出現(xiàn)的那個巨長的API码俩,requestAnimationFrame度帮,不就是最適合這種場景么.requestAnimationFrame API
實際實現(xiàn)思路就是,初始percent傳0稿存,通過判斷是否達到percent笨篷,如果不是,percent+1繼續(xù)再遞歸調(diào)用,而且他不會產(chǎn)生類似setTimeInterval定時器這種影響頁面性能和事件隊列的副作用瓣履。
一段實現(xiàn)代碼
progressRender(context, length, R, r, percent,slogan){
? ? ? ? ?if(slogan!==0) {
? ? ? ? percent += 1;
? ? ? ? if (percent < this.props.progress) {
? ? ? ? ? ? ? requestAnimationFrame(()=> {
? ? ? ? ? ? ? this.progressRender(context, length, R, r, percent)
? ? ? ? })
? ? ?}
}
最終我們能得到如下率翅,ps:隨便找的一個轉(zhuǎn)gif的網(wǎng)站,感覺卡卡的袖迎,但是實際還好冕臭,比較流暢腺晾。
通過這個小需求,不僅有點鍛煉數(shù)學(xué)和邏輯能力辜贵,也有點考驗我們對需求應(yīng)變的機動準備悯蝉,如果需求增加了我們要怎么靈活的實現(xiàn)出來,總之念颈,繼續(xù)加油吧~