利用Canvas画出曲线运动的气泡
前言
最近在写一个HTML项目,里面需要实现一个效果:当鼠标移动到人物上时,冒出一些做曲线运动的气泡(静态效果见下,完整项目请浏览https://zdjzpg.github.io/html5/),一开始确实没什么思路,在网上搜集了下资料,能差不多的写出来了,所以写个博客记录下,也希望能够帮助到其他人。

一、canvas的准备
1)html上添加canvas标签
1 <canvas id="test" width="200" height="400"></canvas>
2)给页面添加点样式,让canvas居中显示
body{ background-color: pink; } #test{ position: absolute; top: 0; left: 0; bottom: 0; right: 0; margin: auto; border: 1px solid; } canvas{ background-color: white; }
3)js初步获取canvas
var canvas=document.getElementById('test') if(canvas.getContext){ var ctx=canvas.getContext('2d')//获取'画笔' }
接下来就需要考虑怎么做呢?做曲线运动的气泡顾名思义其实不就是两件事,1.生成随机的气泡.2.让每个气泡做曲线运动
二、怎么随机的产生气泡呢
气泡其实就是一个个圆,canvas画圆的api是什么呢?正是ctx.arc(x,y,r,startDeg,endDeg),x,y表示圆心位置,r表示半径,startDeg,endDeg表示初始角度和结束角度,因为画的形状是固定的,所以startDeg,endDeg也是固定的。所以我们只要随机生成x,y,r,再不断的把他画到画布上,不就实现了产生随机的气泡,但是x和y的范围是什么呢?我们可以观察到,气泡一开始是出现在最底部,所以x的范围不就是0-canvas的宽吗,y的位置需要根据半径r的大小不断调整让他处于canvas底部。说了这么多,让我们开始动手吧,
var r=Math.random()*10+3 var x=Math.random()*canvas.clientWidth var y=canvas.clientHeight-r
这个只生成了一个气泡,很多的气泡要怎么办呢?我们可以设置定时器,定时产生一个气泡,并把他存到数组里,然后通过另一个定时器不断的取出数组里的数据作为画圆的参数画出来,如下:
1 var arr=[] 2 setInterval(function(){ 3 for(var i=0;i<arr.length;i++){ 4 ctx.save() 5 ctx.arc(arr[i].x,arr[i].y,arr[i].r,0,2*Math.PI)//取出参数作为圆的参数 6 ctx.fill() 7 ctx.beginPath() 8 ctx.restore() 9 } 10 },1000/60) 11 setInterval(function(){ 12 var r=Math.random()*10+3 13 var x=Math.random()*canvas.clientWidth 14 var y=canvas.clientHeight-r 15 arr.push({ 16 r,x,y//将参数推入数组 17 }) 18 },5000/60)
此时可以观察到圆的初始位置已经满足了

但是发现颜色是固定的,所以我们还需要给气泡随机颜色,如下
1 var arr=[] 2 setInterval(function(){ 3 for(var i=0;i<arr.length;i++){ 4 ctx.save() 5 ctx.fillStyle='rgba('+arr[i].red+","+arr[i].green+","+arr[i].blue+","+arr[i].alp+')'//为画笔添加颜色 6 ctx.arc(arr[i].x,arr[i].y,arr[i].r,0,2*Math.PI) 7 ctx.fill() 8 ctx.beginPath() 9 ctx.restore() 10 } 11 },1000/60) 12 setInterval(function(){ 13 var red=Math.round(Math.random()*255)//随机生成颜色 14 var blue=Math.round(Math.random()*255) 15 var green=Math.round(Math.random()*255) 16 var alp=1 17 var r=Math.random()*10+3 18 var x=Math.random()*canvas.clientWidth 19 var y=canvas.clientHeight-r 20 arr.push({ 21 red,blue,green,alp,r,x,y 22 }) 23 },5000/60)
到了现在,随机的气泡不管是初始位置或者颜色都可以满足要求了(效果如下),要开始进行下一步了

三、让气泡进行曲线运动
说起曲线运动你们先想到的是什么呢?哈哈,我先想到的是正弦余弦。忘了吗?贴张正弦图帮你回忆下

想到什么了吗?把他旋转90度不就是从下往上波浪向上移动着,不正好满足我们的要求吗?(当然其他曲线也都可以,看你的气泡要做啥样的运动)
(sin(deg),deg)确定一个处于竖向曲线运动的气泡的一个位置,deg不断增加,气泡不就慢慢往上冒吗?因为曲线运动的初始位置每个气泡都不一样,所以我们应该把这些信息都放到数组里,同时取出如下:
1 setInterval(function(){ 2 for(var i=0;i<arr.length;i++){ 3 arr[i].deg+=5 4 arr[i].x=arr[i].startX+Math.sin(arr[i].deg*Math.PI/180)//初始位置+sin(弧度) 5 arr[i].y=arr[i].startY-(arr[i].deg*Math.PI/180)//初始位置+弧度 6 } 7 for(var i=0;i<arr.length;i++){ 8 ctx.save() 9 ctx.fillStyle='rgba('+arr[i].red+","+arr[i].green+","+arr[i].blue+","+arr[i].alp+')' 10 ctx.arc(arr[i].x,arr[i].y,arr[i].r,0,2*Math.PI) 11 ctx.fill() 12 ctx.beginPath() 13 ctx.restore() 14 } 15 },1000/60) 16 setInterval(function(){ 17 var red=Math.round(Math.random()*255) 18 var blue=Math.round(Math.random()*255) 19 var green=Math.round(Math.random()*255) 20 var alp=1 21 var r=Math.random()*10+3 22 var x=Math.random()*canvas.clientWidth 23 var y=canvas.clientHeight-r 24 var deg=0 25 var startX=x 26 var startY=y 27 arr.push({ 28 red,blue,green,alp,r,x,y,step,startX,startY,deg 29 }) 30 },5000/60)
这样就可以了吗?我们可以看下效果

可以看到虽然有曲线运动,但是幅度非常的小,效果很差,所以我们给他‘放大’下,x,y同乘一个数step,但是每个气泡的step不应该一样,这样所有的气泡就会以差不多的曲线向上移动,效果也不佳,所以这里给他生成随机数step,并给x,y乘以这个随机数。并且我们可以发现,这个曲线并不是我们想要的效果,我们想要的是气泡干净的向上移动,但是这里可以看到,这里的气泡似乎没有剪短和初始位置的联系。这是为什么呢?因为每次弧度变化,画笔都会把这个弧度对应的位置画下,由于没有清除前面画下的气泡,所以呈现的效果就是于初始位置连接在一起,所以我们每次画之前都应该把这个气泡的上一次位置清除。代码如下:
1 var arr=[] 2 setInterval(function(){ 3 ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight)//每次开画之前,先把画布清空 4 for(var i=0;i<arr.length;i++){ 5 arr[i].deg+=5 6 arr[i].x=arr[i].startX+Math.sin(arr[i].deg*Math.PI/180)*arr[i].step 7 arr[i].y=arr[i].startY-(arr[i].deg*Math.PI/180)*arr[i].step 8 } 9 for(var i=0;i<arr.length;i++){ 10 ctx.save() 11 ctx.fillStyle='rgba('+arr[i].red+","+arr[i].green+","+arr[i].blue+","+arr[i].alp+')' 12 ctx.arc(arr[i].x,arr[i].y,arr[i].r,0,2*Math.PI) 13 ctx.fill() 14 ctx.beginPath() 15 ctx.restore() 16 } 17 },1000/60) 18 setInterval(function(){ 19 var red=Math.round(Math.random()*255) 20 var blue=Math.round(Math.random()*255) 21 var green=Math.round(Math.random()*255) 22 var alp=1 23 var r=Math.random()*10+3 24 var x=Math.random()*canvas.clientWidth 25 var y=canvas.clientHeight-r 26 var deg=0 27 var step=Math.random()*20+10 28 var startX=x 29 var startY=y 30 arr.push({ 31 red,blue,green,alp,r,x,y,step,startX,startY,deg 32 }) 33 },5000/60)
效果如下:

效果似乎已经达到要求了,但是结束了吗?当然没有,我们还没有考虑到这个功能实现里最重要的arr数组,他在干嘛呢?定时器不断地给他添加数据,他已经快爆了,我们有必要储存这么多数据吗?当然没必要,当气泡跳出canvas的范围,我们就不需要再去计算他,自然也不用保留他的各个信息,就可以把他从数组里删除,所以每次计算位置的时候给他加个判断,大功告成。当然还有没清除的定时器,这个需要根据你的需求,比如在我的项目中,移出人物所在位置,气泡消失,所以我可以在此时清除定时器。
1 if(arr[i].y<20){ 2 arr.splice(i,1) 3 }
四、总结
1.创建canvas环境,获取画笔
2.利用两个定时器生成随机气泡,初始位置需要设计,以及把气泡的所有信息全部储存在数组的每个对象里
3.根据自己想实现的曲线,去设计弧度,再按需求放大缩小,以及每次画新的之前把前面的清除
4.扫尾工作,从数组里把移动出固定范围的气泡的信息去掉,按需停止定时器等
大家都可以试着去写下,有不理解的可以评论我,我会尽量及时回复,有更好的办法也可以互相探讨(●'◡'●)
最后献上本文完整代码
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title></title> 6 <style type="text/css"> 7 body{ 8 background-color: pink; 9 } 10 #test{ 11 position: absolute; 12 top: 0; 13 left: 0; 14 bottom: 0; 15 right: 0; 16 margin: auto; 17 border: 1px solid; 18 } 19 canvas{ 20 background-color: white; 21 } 22 </style> 23 </head> 24 <body> 25 <canvas id="test" width="200" height="400"></canvas> 26 </body> 27 <script type="text/javascript"> 28 var canvas=document.getElementById('test') 29 if(canvas.getContext){ 30 var ctx=canvas.getContext('2d') 31 var arr=[] 32 setInterval(function(){ 33 ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight) 34 for(var i=0;i<arr.length;i++){ 35 arr[i].deg+=5 36 arr[i].x=arr[i].startX+Math.sin(arr[i].deg*Math.PI/180)*arr[i].step 37 arr[i].y=arr[i].startY-(arr[i].deg*Math.PI/180)*arr[i].step 38 if(arr[i].y<20){ 39 arr.splice(i,1) 40 } 41 } 42 for(var i=0;i<arr.length;i++){ 43 ctx.save() 44 ctx.fillStyle='rgba('+arr[i].red+","+arr[i].green+","+arr[i].blue+","+arr[i].alp+')' 45 ctx.arc(arr[i].x,arr[i].y,arr[i].r,0,2*Math.PI) 46 ctx.fill() 47 ctx.beginPath() 48 ctx.restore() 49 } 50 },1000/60) 51 setInterval(function(){ 52 var red=Math.round(Math.random()*255) 53 var blue=Math.round(Math.random()*255) 54 var green=Math.round(Math.random()*255) 55 var alp=1 56 var r=Math.random()*10+3 57 var x=Math.random()*canvas.clientWidth 58 var y=canvas.clientHeight-r 59 var deg=0 60 var step=Math.random()*20+10 61 var startX=x 62 var startY=y 63 arr.push({ 64 red,blue,green,alp,r,x,y,step,startX,startY,deg 65 }) 66 },5000/60) 67 } 68 69 </script> 70 </html>

浙公网安备 33010602011771号