<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用初中數(shù)學(xué)知識(shí)擼一個(gè)canvas環(huán)形進(jìn)度條

          共 12088字,需瀏覽 25分鐘

           ·

          2021-04-06 10:48

          點(diǎn)擊上方藍(lán)字“前端司南”關(guān)注我
          您的關(guān)注意義重大

          原創(chuàng)@前端司南

          周末好,今天給大家?guī)硪豢罱拥貧獾沫h(huán)形進(jìn)度條組件vue-awesome-progress。近日被設(shè)計(jì)小姐姐要求實(shí)現(xiàn)這么一個(gè)環(huán)形進(jìn)度條效果,大體由四部分組成,分別是底色圓環(huán),進(jìn)度弧,環(huán)內(nèi)文字,進(jìn)度圓點(diǎn)。設(shè)計(jì)稿截圖如下:

          我的第一反應(yīng)還是找現(xiàn)成的組件,市面上很多組件都實(shí)現(xiàn)了前3點(diǎn),獨(dú)獨(dú)沒找到能畫進(jìn)度圓點(diǎn)的組件,不然稍加定制也能復(fù)用。既然沒有現(xiàn)成的組件,只有自己用vue + canvas擼一個(gè)了。

          效果圖

          先放個(gè)效果圖,然后再說下具體實(shí)現(xiàn)過程,各位看官且聽我慢慢道來。

          安裝與使用

          源碼地址[1],歡迎star和提issue。

          安裝

          npm install --save vue-awesome-progress

          使用

          全局注冊(cè)

          import Vue from 'vue'
          import VueAwesomeProgress from "vue-awesome-progress"
          Vue.use(VueAwesomeProgress)

          局部使用

          import VueAwesomeProgress from "vue-awesome-progress"

          export default {
              components: {
                  VueAwesomeProgress
              },
              // 其他代碼
          }

          script標(biāo)簽引入組件

          同時(shí)也支持直接使用script標(biāo)簽引入哦,滿足有這部分需求的朋友。

          <!DOCTYPE html>
          <html>
          <head>
            <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
            <script src="path-to/vue-awesome-progress.min.js"></script>
          </head>
          <body>
            <div id="app"></div>
            <script>
              new Vue({
                el"#app",
                template'<vue-awesome-progress :percentage="40"></vue-awesome-progress>'
              })
            
          </script>
          </body>
          </html>

          靜態(tài)展示

          任何事都不是一蹴而就的,我們首先來實(shí)現(xiàn)一個(gè)靜態(tài)的效果,然后再實(shí)現(xiàn)動(dòng)畫效果,甚至是復(fù)雜的控制邏輯。

          確定畫布大小

          第一步是確定畫布大小。從設(shè)計(jì)稿我們可以直觀地看到,整個(gè)環(huán)形進(jìn)度條的最外圍是由進(jìn)度圓點(diǎn)確定的,而進(jìn)度圓點(diǎn)的圓心在圓環(huán)圓周上。

          因此我們得出偽代碼如下:

          // canvasSize: canvas寬度/高度
          // outerRadius: 外圍半徑
          // pointRadius: 圓點(diǎn)半徑
          // pointRadius: 圓環(huán)半徑
          canvasSize = 2 * outerRadius = 2 * (pointRadius + circleRadius)

          據(jù)此我們可以定義如下組件屬性:

          props: {
            circleRadius: {
              typeNumber,
              default40
            },
            pointRadius: {
              typeNumber,
              default6
            }
          },
          computed: {
            // 外圍半徑
            outerRadius() {
              return this.circleRadius + this.pointRadius
            },
            // canvas寬/高
            canvasSize() {
              return 2 * this.outerRadius + 'px'
            }
          }

          那么canvas大小也可以先進(jìn)行綁定了

          <template>
              <canvas ref="canvasDemo" :width="canvasSize" :height="canvasSize" />
          </template>

          獲取繪圖上下文

          getContext('2d')方法返回一個(gè)用于在canvas上繪圖的環(huán)境,支持一系列2d繪圖API

          mounted() {
            // 在 $nextTick初始化畫布,不然dom還未渲染好
            this.$nextTick(() => {
              this.initCanvas()
            })
          },
          methods: {
            initCanvas() {
              var canvas = this.$refs.canvasDemo;
              var ctx = canvas.getContext('2d');
            }
          }

          畫底色圓環(huán)

          完成了上述步驟后,我們就可以著手畫各個(gè)元素了。我們先畫圓環(huán),這時(shí)我們還要定義兩個(gè)屬性,分別是圓環(huán)線寬circleWidth和圓環(huán)顏色circleColor。

          circleWidth: {
            typeNumber,
            default2
          },
          circleColor: {
            typeString,
            default'#3B77E3'
          }

          canvas提供的畫圓弧的方法是ctx.arc(),需要提供圓心坐標(biāo),半徑,起止弧度,是否逆時(shí)針等參數(shù)。

          ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

          我們知道,Web網(wǎng)頁中的坐標(biāo)系是這樣的,從絕對(duì)定位的設(shè)置上其實(shí)就能看出來(top,left設(shè)置正負(fù)值會(huì)發(fā)生什么變化),而且原點(diǎn)(0, 0)是在盒子(比如說canvas)的左上角哦。

          對(duì)于角度而言,x軸正向,默認(rèn)是順時(shí)針方向旋轉(zhuǎn)。

          圓環(huán)的圓心就是canvas的中心,所以x, youterRadius的值就可以了。

          ctx.strokeStyle = this.circleColor;
          ctx.lineWidth = this.circleWidth;
          ctx.beginPath();
          ctx.arc(this.outerRadius, this.outerRadius, this.circleRadius, 0this.deg2Arc(360));
          ctx.stroke();

          注意arc傳的是弧度參數(shù),而不是我們常理解的360°這種概念,因此我們需要將我們理解的360°轉(zhuǎn)為弧度。

          // deg轉(zhuǎn)弧度
          deg2Arc(deg) {
            return deg / 180 * Math.PI
          }

          畫文字

          調(diào)用fillText繪制文字,利用canvas.clientWidth / 2canvas.clientWidth / 2取得中點(diǎn)坐標(biāo),結(jié)合控制文字對(duì)齊的兩個(gè)屬性textAligntextBaseline,我們可以將文字繪制在畫布中央。文字的值由label屬性接收,字體大小由fontSize屬性接收,顏色則取的fontColor。

          if (this.label) {
            ctx.font = `${this.fontSize}px Arial,"Microsoft YaHei"`
            ctx.fillStyle = this.fontColor;
            ctx.textAlign = 'center'
            ctx.textBaseline = 'middle'
            ctx.fillText(this.label, canvas.clientWidth / 2, canvas.clientWidth / 2);
          }

          畫進(jìn)度弧

          支持普通顏色和漸變色,withGradient默認(rèn)為true,代表使用漸變色繪制進(jìn)度弧,漸變方向我默認(rèn)給的從上到下。如果希望使用普通顏色,withGradientfalse即可,并可以通過lineColor自定義顏色。

          if (this.withGradient) {
            this.gradient = ctx.createLinearGradient(this.circleRadius, 0this.circleRadius, this.circleRadius * 2);
            this.lineColorStops.forEach(item => {
              this.gradient.addColorStop(item.percent, item.color);
            });
          }

          其中lineColorStops是漸變色的顏色偏移斷點(diǎn),由父組件傳入,可傳入任意個(gè)顏色斷點(diǎn),格式如下:

          colorStops2: [
            { percent0color'#FF9933' },
            { percent1color'#FF4949' }
          ]

          畫一條從上到下的進(jìn)度弧,即270°90°

          ctx.strokeStyle = this.withGradient ? this.gradient : this.lineColor;
          ctx.lineWidth = this.lineWidth;
          ctx.beginPath();
          ctx.arc(this.outerRadius, this.outerRadius, this.circleRadius, this.deg2Arc(270), this.deg2Arc(90));
          ctx.stroke();

          其中lineWidth是弧線的寬度,由父組件傳入

          lineWidth: {
            typeNumber,
            default8
          }

          畫進(jìn)度圓點(diǎn)

          最后我們需要把進(jìn)度圓點(diǎn)補(bǔ)上,我們先寫死一個(gè)角度90°,顯而易見,圓點(diǎn)坐標(biāo)為(this.outerRadius, this.outerRadius + this.circleRadius)

          畫圓點(diǎn)的代碼如下:

          ctx.fillStyle = this.pointColor;
          ctx.beginPath();
          ctx.arc(this.outerRadius, this.outerRadius + this.circleRadius, this.pointRadius, 0this.deg2Arc(360));
          ctx.fill();

          其中pointRadius是圓點(diǎn)的半徑,由父組件傳入:

          pointRadius: {
            typeNumber,
            default6
          }

          角度自定義

          當(dāng)然,進(jìn)度條的角度是靈活定義的,包括開始角度,結(jié)束角度,都應(yīng)該由調(diào)用者隨意給出。因此我們?cè)俣x一個(gè)屬性angleRange,用于接收起止角度。

          angleRange: {
            typeArray,
            defaultfunction({
              return [27090]
            }
          }

          有了這個(gè)屬性,我們就可以隨意地畫進(jìn)度弧和圓點(diǎn)了,哈哈哈哈。

          老哥,這種圓點(diǎn)坐標(biāo)怎么求?

          噗......看來高興過早了,最重要的是根據(jù)不同角度求得圓點(diǎn)的圓心坐標(biāo),這讓我頓時(shí)犯了難。

          經(jīng)過冷靜思考,我腦子里閃過了一個(gè)利用正余弦公式求坐標(biāo)的思路,但前提是坐標(biāo)系原點(diǎn)如果在圓環(huán)外接矩形的左上角才好算。仔細(xì)想想,冇問題啦,我先給坐標(biāo)系平移一下,最后求出來結(jié)果,再補(bǔ)個(gè)平移差值不就行了嘛。

          畫圖工具不是很熟練,這里圖沒畫好,線歪了,請(qǐng)忽略細(xì)節(jié)。

          好的,我們先給坐標(biāo)系向右下方平移pointRadius,最后求得結(jié)果再加上pointRadius就好了。偽代碼如下:

          // realx:真實(shí)的x坐標(biāo)
          // realy:真實(shí)的y坐標(biāo)
          // resultx:平移后求取的x坐標(biāo)
          // resultx:平移后求取的y坐標(biāo)
          // pointRadius 圓點(diǎn)半徑
          realx = resultx + pointRadius
          realy = resulty + pointRadius

          求解坐標(biāo)的思路大概如下,分四個(gè)范圍判斷,得出求解公式,應(yīng)該還可以化簡,不過我數(shù)學(xué)太菜了,先這樣吧。

          getPositionsByDeg(deg) {
              let x = 0;
              let y = 0;
              if (deg >= 0 && deg <= 90) {
                  // 0~90度
                  x = this.circleRadius * (1 + Math.cos(this.deg2Arc(deg)))
                  y = this.circleRadius * (1 + Math.sin(this.deg2Arc(deg)))
              } else if (deg > 90 && deg <= 180) {
                  // 90~180度
                  x = this.circleRadius * (1 - Math.cos(this.deg2Arc(180 - deg)))
                  y = this.circleRadius * (1 + Math.sin(this.deg2Arc(180 - deg)))
              } else if (deg > 180 && deg <= 270) {
                  // 180~270度
                  x = this.circleRadius * (1 - Math.sin(this.deg2Arc(270 - deg)))
                  y = this.circleRadius * (1 - Math.cos(this.deg2Arc(270 - deg)))
              } else {
                  // 270~360度
                  x = this.circleRadius * (1 + Math.cos(this.deg2Arc(360 - deg)))
                  y = this.circleRadius * (1 - Math.sin(this.deg2Arc(360 - deg)))
              }
              return { x, y }
          }

          最后再補(bǔ)上偏移值即可。

          const pointPosition = this.getPositionsByDeg(nextDeg);
          ctx.arc(pointPosition.x + this.pointRadius, pointPosition.y + this.pointRadius, this.pointRadius, 0this.deg2Arc(360));

          這樣,一個(gè)基本的canvas環(huán)形進(jìn)度條就成型了。

          動(dòng)畫展示

          靜態(tài)的東西逼格自然是不夠的,因此我們需要再搞點(diǎn)動(dòng)畫效果裝裝逼。

          基礎(chǔ)動(dòng)畫

          我們先簡單實(shí)現(xiàn)一個(gè)線性的動(dòng)畫效果?;舅悸肥前验_始角度和結(jié)束角度的差值分為N段,利用window.requestAnimationFrame依次執(zhí)行動(dòng)畫。

          比如從30°90°,我給它分為6段,每次畫10°。要注意canvas畫這種動(dòng)畫過程一般是要重復(fù)地清空畫布并重繪的,所以第一次我畫的弧線范圍就是30°~40°,第二次我畫的弧線范圍就是30°~50°,以此類推......

          基本的代碼結(jié)構(gòu)如下,具體代碼請(qǐng)參考vue-awesome-progress[2] v1.1.0版本,如果順手幫忙點(diǎn)個(gè)star也是極好的。

          animateDrawArc(canvas, ctx, startDeg, endDeg, nextDeg, step) {
            window.requestAnimationFrame(() => {
              // 清空畫布
              ctx.clearRect(00, canvas.clientWidth, canvas.clientHeight);
              // 求下一個(gè)目標(biāo)角度
              nextDeg = this.getTargetDeg(nextDeg || startDeg, endDeg, step);
              // 畫圓環(huán)
              // 畫文字
              // 畫進(jìn)度弧線
              // 畫進(jìn)度圓點(diǎn)
              if (nextDeg !== endDeg) {
                // 滿足條件繼續(xù)調(diào)用動(dòng)畫,否則結(jié)束動(dòng)畫
                this.animateDrawArc(canvas, ctx, startDeg, endDeg, nextDeg, step)
              }
            }
          }

          緩動(dòng)效果

          線性動(dòng)畫顯得有點(diǎn)單調(diào),可操作性不大,因此我考慮引入貝塞爾緩動(dòng)函數(shù)easing,并且支持傳入動(dòng)畫執(zhí)行時(shí)間周期duration,增強(qiáng)了可定制性,使用體驗(yàn)更好。這里不列出實(shí)現(xiàn)代碼了,請(qǐng)前往vue-awesome-progress[2]查看。

          <vue-awesome-progress label="188人" :duration="10" easing="0,0,1,1" />

          <vue-awesome-progress
            label="36℃"
            circle-color="#FF4949"
            :line-color-stops="colorStops"
            :angle-range="[60, 180]"
            :duration="5"
          />


          // 省略部分...

          <vue-awesome-progress label="188人" easing="1,0.28,0.17,0.53" :duration="10" />

          <vue-awesome-progress
            label="36℃"
            circle-color="#FF4949"
            :line-color-stops="colorStops"
            :angle-range="[60, 180]"
            :duration="5"
            easing="0.17,0.67,0.83,0.67"
          />

          可以看到,當(dāng)傳入不同的動(dòng)畫周期duration和緩動(dòng)參數(shù)easing時(shí),動(dòng)畫效果各異,完全取決于使用者自己。

          其他效果

          當(dāng)然根據(jù)組件支持的屬性,我們也可以定制出其他效果,比如不顯示文字,不顯示圓點(diǎn),弧線線寬與圓環(huán)線寬一樣,不使用漸變色,不需要?jiǎng)赢嫞鹊?。我們后續(xù)也會(huì)考慮支持更多能力,比如控制進(jìn)度,數(shù)字動(dòng)態(tài)增長等!具體使用方法,請(qǐng)參考vue-awesome-progress[2]。

          更新日志

          2020年04月10日更新

          支持進(jìn)度控制,只需要修改組件的屬性值percentage即可。



          2019年11月10日更新

          由于我從業(yè)務(wù)場(chǎng)景出發(fā)做了這個(gè)組件,沒有考慮到大部分場(chǎng)景都是傳百分比控制進(jìn)度的,因此在v1.4.0版本做了如下修正:

          1. 廢棄angle-range,改用percentage控制進(jìn)度,同時(shí)提供start-deg屬性控制起始角度;

          2. with-gradient改為use-gradient

          3. 通過show-text控制是否顯示進(jìn)度文字

          4. 支持通過format函數(shù)自定義顯示文字的規(guī)則



          2021年04月04日更新

          1. 支持自動(dòng)檢測(cè)DPR,解決高清屏模糊等問題

          結(jié)語

          寫完這個(gè)組件有讓我感覺到,程序員最終不是輸給了代碼和技術(shù)的快速迭代,而是輸給了自己的邏輯思維能力和數(shù)學(xué)功底。就vue-awesome-progress[2]這個(gè)組件而言,根據(jù)這個(gè)思路,我們也能迅速開發(fā)出適用于React,Angular以及其他框架生態(tài)下的組件。工作三年有余,接觸了不少框架和技術(shù),經(jīng)歷了MVVM,Hybrid,小程序,跨平臺(tái)大前端,serverless的大火,也時(shí)常感慨“學(xué)不動(dòng)了”,在這個(gè)快速演進(jìn)的代碼世界里常常感到失落。好在自己還沒有丟掉分析問題的能力,而不僅僅是調(diào)用各種API和插件,這可能是程序員最寶貴的財(cái)富吧。前路坎坷,我輩當(dāng)不忘初心,愿你出走半生,歸來仍是少年!

          參考

          [1]

          源碼地址: https://github.com/cumt-robin/vue-awesome-progress

          [2]

          vue-awesome-progress: https://github.com/cumt-robin/vue-awesome-progress


          END



          如果覺得這篇文章還不錯(cuò)
          點(diǎn)擊下面卡片關(guān)注我
          來個(gè)【分享、點(diǎn)贊、在看】三連支持一下吧



             “分享、點(diǎn)贊、在看” 支持一波  

          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲AV无码无线在线观看护士 | 大香蕉网欧美 | 国产免费高清视频 | PANS私拍在线一区二区 | 欧美成人一区二区三区四区 |