Canvas绘图学习笔记:画笔的路径与状态

beginPath

beginPath表示开始一个路径,我们在上一章画弧的时候用到过好多次,他的API非常简单:

context.beginPath();

开始路径有2层意思,一个就是本次绘制的起点是新的(不再是上次结束的点了),另外一个意思就是绘制的样式也是新的(不再与之前的样式有关联)。对于第一条上一个章节我们已经见识过了,如果不开始一个新的路径那么描边弧线的时候就会有一条上次绘制结束到弧线开始时的连线(当然moveTo可以避免,但需要计算,不方便)。
现在考虑这么一个需求,我们需要画3条线,线的颜色分别是红绿蓝,使用之前的知识,你应该可以的,你先试一下?或许你写的代码是这样的:

context.moveTo(10, 50);
context.lineTo(290, 50);
context.strokeStyle='#FF0000';
context.stroke();

context.moveTo(10, 75);
context.lineTo(290, 75);
context.strokeStyle='#00FF00';
context.stroke();

context.moveTo(10, 100);
context.lineTo(290, 100);
context.strokeStyle='#0000FF';
context.stroke();

效果如下:

颜色出现错误

我们发现颜色是错误的,但是又不清楚哪里有问题了。难道是它的API有bug了吗?我们打断点,然后单步执行,看看上面三个stroke依次执行后的效果:

红色执行后


 

绿色执行后

蓝色执行后

我们可以看到,当代码执行到红色以后是对的(虽然把坐标系也变成红色了);然后绿色执行后把绿色这条渲染对了,但是又用绿色渲染了一下红色的那条线,使得红色的线变成2者的叠加色了;当绿色的执行完了以后,把最后一条线描边成绿色,但是又把前面的也渲染了一遍,所以最终的颜色就是我们之前看到的。要让新的线不在绘制之前的就用beginPath来开启一个新的路径。看看我们使用后的效果:

context.beginPath();
context.moveTo(10, 50);
context.lineTo(290, 50);
context.strokeStyle='#FF0000';
context.stroke();

context.beginPath();
context.moveTo(10, 75);
context.lineTo(290, 75);
context.strokeStyle='#00FF00';
context.stroke();

context.beginPath();
context.moveTo(10, 100);
context.lineTo(290, 100);
context.strokeStyle='#0000FF';
context.stroke();

效果如下:

正常渲染

总结一下:使用beginPath路径将不再与之前的联系,绘制时也不再绘制之前的(所以已绘制图案的样式不再叠加)。

closePath

closePath是闭合路径,注意是闭合路径而不是结束路径,虽然目前的位置是在beginPath后面,但是两者没什么关系,它并不是endPath(没有这个)。

现在有需求,需要描边一个45°的扇形,你以你现在的技术完全可以胜任,大笔一挥:

context.beginPath();
context.moveTo(150, 75);
context.arc(150, 75, 80, Math.PI / 180 * 0, Math.PI / 180 * 45);
context.lineTo(150, 75);
context.stroke();

结果如下: 

描边扇形

效果不错,挺满意的。现在我们观察倒数第二行代码,我们使用context.lineTo(150, 75);画了一条回到圆心(起点)的线。在stroke的时候回到起点可以绘制出一个闭合的图形,这种操作实在太多了,为了简化这个步骤,我们就可以使用closePath。现在直接把context.lineTo(150, 75);替换为context.closePath();你会发现效果是一样的,这样就省去了自己计算起点位置的步骤了。我强烈建议在闭合路径的时候使用closePath
需要顺便提醒一下,填充(fill)的时候,对于一个终点和起点没有闭合的路径,默认会闭合了再去填充(不然没得玩了),如下。当然如果还有其他没有闭合的时候(就比如平行的2个线段),那么就真的没的完了,他也“不会”绘制了。

context.beginPath();
context.moveTo(150, 75);
context.arc(150, 75, 80, Math.PI / 180 * 0, Math.PI / 180 * 45);
context.fill();

上面没有闭合,直接填充,结果和闭合了以后是一样的效果:

填充扇形

点是否在路径内部

跟路径有关的一个常见问题,就是需要判断点是否在一个路径的内部。canvas考虑到大家的这个需要,给了大家提供了这样的API:

// 坐标(x, y)是否在路径内部 如果在就返回true否则就返回false
context.isPointInPath(x, y);

这里需要注意的有三点:

  1. 如果一个路径结束和开始的位置没有闭合,判断的时候会按照闭合来处理(如果结束点和开始点闭合后整个路径还没有闭合,那么就返回false)。
  2. strokeRectfillRect不会保留绘制的矩形路径,所以isPointInPath不能对他们进行判断,可以使用rect代替。
  3. 如果刚刚在路径所处的直线上,那么需要根据线宽来决定,如果路径内与线中心一侧的时候那么返回false,其他的时候返回true,举个例子比如线宽是1,那么如果在线上,说明是内部;如果线宽是3,那么在内部和前2个像素上是内部,外面的一个像素是外部。

看了第三条你可能又会问那么就只想知道是否在线上怎么办,那就可能会用到另一个API了:

// 坐标(x, y)是否在描边上 如果在就返回true否则就返回false
context.isPointInStroke(x, y);

此时你可能还会问,你只想知道是否在路径的内部,根本不关心在不在描边上,那么怎么办?给你提醒一下,把这两个API综合起来判断就可以了,相信你一定可以做到的。此外这两个API比较简单就不再给出例子了,感兴趣的同学可以自己研究下。

裁剪区域

路径学完了我们先额外插播一个小知识,就是裁剪区域,先看个例子,我们先描边一个圆形,再填充一个矩形:

context.beginPath();
context.arc(150, 75, 40, Math.PI / 180 * 0, Math.PI / 180 * 360);
context.stroke();

// 开始新的路径 与之前的不再有关系 如果不开始 下面的fill的时候会把上面圆也fill了
context.beginPath();
context.rect(150, 75, 40, 40);
context.fill();

结果如下: 

描边圆,填充矩形

然后我们按照圆的样子裁剪矩形,稍微修改一下代码:

context.beginPath();
context.arc(150, 75, 40, Math.PI / 180 * 0, Math.PI / 180 * 360);
context.stroke();

// 按照圆裁剪
context.clip();

context.beginPath();
context.rect(150, 75, 40, 40);
context.fill();

 结果如下:

按照圆裁剪矩形

这里需要注意的是裁剪也是基于路径来的,所以strokeRectfillRect是不生效的。
我们再画一个矩形:

context.beginPath();
context.arc(150, 75, 40, Math.PI / 180 * 0, Math.PI / 180 * 360);
context.stroke();

// 按照圆裁剪
context.clip();

context.beginPath();
context.rect(150, 75, 40, 40);
context.fill();

// 再画一个矩形
context.beginPath();
context.rect(190, 35, 80, 80);
context.fill();

结果: 

再画一个矩形

什么放错图了?没错,就是这个样子!我们分析一下,上面画了一个圆,然后描边了,然后按照圆裁剪,那么下面画的第一个矩形会按照圆来裁剪,没问题。然后画了第二个矩形,那么问题来了,这个矩形也被裁剪了!那么怎么让第二个矩形不再裁剪呢?如果后面的一直都被裁剪,那么每裁剪一次就缩小一点点距离,那多痛苦。

状态的保存于恢复

接下来就是我们的处理办法了,如果裁剪前把当前状态保存了,然后裁剪完第一个矩形后,再把状态恢复了,不是很好的解决了这个问题吗?canvas也是这么做的:

context.beginPath();
context.arc(150, 75, 40, Math.PI / 180 * 0, Math.PI / 180 * 360);
context.stroke();

// 保存状态
context.save();

context.clip();

context.beginPath();
context.rect(150, 75, 40, 40);
context.fill();

// 恢复之前保存的状态,即没有裁剪时那个状态
context.restore();

context.beginPath();
context.rect(190, 35, 80, 80);
context.fill();

结果:

恢复状态

通常裁剪前一般都会保存路径的,裁剪完后,一般都会恢复的。除此之外保存与恢复也可以用在某些样式状态上,还可以用在形变(后面会讲到的,类似与CSS3的transform)的状态保存上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Canvas红包雨是一种基于HTML5 Canvas技术开发的节日效果,通过JavaScript控制红包的下落和动画效果,实现一个红包雨的动态效果。 下面是Canvas红包雨的开发流程: 1. HTML页面中添加Canvas画布,并设置画布的宽高和样式。 ```html <canvas id="canvas"></canvas> ``` ```css #canvas { width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 999; } ``` 2. 在JavaScript中获取Canvas画布对象,并设置画布的宽高。 ```javascript var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; ``` 3. 定义红包对象,包括红包的位置、速度、大小和颜色等属性。 ```javascript function RedPacket(x, y, vx, vy, size, color) { this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.size = size; this.color = color; } ``` 4. 在Canvas画布上绘制红包,并实现红包的下落和动画效果。 ```javascript var redPackets = []; // 存储红包对象的数组 function drawRedPacket(redPacket) { ctx.beginPath(); ctx.arc(redPacket.x, redPacket.y, redPacket.size, 0, 2 * Math.PI); ctx.fillStyle = redPacket.color; ctx.fill(); } function updateRedPacket(redPacket) { redPacket.x += redPacket.vx; redPacket.y += redPacket.vy; redPacket.vy += gravity; } function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (var i = 0; i < redPackets.length; i++) { drawRedPacket(redPackets[i]); updateRedPacket(redPackets[i]); } requestAnimationFrame(animate); } requestAnimationFrame(animate); ``` 5. 实现红包的自动生成和动态效果,可以通过setInterval或setTimeout定时调用生成红包的函数。 ```javascript function createRedPacket() { var x = Math.random() * canvas.width; var y = -20; var vx = Math.random() * 6 - 3; var vy = Math.random() * 2 + 2; var size = Math.random() * 10 + 10; var color = '#' + Math.floor(Math.random() * 16777215).toString(16); // 随机生成颜色 var redPacket = new RedPacket(x, y, vx, vy, size, color); redPackets.push(redPacket); } setInterval(createRedPacket, 500); // 每500ms生成一个红包 ``` 6. 实现红包的点击事件,当用户点击红包时,可以实现红包爆炸效果,并显示红包金额或祝福语。 ```javascript function explodeRedPacket(redPacket) { var particles = []; // 存储爆炸粒子的数组 for (var i = 0; i < 20; i++) { var particle = { x: redPacket.x, y: redPacket.y, vx: Math.random() * 6 - 3, vy: Math.random() * 6 - 3, size: Math.random() * 3 + 1, color: redPacket.color }; particles.push(particle); } for (var i = 0; i < particles.length; i++) { drawRedPacket(particles[i]); } } canvas.addEventListener('click', function(event) { var x = event.clientX; var y = event.clientY; for (var i = 0; i < redPackets.length; i++) { var redPacket = redPackets[i]; if (x > redPacket.x - redPacket.size && x < redPacket.x + redPacket.size && y > redPacket.y - redPacket.size && y < redPacket.y + redPacket.size) { explodeRedPacket(redPacket); redPackets.splice(i, 1); break; } } }); ``` 上述就是Canvas红包雨的开发流程,通过Canvas技术和JavaScript实现红包的下落、动画、自动生成和点击事件等效果,可以增加网站的趣味性和用户互动性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1024小神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值