效果展示:
html+js代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>bezierCurveTo</title> <style> .canvas{ display: inline-block; float: left; width: 300px; margin: 10px; } canvas { border:1px solid #d3d3d3; } </style> </head> <body> <div class="canvas"> <div>------------射箭游戏-----------</div> <canvas id="myCanvas" width="900" height="300"></canvas> </div> <script> // 射箭游戏 class ShotAnArrow { constructor(id){ let idNode = document.getElementById(id); this.id = idNode; this.width = idNode.width; this.height = idNode.height; this.ctx = idNode.getContext('2d'); this.top = idNode.getBoundingClientRect().top; this.left = idNode.getBoundingClientRect().left; this.startX = 700; this.startY = 100; this.startText = ''; this.endX = 700; this.endY = 200; this.endText = ''; this.controlX = 700; this.controlY = 150; this.controlText = ''; this.pointName = ''; // 箭的属性值 this.arrowEndX = 700; this.arrowEndY = 150; this.arrowStartX = this.arrowEndX - Math.cos(this.angle / 180 * Math.PI) * 100; this.arrowStartY = this.arrowEndY + Math.sin(this.angle / 180 * Math.PI) * 100; this.fly = false; // 箭头是否释放 this.angle = 0; // 分数面板属性 this.score = 0; this.mouseupfn = this.mouseupfn.bind(this); this.mousemovefn = this.mousemovefn.bind(this); this.mousedownfn = this.mousedownfn.bind(this); this.getMousePos = this.getMousePos.bind(this); this.sampleMousemovefn = this.sampleMousemovefn.bind(this); } init() { this.ctx.clearRect(0,0,this.width,this.height); this.drawLine(); this.drawTarget(); this.drawthreeArc(); this.dragFn(); this.drawBow(); this.drawArrow(); this.drawEnergySolid(); this.drawScore(); } // 绘制贝塞尔曲线 以及控制点到贝塞尔曲线两端的直线 drawLine() { let {startX, startY, endX, endY, controlX, controlY} = this; this.ctx.beginPath(); this.ctx.strokeStyle = 'pink'; this.ctx.lineWidth = 1; this.ctx.moveTo(startX, startY); this.ctx.quadraticCurveTo(controlX, controlY, endX, endY); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'pink'; this.ctx.lineWidth = 1; this.ctx.moveTo(startX, startY); this.ctx.lineTo(controlX, controlY); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'pink'; this.ctx.lineWidth = 1; this.ctx.moveTo(endX, endY); this.ctx.lineTo(controlX, controlY); this.ctx.stroke(); } // 控制点增加圆环样式 drawArc(x, y, r, lineWidth) { this.ctx.beginPath(); this.ctx.strokeStyle = "#999"; this.ctx.lineWidth = 1; this.ctx.arc(x,y,r,0,2*Math.PI); // if (lineWidth) this.ctx.lineWidth = lineWidth; this.ctx.fillText("("+ x +","+ y +")", x + 10, y); this.ctx.stroke(); } // 绘制三个控制点 直线的开始点 结束点 贝塞尔曲线的控制点 drawthreeArc(){ let {controlX, controlY} = this; this.drawArc(controlX, controlY, 6); } //获取鼠标在画布中的绝对位置 getMousePos(evt){ let rect = this.id.getBoundingClientRect(); return { x: evt.clientX - rect.left * (this.width / rect.width), y: evt.clientY - rect.top * (this.height / rect.height) } } // 点被选中后的鼠标移动事件 mousemovefn(evt){ let {x, y} = this.getMousePos(evt); let {top, left} = this; let {startX, startY, endX, endY, controlX, controlY} = this; if (x > controlX - 10 && y > controlY - 10 && x < controlX + 10 && y < controlY + 10){ this.pointName = 'control'; this.id.style.cssText = "cursor: pointer;"; this.drawArc(controlX, controlY, 8, 1); this.controlX = evt.clientX - left; this.controlY = evt.clientY - top; this.avoidStartAndEndPointOverlay(); this.fly = false; this.init(); }else { this.pointName = ''; this.id.style.cssText = "cuosor: default;"; this.fly = true; this.init(); } } // 避免三个控制点位置重叠 avoidStartAndEndPointOverlay(){ let {startX, startY, endX, endY, controlX, controlY} = this; if (Math.abs(startX - controlX) < 5 || Math.abs(startY - controlY) < 5) { if (this.pointName === 'start') { this.startX += 5; this.startY += 5; } if (this.pointName === 'control') { this.controlX += 5; this.controlY += 5; } } if (Math.abs(endX - controlX) < 5 || Math.abs(endY - controlY) < 5) { if (this.pointName === 'end') { this.endX += 5; this.endY += 5; } if (this.pointName === 'control') { this.controlX += 5; this.controlY += 5; } } } // 鼠标移动事件。当鼠标移动到控制点时控制点增加样式 sampleMousemovefn(evt){ let {x, y} = this.getMousePos(evt); let {top, left} = this; let {startX, startY, endX, endY, controlX, controlY} = this; if (x > controlX - 10 && y > controlY - 10 && x < controlX + 10 && y < controlY + 10){ this.id.style.cssText = "cursor: pointer;"; this.drawArc(controlX, controlY, 6, 1); }else { this.id.style.cssText = "cuosor: default;"; } } mousedownfn(){ // 重置各项参数 this.startX = 700; this.startY = 100; this.startText = ''; this.endX = 700; this.endY = 200; this.endText = ''; this.controlX = 700; this.controlY = 150; this.controlText = ''; this.pointName = ''; // 箭的属性值 this.arrowEndX = 700; this.arrowEndY = 150; this.arrowStartX = this.arrowEndX - Math.cos(this.angle / 180 * Math.PI) * 100; this.arrowStartY = this.arrowEndY + Math.sin(this.angle / 180 * Math.PI) * 100; this.fly = false; // 箭头是否释放 this.angle = 0; // 分数面板属性 this.score = 0; this.id.addEventListener("mousemove", this.mousemovefn, false); } mouseupfn(){ this.fly = true; this.id.removeEventListener("mousemove", this.mousemovefn, false); this.controlXStatic = this.controlX; this.controlYStatic = this.controlY; this.controlAnimation(); this.arrawAnimation(); } // 控制点被释放后的动画 controlAnimation(){ let {controlX, controlY} = this; let speedX = 5, speedY = 5 * (controlY - 150) / (controlX - 700); let controlTimer = setInterval(()=>{ this.controlX = (this.controlX - speedX).toFixed(2); this.controlY = (this.controlY - speedY).toFixed(2); if(this.controlX <= 700){ clearInterval(controlTimer); controlTimer = null; this.controlX = 700; this.controlY = 150; } },30); } // 圆点拖拽功能 dragFn(){ this.id.removeEventListener('mousemove', this.sampleMousemovefn, false); this.id.removeEventListener('mousedown', this.mousedownfn, false); this.id.removeEventListener('mouseup', this.mouseupfn, false); this.id.addEventListener("mousemove", this.sampleMousemovefn, false); this.id.addEventListener("mousedown", this.mousedownfn, false); this.id.addEventListener("mouseup", this.mouseupfn, false); } // 绘制弓的形状 drawBow(){ this.ctx.beginPath(); this.ctx.lineCap = 'round'; this.ctx.moveTo(700,95); this.ctx.lineTo(700, 100); this.ctx.quadraticCurveTo(650, 100, 650, 150); this.ctx.quadraticCurveTo(650, 200, 700, 200); this.ctx.lineTo(700, 205); this.ctx.lineWidth = 5; this.ctx.strokeStyle='#795548'; this.ctx.stroke(); this.ctx.beginPath(); this.ctx.lineWidth=4; this.ctx.moveTo(646, 144); this.ctx.lineTo(646, 156); this.ctx.stroke(); } // 绘制箭 drawArrow(){ this.ctx.beginPath(); // controlY controlX if(this.fly){ }else { this.arrowEndX = this.controlX; this.arrowEndY = this.controlY; } this.arrowStartX = this.arrowEndX - Math.cos(this.angle / 180 * Math.PI) * 100; this.arrowStartY = this.arrowEndY + Math.sin(this.angle / 180 * Math.PI) * 100; this.ctx.lineWidth = 1; this.ctx.strokeStyle = 'red'; this.ctx.moveTo(this.arrowEndX, this.arrowEndY); this.ctx.lineTo(this.arrowStartX, this.arrowStartY); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.lineWidth = 3; this.ctx.strokeStyle = 'red'; // this.ctx.moveTo(this.arrowStartX + Math.cos((90 + this.angle) / 180) * 10, this.arrowStartY - Math.sin((90 + this.angle) / 180) *8); // this.ctx.lineTo(this.arrowStartX, this.arrowStartY); // this.ctx.lineTo(this.arrowStartX + Math.cos((this.angle+135) / 180) * 10, this.arrowStartY + Math.sin((this.angle+135) / 180) *8) this.ctx.arc(this.arrowStartX, this.arrowStartY, 4, 0, 2 * Math.PI); this.ctx.stroke(); } // 箭的飞行动画 arrawAnimation(){ let {controlXStatic, controlYStatic} = this; let rate = (controlXStatic - 700) / 100 > 1?1:(controlYStatic - 700) / 100; // 初始水平速度 let vx = rate * 70; let time = 0; // 箭的飞行时间 let arrowTimer = setInterval(()=>{ // 水平移动量 let s = vx * time; // 竖直移动量 let H = 9.8 * time * time / 2; this.angle = Math.atan(9.8 * time / vx ) * 180 > 90 ? 85 : Math.atan(9.8 * time / vx ) * 180 ; // 更新箭结束点的位置 this.arrowEndX -= s; this.arrowEndY += H; if (this.arrowStartX < 100) { clearInterval(arrowTimer); arrowTimer = null; this.arrowStartX = 100; this.arrowStartY = Math.sin(this.angle / 180 * Math.PI) * 100 + 9.8 * time * time / 2 + this.controlYStatic; this.arrowEndX = 100 + Math.cos(this.angle / 180 * Math.PI) * 100; this.arrowEndY = 9.8 * time * time / 2 + this.controlYStatic; this.controlX = 700; this.controlY = 150; this.pointName = ''; this.id.style.cssText = "cuosor: default;"; this.getScore(); this.init(); return ; } if (this.arrowStartY > 300) { clearInterval(arrowTimer); arrowTimer = null; this.controlX = 700; this.controlY = 150; this.pointName = ''; this.id.style.cssText = "cuosor: default;"; this.getScore(); this.init(); return; } this.init(); time += 0.1; }, 30); } // 获得射箭的分数 getScore(){ let {arrowStartY} = this; this.score = 100 * (1-(Math.abs(arrowStartY - 150) / 95)).toFixed(2) > 0 ? 100 * (1-(Math.abs(arrowStartY - 150) / 95)).toFixed(2) : 0; } // 绘制能量柱 drawEnergySolid(){ let {controlX, controlY} = this; let rate = (controlX - 700) / 100 > 1?1:(controlX - 700) / 100; let colorArr = ['#3988dc','#318e27','#c6d222', '#da8b12', '#ca2929']; // 能量越来越高 this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.strokeRect(850, 200, 50, 100); this.ctx.globalCompositeOperation = 'source-over'; let gradient = this.ctx.createLinearGradient(875, 300 - 100 * rate, 875, 300); if (rate > 0 && rate < 0.25){ gradient.addColorStop(0,"#3988dc"); gradient.addColorStop(1,"#9cb0da"); }else if (rate >= 0.25 && rate < 0.5){ gradient.addColorStop(0,"#318e27"); gradient.addColorStop(0.5,"#3988dc"); gradient.addColorStop(1,"#9cb0da"); } else if (rate >= 0.5 && rate < 0.7){ gradient.addColorStop(0,"#c6d222"); gradient.addColorStop(0.3,"#318e27"); gradient.addColorStop(0.6,"#3988dc"); gradient.addColorStop(1,"#9cb0da"); } else if (rate >= 0.7 && rate < 0.9){ gradient.addColorStop(0,"#da8b12"); gradient.addColorStop(0.25,"#c6d222"); gradient.addColorStop(0.5,"#318e27"); gradient.addColorStop(0.75,"#3988dc"); gradient.addColorStop(1,"#9cb0da"); }else if (rate >= 0.9 && rate <= 1){ gradient.addColorStop(0,"#ca2929"); gradient.addColorStop(0.25,"#da8b12"); gradient.addColorStop(0.5,"#c6d222"); gradient.addColorStop(0.75,"#318e27"); gradient.addColorStop(0.9,"#3988dc"); gradient.addColorStop(1,"#9cb0da"); } this.ctx.fillStyle = gradient; this.ctx.fillRect(850, 300 - 100 * rate, 50, 100 * rate); } // 绘制靶子 drawTarget(){ this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.ellipse(100, 150, 5, 35, 0, 0, 2 * Math.PI); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.ellipse(100, 150, 15, 45, 0, 0, 2 * Math.PI); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.ellipse(100, 150, 25, 55, 0, 0, 2 * Math.PI); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.ellipse(100, 150, 35, 65, 0, 0, 2 * Math.PI); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.ellipse(100, 150, 45, 75, 0, 0, 2 * Math.PI); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.ellipse(100, 150, 55, 85, 0, 0, 2 * Math.PI); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.strokeStyle = 'red'; this.ctx.lineWidth = 1; this.ctx.ellipse(100, 150, 65, 95, 0, 0, 2 * Math.PI); this.ctx.stroke(); } // 绘制分数面板 drawScore(){ this.ctx.beginPath(); this.ctx.fillStyle = '#999'; this.ctx.fillRect(800, 0, 100, 50); this.ctx.textAlign = "center"; this.ctx.font = '20px Arial'; this.ctx.fillStyle = 'red'; this.ctx.fillText('得分:' + this.score, 850, 30); this.ctx.stroke(); let time = 4 * (this.score / 100); let score = 0; let scoreTimer = setInterval(()=>{ this.ctx.clearRect(800, 0, 100, 50); this.ctx.beginPath(); this.ctx.fillStyle = '#999'; this.ctx.fillRect(800, 0, 100, 50); this.ctx.textAlign = "center"; this.ctx.font = '20px Arial'; this.ctx.fillStyle = 'red'; this.ctx.fillText('得分:' + score, 850, 30); this.ctx.stroke(); score = score + 1; if(score > this.score) { clearInterval(scoreTimer); scoreTimer = null; } }, time); } } const myCanvas = new ShotAnArrow('myCanvas'); myCanvas.init(); </script> </body> </html>