canvas錐形漸變進度條

謝大帥哥發表於2020-05-28

從一個漸變圓角進度條淺出畫一個圓

開始

這一切需要從一個(簡單)的需求開始,在最開始對設計第一眼看到這張圖的時候,感覺挺簡單的嘛,直接用echarts餅圖模擬出來一個就好了

image

echarts

然後上echarts試了一下發現實現不出來了

image

設計圖這邊採用的是錐形漸變,而echarts只有線性漸變和徑向漸變。

css

然後準備換種方案,css就有錐形漸變,然後通過conic-gradient加上mask畫出了一個漸變的環形然後可以再通過剪裁實現出進度的展示。

但是存在兩個問題,一個是conic-gradient屬性相容性不好ie和火狐都不支援,二個是後來發現了還存在一個需求進度條的兩端需要有圓角,然後這種實現方式就不行了。

其實在寫這篇文章的時候才想到一個方法就是在兩端加上兩個半圓形,不過得計算半圓形的位置。

Canvas & SVG

在我的理解中在頁面上作圖總共有四種方式。

  • dom+css
  • Canvas
  • SVG
  • WebGL

WebGL是一頭霧水還是試試Canvas和SVG吧,因為更熟悉Canvas一些,我這邊就採用Canvas來試試。

Canvas可以輕鬆的實現圓角和環形,但是他的api裡面居然沒有錐形漸變

然後就想著嘗試手動來實現一個錐形漸變,然後查閱資料看到了一篇文章手把手教你畫圓錐漸變,就是相當於畫圓嘛,我們可以通過一條線一條線的畫從而畫出一個圓,然後把兩端漸變的顏色通過計算找到中間畫圓的每一條線的顏色組合起來就是一個漸變的效果了。

然後問題就是給你兩個色值怎麼計算中間的線段的顏色,其實對於rgba的顏色我們可以看到他是由四個數字組成的,那我把這四個數字分別求出四組長度相同且組內間隔相同的中間值那就可以得到顏色的中間值了,然後在搭配上張老師硬核的色值轉換JS HEX十六進位制與RGB,HSL顏色的相互轉換那就可以實現出我們想要的效果了。

通過一個開始顏色和一個結束顏色,預設是rgba的顏色,num是分段數,就可以求出中間每一段的顏色了

  // 把顏色分段
  const beginColor = begin.slice(5, -1).split(',').map(item => Number(item))
  const endColor = end.slice(5, -1).split(',').map(item => Number(item))
  // 分段後的顏色儲存在這個陣列
  const middleColor = [[], [], [], []]
  // 迴圈rgba四種
  beginColor.forEach((item, index) => {
    // 當前的值每段顏色之間的間隔
    const differ = (endColor[index] - item) / (num - 1)
    // 迴圈分段數的次數
    for(let i = 0; i< num; i++) {
      // 每次加上這個間隔
      middleColor[index].push((item + differ * i).toFixed(2))
    }
  })
  console.log(middleColor)
  console.log(num)

然後繪製的話就是一段一段的畫了,麻煩的地方就是計算每次從多少的角度畫到多少的角度

  for(let i = 0; i< num; i++) {
    ctx.beginPath()
    // 這裡是每次繪製的過程
    // 每次繪製一段小圓弧
    // 最後一段只需要畫一段就好    
    if(i === num - 1) 
      ctx.arc( 150 * dpr,150 * dpr, 100 * dpr, (Math.PI * 2 * value) / num * i, (Math.PI * 2 * value) / num * (i + 1));
    else
      ctx.arc( 150 * dpr,150 * dpr, 100 * dpr, (Math.PI * 2 * value) / num * i, (Math.PI * 2 * value) / num * (i + 2));
    ctx.lineWidth = 60;
    // ctx.lineCap = "round";
    ctx.strokeStyle= `rgba(${middleColor[0][i]}, ${middleColor[1][i]}, ${middleColor[2][i]}, ${middleColor[3][i]})`;
    ctx.stroke();   
    ctx.closePath() 
  }

結果非常的順利,就是自己想要的結果,具體怎麼一段一段的畫,怎麼求出顏色的中間值看這裡

優化

其實把繪製的過程放慢看

image

就是這個過程,每次畫一段,每段不同的顏色組合起來就是一個漸變色,然後分段數再加多一點就會靠上去很流暢。

在完成後發現了幾個問題,首先是在分段數很少的時候就會出現一塊一塊的間隔

image

就像這樣,我大概分析了一下,猜測這個間隔出現的原因應該是我計算每一段的角度的時候肯定有除不盡的,我就四捨五入了,應該就會產生一些小間隔。

然後我就覺得把分段數提高應該就會好一些,然後就發現分段數高間隔會生成類似於摩爾紋的東西

image

然後就開始思考怎麼去消除,最後想到了一種方案就是在每次繪製的時候繪製兩段的長度,然後移動只移動一段的長度,就會下一段覆蓋在上一段,就不會有間隔了,然後顏色漸變也還是一段一段的不會有影響。

image

然後還有一個問題就是有鋸齒,不清楚,解決方案也很簡單,就是把你的畫布放大指定倍數,然後半徑也放大同樣的倍數,最後dom的高寬不變,就會讓繪製的圖形更加的清晰。

總結

到此這個問題就算是解決了,然後我還順便寫了一個庫,大家有興趣的可以去使用一下,我還加上了數字,動畫也可以支援多段漸變gradient-ring-progress

通過這次的需求我收穫到了,做東西需要完全的去了解了需求再去確定實現方案然後再動手,實現方案其實有非常多種,我們需要找到的是最合適的解決方案。最後抄襲張老師的一句話

然而一個人的積累總是有限,而創意總是無限的,因此一定還有其他更好更妙更簡單的實現,歡迎分享歡迎指教!

參考資料

CSS conic-gradient()錐形漸變簡介

手把手教你畫圓錐漸變

JS HEX十六進位制與RGB,HSL顏色的相互轉換

相關文章