当前位置: 首页 > 科技观察

用Canvas为你实现一个大大的气球

时间:2023-03-21 12:09:57 科技观察

一、背景最近在做一个气球挂件的特效需求。借此机会,我想和大家分享一下如何使用canvas以及相应的数学知识来构造一个栩栩如生的气球。2.实现在实现这个看似鼓鼓的气球之前,我们先了解一下它的实现思路,主要分为以下几个部分:实现球体部分;实现气球开口部分;实现气球的线条部分;进行颜色填充;实现动画;Balloon.PNG2.1球体部分的实现对于这样一个气球的球体部分,大家有什么好的实现思路吗?相信每个人都会有各种各样的实现方案,我也在看某位高手的效果,最后感受一下用四次三次贝塞尔曲线来实现这个效果的美妙之处。为了看懂后续的代码,先了解一下三次贝塞尔曲线的原理。(注:引用了CSDN上一位大佬的文章,写的很好,这里引用下图)三次贝塞尔曲线.gif上图中P0为起点,P3为终点,P1和P2为控制点,最终曲线公式如下:B(t)=(1?t)^3*P0+3t(1?t)^2*P1+3t^2(1?t)*P2+t^3P3,t∈[0,1]上面已经列出了三次贝塞尔曲线的效果图和公式,但是如何通过这个和我们的气球挂钩呢?下面通过几张图来理解一下:如上图所示就是实现整个气球球体的思路。具体解释如下:图A中,起点为p1,终点为p2,控制点为c1、c2。让两个控制点重叠,绘制出来的效果不是很好。就像气球的一部分,此时需要通过改变控制点来改变它的外观;改变控制点c1和c2,c1中y值不变,x值减小;c2中的x值不变,增加y值(注意canvas中的坐标方向即可),改变后得到图B的效果,很像气球的外观这次;然后按照这个方法实现整个气球球体的外观。functiondraw(){constcanvas=document.getElementById('canvas');constctx=canvas.getContext('2d');ctx.translate(250,250);drawCoordiante(ctx);ctx.save();ctx.beginPath();ctx.moveTo(0,-80);ctx.bezierCurveTo(45,-80,80,-45,80,0);ctx.bezierCurveTo(80,85,45,120,0,120);ctx.bezierCurveTo(-45,120,-80,85,-80,0);ctx.bezierCurveTo(-80,-45,-45,-80,0,-80);ctx.stroke();ctx.restore();}functiondrawCoordiante(ctx){ctx.beginPath();ctx.moveTo(-120,0);ctx.lineTo(120,0);ctx.moveTo(0,-120);ctx.lineTo(0,120);ctx.closePath();ctx.stroke();}2.2开口部分可以简化成三角形,效果如下:functiondraw(){constcanvas=document.getElementById('canvas');constctx=canvas.getContext('2d');……ctx.save();ctx.beginPath();ctx.moveTo(0,120);ctx.lineTo(-5,130);ctx.lineTo(5,130);ctx.closePath();ctx.stroke();ctx.restore();}2.3Line部分实现Line实现比较简单,直接用直线实现functiondraw(){constcanvas=document.getElementById('canvas');constctx=canvas.getContext('2d');……ctx.保存();ctx.beginPath();ctx.moveTo(0,120);ctx.lineTo(0,300);ctx.stroke();ctx.restore();}2.4填充气球部分的填充使用圆形渐变效果,比纯色函数draw(){constcanvas更漂亮=document.getElementById('canvas');constctx=canvas.getContext('2d');ctx.fillStyle=getBalloonGradient(ctx,0,0,80,210);...}functiongetBalloonGradient(ctx,x,y,r,hue){constgrd=ctx.createRadialGradient(x,y,0,x,y,r);grd.addColorStop(0,'hsla('+hue+',100%,65%,.95)');grd.addColorStop(0.4,'hsla('+hue+',100%,45%,.85)');grd.addColorStop(1,'hsla('+hue+',100%,25%,.80)');returningrd;}2.5动画效果及整体代码以上过程绘制了一个静态的气球部分。实现动画效果只需要使用requestAnimationFrame函数不断循环调用即可。下面直接抛出整体代码,方便同学们观察效果和调试。整体代码如下:letposX=225;letposY=300;letpoints=getPoints();draw();functiondraw(){constcanvas=document.getElementById('canvas');constctx=canvas.getContext('2d');ctx.clearRect(0,0,canvas.width,canvas.height);if(posY<-200){posY=300;posX+=300*(Math.random()-0.5);points=getPoints();}else{posY-=2;}ctx.save();ctx.translate(posX,posY);drawBalloon(ctx,points);ctx.restore();window.requestAnimationFrame(draw);}functiondrawBalloon(ctx,points){ctx.scale(points.scale,points.scale);ctx.save();ctx.fillStyle=getBalloonGradient(ctx,0,0,points.R,points.hue);//绘制球体部分ctx.moveTo(points.p1.x,points.p1.y);ctx.bezierCurveTo(points.pC1to2A.x,points.pC1to2A.y,points.pC1to2B.x,points.pC1to2B.y,points.p2.x,points.p2.y);ctx.bezierCurveTo(points.pC2to3A.x,points.pC2to3A.y,points.pC2to3B.x,points.pC2to3B.y,points.p3.x,points.p3.y);ctx.bezierCurveTo(points.pC3to4A.x,points.pC3to4A.y,points.pC3to4B.x,points.pC3to4B.y,points.p4.x,points.p4.y);ctx.bezierCurveTo(points.pC4to1A.x,points.pC4to1A.y,points.pC4to1B.x,points.pC4to1B.y,points.p1.x,points.p1.y);//绘制空气按钮部分ctx.moveTo(points).p3.x,points.p3.y);ctx.lineTo(points.knowA.x,points.knowA.y);ctx.lineTo(points.knowB.x,points.knowB.y);ctx.fill();ctx.restore();//绘图线部分ctx.save();ctx.strokeStyle='#000000';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(points.p3.x,points.p3.y);ctx.lineTo(points.lineEnd.x,points.lineEnd.y);ctx.stroke();ctx.restore();}functiongetPoints(){constoffset=35;return{scale:0.3+Math.random()/2,hue:Math.random()*255,R:80,p1:{x:0,y:-80},pC1to2A:{x:80-offset,y:-80},pC1to2B:{x:80,y:-80+offset},p2:{x:80,y:0},pC2to3A:{x:80,y:120-offset},pC2to3B:{x:80-offset,y:120},p3:{x:0,y:120},pC3to4A:{x:-80+offset,y:120},pC3to4B:{x:-80,y:120-offset},p4:{x:-80,y:0},pC4to1A:{x:-80,y:-80+offset},pC4to1B:{x:-80+offset,y:-80},knowA:{x:-5,y:130},knowB:{x:5,y:130},lineEnd:{x:0,y:250}};}functiongetBalloonGradient(ctx,x,y,r,hue){constgrd=ctx.createRadialGradient(x,y,0,x,y,r);grd.addColorStop(0,'hsla('+hue+',100%,65%,.95)');grd.addColorStop(0.4,'hsla('+hue+',100%,45%,.85)');grd.addColorStop(1,'hsla('+hue+',100%,25%,.80)');returngrd;}