做者: 首席填坑官∙苏南
来源: @IT·平头哥联盟
交流群:91259409五、公众号:honeyBadger8
。本文原创,著做权归做者全部,转载请注明原连接及出处
灵感来源于前些天捡到钱了,就想着是时候给本身买辆车了,工做这么多年了应该对本身好一点,在网上搜索了一下看到这个车型。其实几年前是买过一辆的,可是不到一个月就被人偷了,伤心了很久。此次必定锁好,上三把锁保证小偷再也偷不走了,因而我拿着钱去买了些益力多,跟同事分享了,心情仍是比较愉悦的。—— @IT·平头哥联盟,我是首席填坑官
∙苏南(South·Su) ^_^~javascript
但想来做为一名程序(嗯,仍是个菜鸟,专业首席填坑官哦😇),车基本是用不上的啦,为啥?由于有改不完的bug,记得刚毕业那时候最大的梦想是:“撩个妹子 携手仗剑天涯,惩奸除恶、劫富济贫,快意人生~”,无奈一入IT深似海,今后BUG改不完啊。因此仍是多学习吧,这不就学着画了个车知足一下本身的内心安慰,在这里把你们一块儿分享一下,唉,有点扯偏了~,你们先来看一下最终的效果图吧!html
效果已经看了到,有没有感受很牛B??其实也就通常般啦~,接下来就让我带你们一块儿分解一下它的实现过程吧
canvas
中文名中:画布,它就跟咱们在纸上画画同样,画某样东西以前,咱们要先学会构思、拆解你要画的东西,就跟汽车、手机等东西同样,一个成品都是由不少零件组成的,当你拆解开来,一点点完成再组装的,就会变的容易的多。前端
绘制地平线 :java
车的速度
与周围的事物、建筑、人产生一个交差,那种感受是很刺激的,那么咱们也来加一点东西,让动画看起来更丰富一些,我选择了 三条线,线自己有个渐变过渡的效果,比纯色要灵动些动画看起来更逼真,并且初始它是不在画布范围内的,这个点要注意一下;所谓的动画
,也是由一张张静态图组成,而后快速过渡,让视觉造成了视差,最后欺骗了大脑,我看见动画了……lineTo
、strokeStyle
、stroke
、restore
等,这里不一一讲解了,若有不了解可自行查看 w3school API,horizon(){ /** * 轮子的底部,也称地平线: 1.清除画布 2.画一条直线,且高度6px 本文@IT·平头哥联盟-首席填坑官∙苏南分享,非商业转载请注明原连接及出处 */ this.wheelPos = []; this.ctx.save(); this.ctx.clearRect(0, 0, this.canvasW, this.canvasH); let horizonX = 0,horizonY = this.canvasH-100; this.ctx.beginPath(); this.ctx.strokeStyle = this.color; this.ctx.lineWidth=6; this.ctx.moveTo(horizonX,horizonY); this.ctx.lineTo(this.canvasW,horizonY); this.ctx.closePath(); this.ctx.stroke(); Array.from({length:5}).map((k,v)=>{ let dotProportion = (this.canvasW*0.49)*v-this.oneCent; this.wheelPos.push({x:dotProportion,y:horizonY-this.wheelRadius}); let startX = dotProportion-(this.animateNum*2); //用于动画滚动移动 this.ctx.beginPath(); this.ctx.strokeStyle = "#f9f8ef"; this.ctx.lineWidth=6; this.ctx.moveTo(startX,horizonY); this.ctx.lineTo(startX+5,horizonY); this.ctx.closePath(); this.ctx.stroke(); }); this.ctx.restore(); this.shuttle(); // this.wheel(); } shuttle(){ /** * 画几根横线,有点视差,感受骑车在飞速穿梭的感受: 本文@IT·平头哥联盟-首席填坑官∙苏南分享,非商业转载请注明原连接及出处 */ let shuttleX = this.canvasW+100, shuttleY = this.canvasH/6; let shuttleW = shuttleX+100; [0,40,0].map((k,v)=>{ let random = Math.random()+2; let x = shuttleX+k-(this.animateNum*(2.2*random)); let y = shuttleY+v*24; let w = shuttleW+k-(this.animateNum*(2.2*random)); let grd=this.ctx.createLinearGradient(x,y,w,y); grd.addColorStop(0,"#30212c"); grd.addColorStop(1,"#fff"); this.ctx.beginPath(); this.ctx.lineCap="round"; this.ctx.strokeStyle = grd; this.ctx.lineWidth=3; this.ctx.moveTo(x,y); this.ctx.lineTo(w,y); this.ctx.stroke(); this.ctx.closePath(); }); }
绘制车轮 :git
arc
、fill
console.log(this.wheelPos); this.wheelPos = this.wheelPos.slice(1,3); //这里取1-3 console.log(this.wheelPos); this.wheelPos.map((wheelItem,v)=>{ let wheelItemX = wheelItem.x, wheelItemY= wheelItem.y-this.wheelBorder/1.5; //外胎 this.ctx.beginPath(); this.ctx.lineWidth=this.wheelBorder; this.ctx.fillStyle = "#f5f5f0"; this.ctx.strokeStyle = this.color; this.ctx.arc(wheelItemX,wheelItemY,this.wheelRadius,0,Math.PI*2,false); this.ctx.closePath(); this.ctx.stroke(); this.ctx.fill(); //最后两轮胎中心点圆轴承 this.axisDot(wheelItemX,wheelItemY); this.ctx.restore(); }); this.ctx.restore();
createRadialGradient
-建立放射状/环形的渐变(用在画布内容上)context.createRadialGradient(x0,y0,r0,x1,y1,r1); + createRadialGradient API 说明: x0 = 渐变的开始圆的 x 坐标 y0 = 渐变的开始圆的 y 坐标 r0 = 开始圆的半径 x1 = 渐变的结束圆的 x 坐标 y1 = 渐变的结束圆的 y 坐标 r1 = 结束圆的半径 详细使用请看下面代码的实例
let scaleMultiple = this.wheelRadius*.94; let speed1 = this.animateNum*2; //外圈半圆速度 let speed2 = this.animateNum*3; //内小圈半圆速度 //后轮 if(v === 0){ //内圆 this.ctx.beginPath(); let circleGrd=this.ctx.createRadialGradient(wheelItemX,wheelItemY,18,wheelItemX,wheelItemY,scaleMultiple); circleGrd.addColorStop(0,"#584a51"); circleGrd.addColorStop(1,"#11090d"); this.ctx.fillStyle = circleGrd; this.ctx.arc(wheelItemX,wheelItemY,scaleMultiple,0,Math.PI*2,false); this.ctx.fill(); this.ctx.closePath(); //两个半圆线 [ {lineW:2,radius:scaleMultiple*.6,sAngle:getRads(-135+speed1) , eAngle:getRads(110+speed1)}, {lineW:1.2,radius:scaleMultiple*.45,sAngle:getRads(45+speed2) , eAngle:getRads(-50+speed2)} ].map((k,v)=>{ this.ctx.beginPath(); this.ctx.lineCap="round"; this.ctx.strokeStyle ="#fff"; this.ctx.lineWidth=k.lineW; this.ctx.arc(wheelItemX,wheelItemY,k.radius,k.sAngle,k.eAngle,true); this.ctx.stroke(); this.ctx.closePath(); }); this.ctx.restore(); }
//两个圆,再缩小一圈,画线圆 Array.from({length:3}).map((k,v)=>{ let prevIndex = v-1 <= 0 ? 0 : v-1; let eAngle = v*135, sAngle = -45+(prevIndex*45)+v*90; let radius = scaleMultiple*.75; let _color_ = "#120008"; this.ctx.beginPath(); this.ctx.lineCap="round"; this.ctx.strokeStyle = _color_; this.ctx.lineWidth=3.5; this.ctx.arc(wheelItemX,wheelItemY,radius,getRads(sAngle+speed1),getRads(eAngle+speed1),false); this.ctx.stroke(); this.ctx.closePath(); if(v<2){ //再缩小一圈 let eAngleSmaller = 15+ v*210, sAngleSmaller = -30+v*90; let radiusSmaller = scaleMultiple*.45; this.ctx.beginPath(); this.ctx.lineCap="round"; this.ctx.strokeStyle = _color_; this.ctx.lineWidth=3; this.ctx.arc(wheelItemX,wheelItemY,radiusSmaller,getRads(sAngleSmaller+speed2),getRads(eAngleSmaller+speed2),false); this.ctx.stroke(); this.ctx.closePath(); } this.ctx.restore(); });
绘制车身车架 :github
最开始是用了最笨的办法,lineTO
、moveTo
、一根一根线的画,画到一半时发现画两个三角
或者一个菱形
便可,而后再把几根主轴从新画一下,因而两种方法都尝试了一下,canvas
lineTo
点对点的划线,结论 :使用moveTo
把画布坐标从O
移动到A
点 x/y,lineTo
从A
开始画到B
结束,再从B
到C
点,闭合,即一个三角完成
//方法二:三角形 …………此处省略N行代码 [ { moveX:triangleX1, moveY:triangleY1, lineX1:coordinateX, lineY1:triangleH1, lineX2:discX, lineY2:discY, }, { moveX:triangleX2+15, moveY:triangleY2, lineX1:triangleX1, lineY1:triangleY1, lineX2:discX, lineY2:triangleH2, }, ].map((k,v)=>{ this.ctx.beginPath(); this.ctx.moveTo(k.moveX,k.moveY); //把坐标移动到A点,从A开始 this.ctx.strokeStyle = this.gearColor; this.ctx.lineWidth=coordinateW; this.ctx.lineTo(k.lineX1,k.lineY1);//从A开始,画到B点结束 this.ctx.lineTo(k.lineX2,k.lineY2); //再从B到C点,闭合 this.ctx.closePath(); this.ctx.stroke(); this.ctx.restore(); }); …… //方法一:菱形 …………此处省略N行代码 this.ctx.beginPath(); this.ctx.strokeStyle = this.gearColor; this.ctx.lineWidth=coordinateW; this.ctx.moveTo(polygon1X,polygon1Y); this.ctx.lineTo(coordinateX,height); this.ctx.lineTo(discX,discY); this.ctx.lineTo(polygon2X,polygon1Y+5); this.ctx.lineTo(polygon2X-5,polygon1Y); this.ctx.lineTo(polygon1X,polygon1Y); this.ctx.closePath(); this.ctx.stroke(); ……
绘制车的豪华宝坐、扶手 :segmentfault
quadraticCurveTo
能知足这个需求,—— 二次贝塞尔曲线 quadraticCurveTo
,半天也没画成功,后面尝试去找了它邻居bezierCurveTo
,—— 三次贝塞尔曲线 quadraticCurveTo
、bezierCurveTo
、createLinearGradient
//坐位 this.ctx.restore(); let seatX = (discX-85),seatY=discY-140; let curve1Cpx = [seatX-5,seatY+30,seatX+75,seatY+8]; let curve2Cpx =[seatX+85,seatY-5,seatX,seatY]; this.ctx.beginPath(); // this.ctx.fillStyle = this.gearColor; let grd=this.ctx.createLinearGradient(seatX,seatY,seatX+10,seatY+60); //渐变的角度 grd.addColorStop(0,"#712450"); grd.addColorStop(1,"#11090d"); this.ctx.fillStyle = grd; this.ctx.moveTo(seatX,seatY); this.ctx.quadraticCurveTo(...curve1Cpx); this.ctx.quadraticCurveTo(...curve2Cpx); this.ctx.fill(); //车前轴上的手柄 let steeringX = lever1X-20,steeringY = lever1Y-45; let steeringStep1 = [steeringX+40,steeringY-10,steeringX+40,steeringY-10,steeringX+35,steeringY+15] let steeringStep2 = [steeringX+30,steeringY+25,steeringX+25,steeringY+23,steeringX+18,steeringY+23] this.ctx.beginPath(); this.ctx.lineCap="round"; this.ctx.strokeStyle = "#712450"; this.ctx.lineWidth=coordinateW; this.ctx.moveTo(steeringX,steeringY); //40 60; this.ctx.bezierCurveTo(...steeringStep1); this.ctx.bezierCurveTo(...steeringStep2); this.ctx.stroke(); this.ctx.closePath();
绘制车的发动机、脚踏板 :dom
脚踏板,这个好理解,就是用lineTo
画两跟线,其中一根进行一个90度的旋转就ok了,但重点是它在动画过程当中的一个过程呢,个人分析过程是这样:工具
N* (Math.PI / 180)
转动;N* (Math.PI / 180)
的 rotate
角度旋转。discGear(coordinateX,coordinateY,coordinateW){ //车中间齿轮盘 disc let discX = coordinateX,discY = coordinateY; let discRadius = this.wheelRadius*.36;//车轮的3.6; let discDotX = discX+discRadius+8,discDotY = discRadius/.98; this.ctx.restore(); this.ctx.save(); this.ctx.translate(discX,discY); // this.ctx.rotate(-(Math.PI/2)); Array.from({length:30}).map((v,index)=>{ let radian = (Math.PI / 15) ; this.ctx.beginPath(); this.ctx.lineCap="round"; this.ctx.strokeStyle = this.color; this.ctx.rotate(radian); this.ctx.lineWidth=3; this.ctx.moveTo(0,discDotY); this.ctx.lineTo(1.5,discDotY); // ctx.arc(discDotX,discDotY,6,0,Math.PI*2,false); this.ctx.closePath(); this.ctx.stroke(); }); this.pedal(discX,discY,discRadius); this.pedal(discX,discY,discRadius,1); this.ctx.restore(); } pedal(coordinateX,coordinateY,discRadius,turnAngle=0){ //脚踏板,分两次初始化,一次在中心齿轮绘制以前,一次在以后, let pedalX = coordinateX, pedalY = coordinateY - discRadius*.7; let pedalW = 6, pedalH = discRadius*1.9; let radian = (this.animateNum)*(Math.PI / 180) ; let radianHor = (this.animateNum)*(Math.PI / 180) ; let turnAngleNum = 1; let moveY = 28; if(turnAngle !== 0){ this.ctx.rotate(-180*(Math.PI/180)); turnAngleNum = (Math.PI/180); }; this.ctx.beginPath(); this.ctx.rotate(radian*turnAngleNum); this.ctx.lineCap="round"; this.ctx.strokeStyle = this.gearColor; this.ctx.lineWidth=pedalW; this.ctx.moveTo(-1,moveY); this.ctx.lineTo(0,pedalH); this.ctx.closePath(); this.ctx.stroke(); this.ctx.save(); let pedalHorW = pedalH/1.5,pedalHorH=pedalW; this.ctx.translate(0,pedalH); this.ctx.beginPath(); this.ctx.rotate(-radianHor); this.ctx.lineCap="round"; this.ctx.fillStyle = "#fff"; this.ctx.strokeStyle = this.gearColor; this.ctx.lineWidth =2; this.ctx.roundRect(-pedalHorW/2,-2,pedalHorW,pedalHorH,5); this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); this.ctx.restore(); }
绘制车的链条 :
bezierCurveTo
,cp1x,cp1y,cp2x,cp2y,x,y等参数画出来的,具体看下面代码吧,其实就是两个半椭圆的拼接……//链条 let chainW = ( coordinateX+discRadius - this.wheelPos[0].x) / 2; let chainX = this.wheelPos[0].x +chainW-5 ; let chainY = coordinateY; this.ctx.save(); this.ctx.translate(chainX,chainY+4.8); this.ctx.rotate(-2*(Math.PI/180)); let r = chainW+chainW*.06,h = discRadius/2; this.ctx.beginPath(); this.ctx.moveTo(-r, -1); this.ctx.lineWidth=3; this.ctx.strokeStyle = "#1e0c1a"; this.ctx.bezierCurveTo(-r,h*1.5,r,h*4,r,0); this.ctx.bezierCurveTo(r,-h*4,-r,-h*1.5,-r,0); this.ctx.closePath(); this.ctx.stroke(); this.ctx.restore();
以上就是今天@IT·平头哥联盟-首席填坑官
∙苏南给你带来的分享,整个车的绘制过程,感受车架部分应该还有更好的作法,若是您有更好的建议及想法,欢迎斧正,最后送上完整的示例图,如以为不错,记得关注咱们的公众号哦
!
文章源码获取-> blog-resource 👈
想直接在线预览 👈
做者:苏南 - 首席填坑官
交流群:912594095,公众号:honeyBadger8
本文原创,著做权归做者全部。商业转载请联系@IT·平头哥联盟
得到受权,非商业转载请注明原连接及出处。