乐趣区

图形选择图形模块化

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

接下来,咱们就将这个图形封装为一个类对象 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 画布的点位就无法再对图形做出正确选择。
对于这个问题存在的原因和解决方式,咱们下章详解:图形选择 - 物质不易

源码地址

退出移动版