说图形模块化之前,先回顾下我们之前画的图形,那是一个多边形,虽然没有闭合,但这不重要。
接下来,咱们就将这个图形封装为一个类对象 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 画布的点位就无法再对图形做出正确选择。
对于这个问题存在的原因和解决方式,咱们下章详解:图形选择-物质不易
源码地址