canvas mdn_canvas 配合 zrender 绘制椭圆布局

前言

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

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

ellipse 方法兼容性问题

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

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

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

5b8a552baab12b4d2bc22447095a8787.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 对圆进行压缩,实现椭圆的效果。

根据圆心角计算椭圆上每个点的位置

如果我们想计算圆上对应圆心角的坐标,那么很简单,用下面这个公式就行了:

63f5b75926a73c18c44e5c61d5f2d625.png

对应的代码可以写成下面这样:

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);
});

但是如果是椭圆呢?

椭圆也是有计算公式的,如下:

62a9353030f34597cce33d024329c106.png

对应的代码可以写成下面这样:

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);

结果如下:

9d5db9f9d5ceca4efa48c048876be614.png

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

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

......

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

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

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

加上曲线连接效果

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

e28e283b6b16beb7f77b51cf9cb8c789.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);

然后效果变成这样了:

53d1caaf0ac57dee0e0c5f409966d72a.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);
  }

    ......
});

绘制效果:

85dce9ae089bd83d2f2079a729ff3893.png

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

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

97a7b482cd6d07eb8d520425b87cbf20.png

或者这样:

3aa80306b9d6db1f1de41fbcbd46a71b.png

后记

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值