说图形模块化之前,先回顾下我们之前画的图形,那是一个多边形,虽然没有闭合,但这不重要。

接下来,咱们就将这个图形封装为一个类对象 Poly
Poly 对象是对路径的封装,那我们就可以从两方面来考虑:图形、样式。
Poly 对象是对路径的封装,我们可以从两方面来考虑:

  • 图形:路径可以绘制的所有图形,可以是一个图形,也可以是多个图形,只要都在一个路径集合里就行;
  • 样式:路径该有的所有样式了。

接下来我们看一下Poly 对象的默认属性:

const defAttr={    crtPath:crtLinePath,    vertices:[],    close:false,    fill:false,    stroke:false,    shadow:false,    fillStyle:'#000',    strokeStyle:'#000',    lineWidth:1,    lineDash:[],    lineDashOffset:0,    lineCap:'butt',    lineJoin:'miter',    miterLimit:10,    shadowColor:'rgba(0,0,0,0)',    shadowBlur:0,    shadowOffsetX:0,    shadowOffsetY:0,    position:new Vector2(0,0),    rotation:0,    scale:new Vector2(1,1),};

详细解释一下这些属性:
crtPath 是建立路径的方法,默认是给了一个绘制多边形的方法,此方法也可以被覆盖。

/*绘制多边形*/function crtLinePath(ctx){    const {vertices}=this;    /*连点成线*/    ctx.beginPath();    ctx.moveTo(vertices[0].x,vertices[0].y);    const len=vertices.length;    for(let i=1;i<len;i++){        ctx.lineTo(vertices[i].x,vertices[i].y);    }}

vertices 是多边形的顶点集合。
对于其它图形的相关属性,我没有写,以后需要了可以再去扩展,比如arc 的圆心位、半径、起始弧度、结束弧度等等。

绘图方法相关的相关的属性:

  • close:否闭合路径
  • fill:否以填充方式绘制图形
  • strtoke:否以描边方式绘制图形
  • shadow:否给图像添加投影

样式相关的属性:fillStyle 填充样式、strokeStyle 描边样式…… 这些样式名称和canvas 里的样式是一样的,我就不消多说了。

变换相关属性:和canvas 里的变换是一样的,分别是位移、旋转、缩放。

Poly 的方法:

  • draw(ctx) :绘图方法
  • checkPointInPath(ctx,{x,y}):检测点位是否在路径中

整体代码:

import Vector2 from "./Vector2.js";/*多边形默认属性*/const defAttr={    crtPath:crtLinePath,    vertices:[],    close:false,    fill:false,    stroke:false,    shadow:false,    fillStyle:'#000',    strokeStyle:'#000',    lineWidth:1,    lineDash:[],    lineDashOffset:0,    lineCap:'butt',    lineJoin:'miter',    miterLimit:10,    shadowColor:'rgba(0,0,0,0)',    shadowBlur:0,    shadowOffsetX:0,    shadowOffsetY:0,    scale:new Vector2(1,1),    position:new Vector2(0,0),    rotation:0,};/*绘制多边形*/function crtLinePath(ctx){    const {vertices}=this;    /*连点成线*/    ctx.beginPath();    ctx.moveTo(vertices[0].x,vertices[0].y);    const len=vertices.length;    for(let i=1;i<len;i++){        ctx.lineTo(vertices[i].x,vertices[i].y);    }}/*Poly 多边形*/export default class Poly{    constructor(param={}){        Object.assign(this,defAttr,param);    }    draw(ctx){        const {            shadow, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY,            stroke, close, strokeStyle, lineWidth, lineCap, lineJoin, miterLimit,lineDash,lineDashOffset,            fill, fillStyle,            scale,position,rotation        }=this;        ctx.save();        /*投影*/        if(shadow){            ctx.shadowColor=shadowColor;            ctx.shadowBlur=shadowBlur;            ctx.shadowOffsetX=shadowOffsetX;            ctx.shadowOffsetY=shadowOffsetY;        }        /*变换*/        ctx.translate(position.x,position.y);        ctx.rotate(rotation);        ctx.scale(scale.x,scale.y);        /*建立路径*/        this.crtPath(ctx);        /*描边*/        if(stroke){            ctx.strokeStyle=strokeStyle;            ctx.lineWidth=lineWidth;            ctx.lineCap=lineCap;            ctx.lineJoin=lineJoin;            ctx.miterLimit=miterLimit;            ctx.lineDashOffset=lineDashOffset;            ctx.setLineDash(lineDash);            close&&ctx.closePath();            ctx.stroke();        }        /*填充*/        if(fill){            ctx.fillStyle=fillStyle;            ctx.fill();        }        ctx.restore();    }    checkPointInPath(ctx,{x,y}){        this.crtPath(ctx);        const bool=ctx.isPointInPath(x,y);    }}

注意:Poly 对象中,我对其点位的定义使用了一个Vector2 对象。
Vector2 是一个二维向量对象,它存储了基本的x、y 位置信息,封装了点位的运算方法,比如加、减、乘、除等。
这里我先不对Vector2 对象做详细解释,我们先知道它表示一个{x,y} 点位即可,后面我们用到了它的哪个功能,再解释哪个功能。

Poly 对象建立完成后,咱们就画个三角形试试。
实例化Poly 对象:

const poly=new Poly({    stroke:true,    close:true,    vertices:[        new Vector2(50,50),        new Vector2(450,50),        new Vector2(250,250),    ]});poly.draw(ctx);

效果:

为三角形添加划入划出效果:

let hover=false;canvas.addEventListener('mousemove',mousemoveFn);function mousemoveFn(event){    const mousePos=getMousePos(event);    poly.crtPath(ctx);    const bool=ctx.isPointInPath(mousePos.x,mousePos.y);    if(hover!==bool){        poly.fill=bool;        ctx.clearRect(0,0,canvas.width,canvas.height);        poly.draw(ctx);        hover=bool;    }}

鼠标选择逻辑:
1.在事件外声明hover 变量,存储鼠标划入划出状态。
2.用canvas监听鼠标移动事件,获取鼠标在canvas 中的位置
3.使用poly.crtPath(ctx) 方法建立路径
4.使用isPointInPath() 判断鼠标点位是否在路径中
5.鼠标的选择状态发生了改变,让图形的填充样式也做相应的改变,并绘图。

鼠标划入效果:

把svg 多边形画到canvas 里

用Poly 对象,我们还可以基于svg 里的polygon 数据绘图并选择。
接下来我用Poly 对象画一下那座酷似大象的山。
在svg 加载成功后,提取svg 里的polygon的顶点,然后将其放到Poly 的实例对象的vertices 集合里。

window.onload = function() {    const dom = embed.getSVGDocument();    const mount = dom.querySelector('#mount');    backImg = dom.querySelector('#back');    ctx.drawImage(backImg,0,0);    poly.vertices=parsePoints(mount);    poly.draw(ctx);    /*鼠标移动*/    canvas.addEventListener('mousemove',mousemoveFn);};

parsePoints(mount) 解析的就是下面polygon 标签的points 属性

<polygon id="mount" fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#080102" stroke-miterlimit="10" points="   211.7,260.8 234.6,236.6 241.2,190.3 245.6,165.2 255.7,145.4 309.5,95.2 358.4,74.9 381.7,115.9 388.8,130.4 385.7,137.9   398,174.5 406.4,176.2 433.3,205.3 443.8,236.6 468.9,263 288.8,264.8 294.5,239.2 276,243.6 265.9,262.6 "/>

parsePoints(mount) 函数:

function parsePoints(dom){    const points=[];    let pointsAttr=dom.getAttribute('points').split(' ');    for(let ele of pointsAttr){        if(ele){            const arr=ele.split(',');            const [x,y]=[                Math.round(arr[0]),                Math.round(arr[1]),            ];            points.push(new Vector2(x,y));        }    }    return points;}

页面效果:

绘制其它图形

重写Poly 对象的crtPath()方法,我们还可以绘制除多边形之外的其它图形,比如用两个三次贝塞尔画一颗爱心:

const poly=new Poly({    position:new Vector2(300,400),    stroke:true,    close:true,    crtPath:function(ctx){        ctx.beginPath();        ctx.moveTo(0,0);        ctx.bezierCurveTo(-200,-50,-180,-300,0,-200);        ctx.bezierCurveTo(180,-300,200,-50,0,0);    }});poly.draw(ctx);

这里其实还存在了一个问题,那就是图形通过变换属性发生了位移、旋转或缩放后,使用鼠标相对于canvas 画布的点位就无法再对图形做出正确选择。
对于这个问题存在的原因和解决方式,咱们下章详解:图形选择-物质不易

源码地址