说图形模块化之前,先回顾下我们之前画的图形,那是一个多边形,虽然没有闭合,但这不重要。
接下来,咱们就将这个图形封装为一个类对象 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:[
ctx.moveTo(50,50);
ctx.lineTo(450,50);
ctx.lineTo(250,200);
]
});
poly.draw(ctx);
效果:
为三角形添加划入划出效果:
let hover=false;
canvas.addEventListener('mousemove',mousemoveFn);
function mousemoveFn(event){
const mousePos=getMousePos(event,canvas);
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 画布的点位就无法再对图形做出正确选择。
对于这个问题存在的原因和解决方式,咱们下章详解:图形选择-物质不易
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。