天天看點

canvas 配合 zrender 繪制橢圓布局

前言

最近碰到個用 canvas 繪制橢圓形的問題,研究了一下,發現挺有意思的。

在經過一頓摸索以後,實作的效果,也還是挺不錯的,在這個過程中也學到了不少東西吧。

ellipse 方法相容性問題

之前沒繪制過橢圓,上手,當然是想找找 canvas 原生有沒有類似的 api 了。

于是就上 mdn 搜尋一番,這一找,還真的發現有類似的 api,不得不感概,這就太巧了,這問題不就不算問題了麼。

但是一看浏覽器相容性,不禁讓人愁緒頓生,這麼好的方法,怎麼很多浏覽器都不支援呢?

canvas 配合 zrender 繪制橢圓布局

于是上網搜搜有沒有相容性的方法,這一搜,發現還真有。

其實方法挺多的,比如用貝塞爾曲線模拟,用圓模拟,甚至還有用光栅法模拟的。有興趣的同學可以去自行了解學習一下,我這裡就貼一種簡單的用圓模拟的方式吧:

// 對于不支援橢圓繪制的浏覽器進行功能拓展
  if (CanvasRenderingContext2D.prototype.ellipse === undefined) {
    CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {
      this.save();
      this.translate(x, y);
      this.rotate(rotation);
      this.scale(radiusX, radiusY);
      this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
      this.restore();
    };
  }
           

其實原理很簡單,就是用 scale 對圓進行壓縮,實作橢圓的效果。

根據圓心角計算橢圓上每個點的位置

如果我們想計算圓上對應圓心角的坐标,那麼很簡單,用下面這個公式就行了:

canvas 配合 zrender 繪制橢圓布局

對應的代碼可以寫成下面這樣:

const twoPI = Math.PI * 2;

// n 為想要分的份數
Array(...Array(n)).forEach((value, index) => {
  let angle = ( twoPI * index) / n;
  
  // center 為中心點坐标位置,r 為半徑
  let x = center.x + r * Math.cos(angle);	
  let y = center.y + r * Math.sin(angle);
});
           

但是如果是橢圓呢?

橢圓也是有計算公式的,如下:

canvas 配合 zrender 繪制橢圓布局

對應的代碼可以寫成下面這樣:

const twoPI = Math.PI * 2;

// n 為想要分的份數
Array(...Array(n)).forEach((value, index) => {
  let angle = ( twoPI * index) / n;
  
  // center 為中心點坐标位置,a 為長軸半徑,b 為短軸半徑
  let x = center.x + a * Math.cos(angle);	
  let y = center.y + b * Math.sin(angle);
});
           

用 zrender 開始繪制

這裡之是以用 zrender 繪制,純粹是由于自己從零開始寫太過于麻煩了,zrender 本身就是一款不錯的 canvas 2d 開源架構,封裝了一些功能,能夠幫助我們快捷的進行 canvas 繪制。

如果還不了解 zrender 的童鞋,可以去 zrender 官網了解一下:https://ecomfe.github.io/zrender-doc/public/

結合上面的算法,我簡單地封裝了一個繪制的方法:

/**
 * 建立圓形布局的算法
 * @paramas {Object} center 中心點坐标
 * @paramas {Number} a 長半軸的長度
 * @paramas {Number} b 短半軸的長度
 * @paramas {Number} n 網元個數
 */
function createEllipse(center, a, b, n) {
  const tP = 2 * Math.PI;
  Array(...Array(n)).forEach((value, index) => {
    const angle = (tP * index) / n;
    const x = center.x + a * Math.cos(angle);
    const y = center.y + b * Math.sin(angle);

    var circle = new zrender.Circle({
      shape: {
        cx: x,
        cy: y,
        r: 20
      },
      style: {
        fill: "#0eafd1"
      },
      z: 1
    });
    zr.add(circle);
  });
}
           

現在我們調用一下,看一下結果

const a = 300;
const b = 150;
const o = { x: 400, y: 200 };

createEllipse(o, a, b, 10);
           

結果如下:

canvas 配合 zrender 繪制橢圓布局

哈哈,效果還是不錯的吧。

嗯,不錯,此教程到此結束!

等等,我們的弧線還沒有繪制上來呢!

不好意思!差點忘記了本來的目的,汗顔!

接下來,我們就來加上弧線的效果。

加上曲線連接配接效果

zrender 裡面是由直接提供 ellipse 的圖形的,我看打開 zrender 源碼看看:

canvas 配合 zrender 繪制橢圓布局

可以看出,代碼很簡潔。

接下來,我們稍微改改我們的代碼,在 createEllipse 方法末尾加上繪制橢圓的邏輯:

var ellipse = new zrender.Ellipse({
shape: {
  cx: center.x,
  cy: center.y,
  rx: a,
  ry: b,
  // r: 20
},
style: {
  fill: "#0eafd100",
  stroke: "red"
},
});
zr.add(ellipse);
           

然後效果變成這樣了:

canvas 配合 zrender 繪制橢圓布局

說實話,這種效果并不是我想要的效果,我想要的是一段段可以自由加進去的,那麼隻有自己動手添加了。

一段段添加

那麼該怎麼改呢?首先可以分析下,既然我們拓展了浏覽器端繪制橢圓的方法,那麼直接采用繪制橢圓的方法繪制進去就行了。

但是問題是,我們怎麼拿到 canvas 畫筆,繪制到 canvas 裡面去呢?

1. 建立新的 Ellipse2 對象

我們可以照着 zrender.Ellipse 的代碼,建立一個新的圖形,就叫 Ellips2 吧:

zrender.Ellipse2 = zrender.Path.extend({
  type: "ellipse2",

  shape: {
    cx: 0,
    cy: 0,
    rx: 0,
    ry: 0,
    fAngle: 0,
    tAngle: Math.PI*2
  },

  buildPath: function(ctx, shape) {
    var x = shape.cx;
    var y = shape.cy;
    var a = shape.rx;
    var b = shape.ry;
    var fAngle = shape.fAngle;
    var tAngle = shape.tAngle;

    ctx._ctx.ellipse(x, y, a, b, 0, fAngle, tAngle);

  }
});
           

可以看到,我們在 shape對象上新增了 fAngle 和 tAngle 屬性,用來控制起始和終止的角度。

2. 調用 zrender.Ellipse2 開始逐段繪制

添加下面的代碼,調用 zrender.Ellipse2 方法,繪制我們的弧線

const collections = [];
Array(...Array(n)).forEach((value, index) => {
  
	......
  
	collections.push(angle);
  if(index!==0){
    var ellipse = new zrender.Ellipse2({
      shape: {
        cx: center.x,
        cy: center.y,
        rx: a,
        ry: b,
        fAngle: collections[index-1],
        tAngle: angle
      },
      style: {
        fill: "#0eafd100",
        stroke: "red"
      }
    });
    zr.add(ellipse);
  }
  if(index === n - 1){
    var ellipse = new zrender.Ellipse2({
      shape: {
        cx: center.x,
        cy: center.y,
        rx: a,
        ry: b,
        fAngle: angle,
        tAngle: collections[0]
      },
      style: {
        fill: "#0eafd100",
        stroke: "red"
      }
    });
    zr.add(ellipse);
  }

	......
});
           

繪制效果:

canvas 配合 zrender 繪制橢圓布局

咋一看,好像跟之前的繪制方式相比,沒啥優勢啊。

但是你稍微控制下繪制參數,就可以改變下效果,比如這樣:

canvas 配合 zrender 繪制橢圓布局

或者這樣:

canvas 配合 zrender 繪制橢圓布局

後記

總而言之,用 canvas 繪制橢圓還是挺有意思的吧,我所言非虛吧。

小夥伴們,趕快學習一波吧。

繼續閱讀