canvas 配合 zrender 绘制椭圆布局

前言

最近碰到个用 canvas 绘制椭圆形的问题,研究了一下,发现挺有意思的。

在经过一顿摸索以后,实现的效果,也还是挺不错的,在这个过程中也学到了不少东西吧。

ellipse 方法兼容性问题

之前没绘制过椭圆,上手,当然是想找找 canvas 原生有没有类似的 api 了。

于是就上 mdn 搜索一番,这一找,还真的发现有类似的 api,不得不感概,这就太巧了,这问题不就不算问题了么。

但是一看浏览器兼容性,不禁让人愁绪顿生,这么好的方法,怎么很多浏览器都不支持呢?

image.png

于是上网搜搜有没有兼容性的方法,这一搜,发现还真有。

其实方法挺多的,比如用贝塞尔曲线模拟,用圆模拟,甚至还有用光栅法模拟的。有兴趣的同学可以去自行了解学习一下,我这里就贴一种简单的用圆模拟的方式吧:

 // 对于不支持椭圆绘制的浏览器进行功能拓展
  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);

结果如下:
image.png

哈哈,效果还是不错的吧。

嗯,不错,此教程到此结束!

等等,我们的弧线还没有绘制上来呢!

不好意思!差点忘记了本来的目的,汗颜!

接下来,我们就来加上弧线的效果。

加上曲线连接效果

zrender 里面是由直接提供 ellipse 的图形的,我看打开 zrender 源码看看:
image.png

可以看出,代码很简洁。

接下来,我们稍微改改我们的代码,在 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);

然后效果变成这样了:
image.png

说实话,这种效果并不是我想要的效果,我想要的是一段段可以自由加进去的,那么只有自己动手添加了。

一段段添加

那么该怎么改呢?首先可以分析下,既然我们拓展了浏览器端绘制椭圆的方法,那么直接采用绘制椭圆的方法绘制进去就行了。

但是问题是,我们怎么拿到 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);
  }

	......
});

绘制效果:
image.png

咋一看,好像跟之前的绘制方式相比,没啥优势啊。

但是你稍微控制下绘制参数,就可以改变下效果,比如这样:
image.png

或者这样:
image.png

后记

总而言之,用 canvas 绘制椭圆还是挺有意思的吧,我所言非虚吧。

小伙伴们,赶快学习一波吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值