前言
最近碰到個用 canvas 繪制橢圓形的問題,研究了一下,發現挺有意思的。
在經過一頓摸索以後,實作的效果,也還是挺不錯的,在這個過程中也學到了不少東西吧。
ellipse 方法相容性問題
之前沒繪制過橢圓,上手,當然是想找找 canvas 原生有沒有類似的 api 了。
于是就上 mdn 搜尋一番,這一找,還真的發現有類似的 api,不得不感概,這就太巧了,這問題不就不算問題了麼。
但是一看浏覽器相容性,不禁讓人愁緒頓生,這麼好的方法,怎麼很多浏覽器都不支援呢?

于是上網搜搜有沒有相容性的方法,這一搜,發現還真有。
其實方法挺多的,比如用貝塞爾曲線模拟,用圓模拟,甚至還有用光栅法模拟的。有興趣的同學可以去自行了解學習一下,我這裡就貼一種簡單的用圓模拟的方式吧:
// 對于不支援橢圓繪制的浏覽器進行功能拓展
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 對圓進行壓縮,實作橢圓的效果。
根據圓心角計算橢圓上每個點的位置
如果我們想計算圓上對應圓心角的坐标,那麼很簡單,用下面這個公式就行了:
對應的代碼可以寫成下面這樣:
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);
});
但是如果是橢圓呢?
橢圓也是有計算公式的,如下:
對應的代碼可以寫成下面這樣:
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);
結果如下:
哈哈,效果還是不錯的吧。
嗯,不錯,此教程到此結束!
…
等等,我們的弧線還沒有繪制上來呢!
不好意思!差點忘記了本來的目的,汗顔!
接下來,我們就來加上弧線的效果。
加上曲線連接配接效果
zrender 裡面是由直接提供 ellipse 的圖形的,我看打開 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 畫筆,繪制到 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 繪制橢圓還是挺有意思的吧,我所言非虛吧。
小夥伴們,趕快學習一波吧。