基於 HTML5 Canvas 的拓撲組件開發

在如今前端圈大行其道的 React 和 Vue 中,可複用的組件多是他們大受歡迎的緣由之一,html

在 HT 的產品中也有組件的概念,不過在 HT 中組件的開發是依託於 HTML5 Canvas 的技術去實現的,前端

也就是說若是你有過使用 Canvas 的開發經驗你就能夠來封裝本身的組件。html5

下面我以一個進度環爲例,來探究一下如何使用ht.js封裝出一個拓撲組件。canvas

效果圖

代碼實現

前置知識

自定義組件數組

除了HT預約義的組件類型外,用戶還能夠自定義擴展類型,自定義有兩種方式:瀏覽器

  • 直接將type值設置成繪製函數:function(g, rect, comp, data, view){}
  • 經過ht.Default.setCompType(name, funtion(g, rect, comp, data, view){})註冊組件類型,矢量type值設置成相應的註冊名

在這裏我選用第一種經過形如bash

ht.Default.setImage('circle-progress-bar', { width: 100, height: 100, comps: [ { type: function(g, rect, comp, data, view) { // ... } } ] });

這樣的方式完成組件的聲明,那麼 function(g, rect, comp, data, view) { }中的內容就是咱們接下來須要關注的了ide

準備工做

  1. 抽象並聲明出幾個 Coding 中須要的變量函數

    • 進度百分比 progressPercentage {百分比}
    • 圓環漸變色 linearOuter {顏色數組}
    • 內圓漸變色 linearInner {顏色數組}
    • 字體縮放比例 fontScale {數字}
    • 顯示原始值 showOrigin {布爾}
    • 進度條樣式 progressLineCap {線帽樣式}
  2. 變量的聲明和賦值了性能

    var x = rect.x;
    var y = rect.y;
    var rectWidth = rect.width;
    var rectHeight = rect.height;
    var width = rectWidth < rectHeight ? rectWidth : rectHeight;
    var progressPercentage = parseFloat((data.a('progressPercentage') * 100).toFixed(10));
    var fontScale = data.a('fontScale');
    var showOrigin = data.a('showOrigin');
    var backgroundColor = data.a('backgroundColor');
    var progressLineCap = data.a('progressLineCap');
    var fontSize = 16; // 字體大小
    var posX = x + rectWidth / 2; // 圓心 x 座標
    var posY = y + rectHeight / 2; // 圓心 y 座標
    var circleLineWidth = width / 10; // 圓環線寬
    var circleRadius = (width - circleLineWidth) / 2; // 圓環半徑
    var circleAngle = {sAngle: 0, eAngle: 2 * Math.PI}; // 繪製背景圓和圓環內圓所需的角度
    var proStartAngel = Math.PI; // 進度環起始角度
    var proEndAngel = proStartAngel + ((Math.PI * 2) / 100) * progressPercentage; // 進度環結束角度複製代碼
  3. 建立漸變色樣式

    var grd = context.createLinearGradient(x1, y1, x2, y2);
    grd.addColorStop(0, 'red');   
    grd.addColorStop(1, 'blue');複製代碼

    在 Canvas 中的漸變色是按照如上方式來建立的,可是在一個組件中去若是一個一個去添加顯然是去組件的理念是背道而馳的,因此我選擇封裝一個函數根據顏色數組中的各個顏色來生成漸變色樣式

    // 建立漸變色樣式函數
    function addCreateLinear(colorsArr) {
        var linear = rectWidth < rectHeight
            ? g.createLinearGradient(x, posY - width / 2, width, posY + width / 2)
            : g.createLinearGradient(posX - width / 2, y, posX + width / 2, width);
        var len = colorsArr.length;
        for (var key in colorsArr) {
            linear.addColorStop((+key + 1) / len, colorsArr[key]);
        }
        return linear;
    }
    // 建立漸變填充顏色
    var linearOuter = addCreateLinear(data.a('linearOuter'));
    var linearInner = addCreateLinear(data.a('linearInner'));複製代碼

開始 Coding

準備工做結束後下面就是 Canvas 的時間了

  1. 繪製背景圓

    g.beginPath();
    g.arc(posX, posY, circleRadius, circleAngle.sAngle, circleAngle.eAngle);
    g.closePath();
    g.fillStyle = backgroundColor;
    g.fill();
    g.lineWidth = circleLineWidth;
    g.strokeStyle = backgroundColor;
    g.stroke();複製代碼

  2. 繪製進度環

    g.beginPath();
    g.arc(posX, posY, circleRadius, proStartAngel, proEndAngel);
    g.strokeStyle = linearOuter;
    g.lineWidth = circleLineWidth;
    g.lineCap = progressLineCap;
    if (progressPercentage !== 0) g.stroke();複製代碼

  3. 繪製中心圓

    g.beginPath();
    g.fillStyle = linearInner;
    g.arc(posX, posY, circleRadius - circleLineWidth / 2 - 1, 0, Math.PI * 2, false);
    g.strokeStyle = '#0A2E44';
    g.fill();
    g.lineWidth = 2;
    g.stroke();複製代碼

  4. 繪製文字

    g.fillStyle = 'white';
    g.textAlign = 'center';
    g.font = fontSize + 'px Arial';
    g.translate(posX * (1 - fontScale), posY * (1 - fontScale));
    g.scale(fontScale, fontScale);
    showOrigin
        ? g.fillText(progressPercentage / 100, posX, posY + fontSize / 3)
        : g.fillText(progressPercentage + '%', posX, posY + fontSize / 3);複製代碼

    最後經過簡單的配置就能夠在網頁上呈現出這個進度環了

    var dataModel = new ht.DataModel();
    var graphView = new ht.graph.GraphView(dataModel);
    var circle1 = new ht.Node();
    circle1.setPosition(150, 150);
    circle1.setSize(200, 200);
    circle1.setImage('circle-progress-bar');
    circle1.a({
        progressPercentage: 0.48,
        linearOuter: ['#26a67b', '#0474d6'],
        linearInner: ['#004e92', '#000000'],
        fontScale: 1,
        showOrigin: true,
        progressLineCap: 'butt',
        backgroundColor: 'rgb(61,61,61)'
    });
    dataModel.add(circle1);
    // 此次多生成幾個 不過代碼類似 在此就不贅述了複製代碼

幾點心得

聲明屬性

在這個部分有幾點可供參考

  • 使用小駝峯對屬性進行命名,而且少用縮寫儘可能語義化

    舉個栗子:

    • fontScale 字體縮放比例
    • progressPercentage 進度百分比
  • 屬性值類型的選擇也要儘可能貼合屬性的含義

    舉個栗子:

    • 一個存儲着幾個顏色值字符串的數組,用顏色數組就比單純的數組更爲貼切
    • 一個表示畫筆線帽種類的字符串,用線帽樣式就比字符轉更爲貼切

使用屬性

因爲進度環是一個圓形的組件,那麼在這裏有兩點供參考

  • 當組件的 rect.widthrect.height 不相等的時候咱們須要本身來設定一個 width,

    讓圓在這個以 width 爲邊的正方形中繪製,而 width 的值就是 rect.widthrect.height 中較短的一邊,

    而這麼作的理由是這樣繪製圓自適應性能力會更好,而且圓心也直會在 (rect.width/2, rect.height/2)這一點上。

    var rectWidth = rect.width;
    var rectHeight = rect.height;
    var width = rectWidth < rectHeight ? rectWidth : rectHeight;複製代碼
  • 因爲咱們本身設定了一個 width,那麼在設置漸變顏色的參數上就須要注意一下了。

    當 rect.width 不等於 rect.height 的時候。

    若是按照 g.createLinearGradient(0, 0, rect.width, rect.height) 設置漸變色就會出現下面的效果,右下方的藍色不見了。

    不過若是按照以下代碼的方式設置漸變色就會出現下面的效果就會出現預期的效果了。

    var posX = rectWidth / 2;
      var posY = rectHeight / 2;
      var linear = rectWidth < rectHeight
              ? g.createLinearGradient(0, posY - width / 2, width, posY + width / 2)
              : g.createLinearGradient(posX - width / 2, 0, posX + width / 2, width);複製代碼

    緣由其實很簡單,就是漸變顏色方向的起點和終點並無隨着 width 的改變而改變。

    如圖所示以rectWidth > rectHeight 爲例

繪製組件

在繪製組件的過程當中,咱們須要把一些邊界條件和特殊狀況考慮到,來保持組件的擴展性和穩定性

下面就是一些個人心得

  • 在作了 g 操做的頭尾分別使用 saverestore ,以此來保障 g 操做的不影響後續的擴展開發。

    g.save()
    // g 操做
    // ...
    // ...
    g.restore()複製代碼

    save/restore

    設想一下,咱們正在用 10 像素寬,顏色爲紅色的筆畫圖,而後把畫筆設置成1像素寬,顏色變成綠色。綠色畫完以後呢,咱們想接着用10像素的紅色來畫,若是沒有 save 與 restore,那咱們就不得不從新設置一遍畫筆——若是畫筆狀態過多,那咱們的代碼就會大量增長;並且,這些設置過程是重複而乏味的。

    最後保存的最早還原!restore 老是還原離他最近的 save 點(已經還原的不能第2次還原到他)。

    另外 save 和 restore 通常是改變了 transform 或 clip 才須要,大部分狀況下不須要,例如你設置了顏色、寬度等等參數,下次要繪製這些的人會本身再設置這些,因此能儘可能不用 save/restore 的地方能夠儘可能不用,那也是有代價的

  • 當進度值爲 0 且 線帽樣式爲圓角的時候進度環會變成一個圓點,正確的作法使須要對進度值爲 0 的時候進行特殊處理。

    // 進度環
    g.beginPath();
    g.arc(posX, posY, circleRadius, proStartAngel, proEndAngel);
    g.strokeStyle = linearOuter;
    g.lineCap = progressLineCap;
    if (progressPercentage !== 0) g.stroke();複製代碼
  • 因爲 Chrome 瀏覽器的限制(Chrome 顯示最小字體爲 12 px),因此不能經過 12px這樣的數值設定文字大小,只能經過縮放來控制文字的大小了。

    當你高高興興的的使用 scale 對文字進行縮放的時候

    var fontScale = 0.75
    g.fillStyle = 'white';
    g.textAlign = 'center';
    g.font = fontSize + 'px Arial';
    g.scale(fontScale, fontScale);
    g.fillText(progressPercentage + '%', posX, posY + fontSize / 3);複製代碼

    你會獲得這樣的結果

    形成這個結果的緣由是 scale 操做的參考點位置不對

    下面咱們使用矩形的例子詳細解釋一下

    // 原矩形
      ctx.save();
      ctx.beginPath();
      ctx.strokeRect(0, 0, 400, 400);
      ctx.restore();
      // 縮放後的矩形
      ctx.save();
      ctx.beginPath();
      ctx.scale(0.75, 0.75);
      ctx.strokeRect(0, 0, 400, 400);
      ctx.restore();複製代碼

    這時 scale 的參考點是(0,0)因此,中心縮放沒有按照咱們預期的進行

    當修改參考點的座標爲(50,50)以後,中心縮放就正常了

    那麼這個(50,50)是怎麼得來的?

    根據上圖咱們不難看出這個距離其實就是 (縮放前的邊長 - 縮放後的邊長) / 2獲得得

    公式就是 width * (1 - scale) / 2

    在這個例子中套用一下就是 400 * (1 - 0.75) / 2 = 50

    // 原矩形
      ctx.save();
      ctx.beginPath();
      ctx.strokeRect(0, 0, 400, 400);
      ctx.restore();
      // 縮放後的矩形
      ctx.save();
      ctx.beginPath();
      ctx.translate(50, 50)
      ctx.scale(0.75, 0.75);
      ctx.strokeRect(0, 0, 400, 400);
      ctx.restore();複製代碼

    咱們把上面得公式在作進一步的擴展,讓它的適用性更強

    width * (1 - scale) / 2   -> width / 2 * (1 - scale)  -> posX * (1 - scale)
      height * (1 - scale) / 2  -> height / 2 * (1 - scale) -> posY * (1 - scale)複製代碼

    在這裏也須要明確一點 posX = x + (width / 2) posY = y + (height / 2)

    在進一步抽象成函數

    function centerScale(ctx, posX, posY, scaleX, scaleY) {
          ctx.translate(posX * (1 - scaleX), posY * (1 - scaleY));
          ctx.scale(scaleX, scaleY);
      }複製代碼

    那麼其中的文字縮放也是一模一樣

    var fontScale = 0.75
      g.fillStyle = 'white';
      g.textAlign = 'center';
      g.font = fontSize + 'px Arial';
      g.translate(posX * (1 - fontScale), posY * (1 - fontScale));
      g.scale(fontScale, fontScale);
      g.fillText(progressPercentage + '%', posX, posY + fontSize / 3);複製代碼

    固然結果也是很不錯的😉,文字的縮放功能實現了

    在實現上若是你們有什麼問題能夠直接留言或者私信或者直接去官網hightopo上查閱相關的資料

結語

這個進度環組件的開發就到此結束了,相信小夥伴們經過個人這篇學習筆記也是能夠經過ht.js獨立開發一個拓撲組件了。

相關文章
相關標籤/搜索