Canvas之绘制图形后续
Canvas 提供了一些线条 API ,它可以设置 ctx.lineCap 线条的端点,ctx.lineJoin 线条的折线连接处,ctx.miterLimit 控制线条折线的尖角长短。
js
ctx.beginPath();
ctx.lineWidth = 10;
ctx.strokeStyle = 'red';
ctx.lineCap ="butt"; //平的
ctx.moveTo(50, 50)
ctx.lineTo(300, 50);
ctx.stroke();
ctx.beginPath();
ctx.lineCap="round" //突出圆
ctx.moveTo(50, 100)
ctx.lineTo(300, 100);
ctx.stroke();
ctx.beginPath();
ctx.lineCap="square" //突出平
ctx.moveTo(50, 150)
ctx.lineTo(300, 150);
ctx.stroke();
js
ctx.beginPath();
ctx.lineWidth = 10;
ctx.strokeStyle = 'red';
ctx.lineJoin = "miter";//尖的
ctx.moveTo(50, 50);
ctx.lineTo(150, 150);
ctx.lineTo(250, 50);
ctx.stroke();
ctx.beginPath();
ctx.lineJoin = "round";//圆的
ctx.moveTo(50, 100);
ctx.lineTo(150, 200);
ctx.lineTo(250, 100);
ctx.stroke();
ctx.beginPath();
ctx.lineJoin = "bevel";//平的
ctx.moveTo(50, 150);
ctx.lineTo(150, 250);
ctx.lineTo(250, 150);
ctx.stroke();
js
ctx.beginPath();
ctx.lineWidth = 20;
ctx.strokeStyle = 'red';
ctx.lineJoin = "miter";
ctx.miterLimit = 2;
ctx.moveTo(50, 50);
ctx.lineTo(70, 100);
ctx.lineTo(100, 50);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 40;
ctx.miterLimit = 2;
ctx.moveTo(150, 50);
ctx.lineTo(170, 100);
ctx.lineTo(200, 50);
ctx.stroke();
ctx.miterLimit 会根据 ctx.lineWidth 来设置相应的比例进行长短变化。不同宽度设置同一个 ctx.miterLimit 长短不一致。
js
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.lineTo(200, 100);
ctx.setLineDash([200])
ctx.lineDashOffset = 200;
// ctx.lineDashOffset = -50;
ctx.lineWidth = 10;
ctx.strokeStyle = 'blue';
ctx.stroke();
function move() {
ctx.lineDashOffset -= 1;
console.log(ctx.lineDashOffset);
ctx.stroke();
if (ctx.lineDashOffset > -200) {
requestAnimationFrame(move);
}
}
requestAnimationFrame(move);
同样可以设置虚线,由于需要知道虚线实线长和空白长,需要一个数组,前面表示实线长,后面表示空白长。如果只设置一个数据,表示实线和空白长度相同。如果设置更多数据,表示重复循环设置。最好设置成偶数个数,这样就是一组。
同时,也可以设置偏移量。负数向右,正数向左偏移。
js
ctx.beginPath();
ctx.lineWidth=10;
ctx.moveTo(0, 50);
ctx.lineTo(400, 50);
ctx.stroke();
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ctx.beginPath();
ctx.lineWidth=10;
ctx.moveTo(50, 0);
ctx.lineTo(50, 400);
ctx.stroke();
在之前的虚线移动动画中,并不是真的移动,而是我们在不断进行重新描边,所以之前的线条依然被保留。只是因为线条在不断加长,所以没有看到问题。当让线条继续向右移动时,本该出现的空白就会被原来的线条挡住。
可以通过设置清除画布的区域来进行处理。如果设置的区域是整个画布,那么整个画布会被清除。注意,要使用 ctx.beginPath() ,如果不使用,清除画布只是设置了透明度,会保留原来的路径,在绘制时会全部画出。
js
ctx.setLineDash([200])
ctx.lineDashOffset = 200;
ctx.lineWidth = 10;
ctx.strokeStyle = 'blue';
function move() {
ctx.clearRect(50, 95, 200, 10);
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.lineTo(200, 100);
ctx.lineDashOffset -= 1;
ctx.stroke();
if (ctx.lineDashOffset === -200) {
ctx.lineDashOffset = 200;
}
requestAnimationFrame(move);
}
requestAnimationFrame(move);
我们可以实现简单的动画。在每一次动画前清除动画,设置 ctx.beginPath() ,然后不断递归,每次使得偏移量减一。当偏移量达到最低时,重新设置偏移量。
js
ctx.beginPath();
ctx.lineWidth = 10;
ctx.moveTo(50, 50);
ctx.lineTo(50, 200);
ctx.lineTo(200, 200)
// ctx.lineTo(50, 50)
ctx.closePath();
ctx.stroke();
ctx.fill()
线条形成区域设置填充效果会将相关区域填充。但这时会出现最后端点闭合的样式问题,因为最后的端点并没有相连。所以想要实现自动闭合 ctx.closePath() 会解决这个问题。
而且不管是一条线段还是已经闭合的区域,自动闭合都会有效果,只是效果不太明显。
js
ctx.beginPath();
ctx.arc(100,100,50,0,Math.PI * 2) ;
ctx.stroke();
ctx.beginPath();
ctx.arc(300,100,50,0,Math.PI,true);
ctx.stroke();
ctx.beginPath();
ctx.arc(100,300,50,Math.PI/2,Math.PI,true);
ctx.stroke();
js
ctx.beginPath();
ctx.moveTo(100,100);
ctx.lineTo(100,300);
ctx.lineTo(300,300);
ctx.stroke();
ctx.beginPath();
ctx.arc(200,200,100,0,Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 4 ;
ctx.strokeStyle = '#00f';
ctx.moveTo(100,200);
ctx.arcTo(100,300,200,300,100);
ctx.stroke();
可以使用 ctx.arc( x , y , r , startAngle , endAngle ,[ dir ]),需要知道圆点,半径,起始点,结束点,绘制方向可选。起始点都是顺时针方向的,从x轴右侧作为起始点。false 默认为顺时针,true 为逆时针。
还可以使用 ctx.arcTo( x1 , y1 , x2 , y2 , r )实际由三个点相切,第一个点为 moveTo 的点或者是上一次绘制的结果,剩下的点为自己设置。会形成一个有夹角的线条,保证一段和线条相切的圆弧。
js
ctx.beginPath();
ctx.ellipse(100, 100, 100, 50, 0, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(300, 100, 100, 50, 0, 0, Math.PI / 2);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(100, 300, 100, 50, Math.PI / 4, 0, Math.PI * 2);
ctx.stroke();
除了可以设置圆弧,还可以设置椭圆。ctx.ellipse( x , y , rx , ry , rotate , startAngle , endAngle , dir ) 需要增加 x 轴,y 轴,x 轴旋转角度,旋转角度默认为顺时针。其他和之前设置圆弧的方式相同。
js
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.arc(100, 300, 5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(200, 100, 5, 0, Math.PI * 2)
ctx.fill();
ctx.beginPath()
ctx.arc(300, 300, 5, 0, Math.PI * 2)
ctx.fill();
ctx.beginPath();
ctx.moveTo(100, 300);
ctx.quadraticCurveTo(200, 100, 300, 300)
ctx.stroke();
js
ctx.beginPath();
ctx.moveTo(100, 300);
ctx.bezierCurveTo(200, 100, 250, 350, 300, 300)
ctx.stroke();
为了更好地设置曲线,可以采用贝塞尔曲线来进行绘制。除了起始点和结束点,还需要设置一些控制点。一个控制点叫做二次贝塞尔曲线,两个控制点叫做三次贝塞尔曲线。
设置一个参数 t ,数值为从0到1,不断找符合参数比例的点连线,直到找到最后一个点,然后这个点就是曲线的切点,这个点所在的直线叫做切线。这样随着参数 t 不断变化,就会得到一条最终的曲线。
前面是二次贝塞尔曲线,ctx.quadraticCurveTo( cx1 , cy1 , ex , ey),参数为控制点和结束点坐标,起始点坐标为前面给出的坐标。后面是三次贝塞尔曲线,bezierCurveTo(cx1,cy1 , cx2 , cy2 , ex , ey) 多了一个控制点坐标。
可以看到,贝塞尔曲线最多只能达到中间的位置,不会离控制点太过靠近。
js
ctx.beginPath();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = 'bold italic 50px sans-serif'
ctx.fillText('你好', 100, 100)
ctx.beginPath();
ctx.font = 'bold italic 50px sans-serif'
ctx.strokeText('你好', 100, 200)
ctx.beginPath();
ctx.fillStyle = '#f00'
ctx.arc(100, 100, 5, 0, Math.PI * 2)
ctx.fill();
ctx.beginPath();
ctx.fillStyle = '#f00'
ctx.arc(100, 200, 5, 0, Math.PI * 2)
ctx.fill();
同样字体也可以被绘制出来,使用填充或者描边的形式绘制。ctx.fillText(textStr , x , y [, maxWidth])分别是绘制的文本,坐标和最大的宽度。如果超过了最大宽度,就会整体压缩到最大宽度。
还需要在绘制之前设置字体样式,记住一定要在最后设置字体,否则样式无法显示。
使用 ctx.textAlign 和 ctx.textBaseline 分别设置水平位置和垂直位置。center 表示水平居中,middle 表示垂直居中。