- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=yes">
- <title>
- 速度分解
- </title>
- <style type="text/CSS">
- .wrap { position: relative; width: 1000px; height: 600px; margin: 20px
- auto; } .wrap canvas { border: 1px red solid; background: #dddddd; }
- </style>
- </head>
- <body>
- <div class="wrap">
- <canvas width="800" height="400" id="canvas">
- </canvas>
- <button id="start" data-lock=false>
- 开始
- </button>
- <button id="end">
- 结束
- </button>
- </div>
- <script>
- let canvas = document.querySelector("#canvas");
- let ctx = canvas.getContext("2d");
- let constant = {
- bounceRatio: -0.8,
- requestId: undefined,
- };
- let tool = {
- getRandom: function(a, b, bool) {
- return bool ? Math.floor(Math.random() * (b - a) + a) : (Math.random() * (b - a) + a);
- }
- };
- //生成balls数组
- let balls = [];
- while (balls.length < 20) {
- let x = tool.getRandom(0, canvas.width);
- let y = tool.getRandom(0, canvas.height);
- let r = tool.getRandom(10, 30, true);
- let vx = tool.getRandom(10, 20);
- let vy = tool.getRandom(10, 20);
- let color = "#" + Math.random().toString(16).slice(2, 8);
- let mass = r * r;
- balls.push(new Ball(x, y, r, vx, vy, color, mass));
- }
- document.querySelector("#start").onclick = function() {
- if (this.getAttribute('data-lock') === 'false') {
- this.setAttribute('data-lock', 'true');
- requestAnimationFrame(draw);
- }
- };
- document.querySelector("#end").onclick = function() {
- this.previousElementSibling.setAttribute('data-lock', 'false');
- cancelAnimationFrame(constant.requestId);
- };
- // 绘制所有ball
- function draw() {
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- balls.forEach(function(item, index) {
- item.render(index);
- });
- constant.requestId = requestAnimationFrame(draw);
- }
- //单个ball构造函数
- function Ball(x, y, r, vx, vy, color, mass) {
- this.x = x;
- this.y = y;
- this.r = r;
- this.vx = vx;
- this.vy = vy;
- this.color = color;
- this.mass = mass
- }
- Ball.prototype.render = function(index) {
- //检测是否存在边界碰撞
- let limits = {
- top: this.r,
- right: canvas.width - this.r,
- bottom: canvas.height - this.r,
- left: this.r
- };
- if (this.x > limits.right) {
- this.x = limits.right;
- this.vx *= constant.bounceRatio;
- } else if (this.x < limits.left) {
- this.x = limits.left;
- this.vx *= constant.bounceRatio;
- } else if (this.y > limits.bottom) {
- this.y = limits.bottom;
- this.vy *= constant.bounceRatio;
- } else if (this.y < limits.top) {
- this.y = limits.top;
- this.vy *= constant.bounceRatio;
- }
- for (let i = index + 1; i < balls.length; i++) {
- ballCollision(this, balls[i]);
- //如果两个小球位置有重叠的解决办法
- checkOverlay(this, balls[i]);
- }
- //绘图
- this.x += this.vx;
- this.y += this.vy;
- ctx.save();
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
- ctx.fillStyle = this.color;
- ctx.fill();
- ctx.closePath();
- ctx.restore();
- };
- //碰撞检测、碰撞状态转变
- function ballCollision(A, B) {
- let dx = A.x - B.x;
- let dy = A.y - B.y;
- let dist = Math.sqrt(dx * dx + dy * dy);
- let min_dist = A.r + B.r;
- if (dist < min_dist) {
- let alpha = Math.atan2(dy, dx);
- let vA = Math.sqrt(A.vx * A.vx + A.vy * A.vy);
- let vB = Math.sqrt(B.vx * B.vx + B.vy * B.vy);
- //变换坐标系
- let directionA = Math.atan2(A.vy, A.vx);
- let directionB = Math.atan2(B.vy, B.vx);
- let vA_reverseXbefore = vA * Math.cos(directionA - alpha);
- let vA_reverseYbefore = vA * Math.sin(directionA - alpha);
- let vB_reverseXbefore = vB * Math.cos(directionB - alpha);
- let vB_reverseYbefore = vB * Math.sin(directionB - alpha);
- //球心方向动量守恒,变换后的速度大小
- let vA_reverseXafter = ((A.mass - B.mass) * vA_reverseXbefore + 2 * B.mass * vB_reverseXbefore) / (A.mass + B.mass);
- let vB_reverseXafter = ((B.mass - A.mass) * vB_reverseXbefore + 2 * A.mass * vA_reverseXbefore) / (A.mass + B.mass);
- let vA_reverseYafter = vA_reverseYbefore;
- let vB_reverseYafter = vB_reverseYbefore;
- //变化为正常的坐标系
- A.vx = vA_reverseXafter * Math.cos(alpha) - vA_reverseYafter * Math.sin(alpha);
- A.vy = vA_reverseXafter * Math.sin(alpha) + vA_reverseYafter * Math.cos(alpha);
- B.vx = vB_reverseXafter * Math.cos(alpha) - vB_reverseYafter * Math.sin(alpha);
- B.vy = vB_reverseXafter * Math.sin(alpha) + vB_reverseYafter * Math.cos(alpha);
- }
- }
- function checkOverlay(Ra, Rb) {
- let dx = Ra.x - Rb.x;
- let dy = Ra.y - Rb.y;
- let dist = Math.sqrt(dx * dx + dy * dy);
- let min_dist = Ra.r + Rb.r;
- let angle = Math.atan2(dy, dx);
- if (dist < min_dist - 2) {
- Ra.x += Math.cos(angle) * (min_dist - dist) / 2;
- Ra.y += Math.sin(angle) * (min_dist - dist) / 2;
- Rb.x -= Math.cos(angle) * (min_dist - dist) / 2;
- Rb.y -= Math.sin(angle) * (min_dist - dist) / 2;
- }
- }
- </script>
- </body>
- </html>
来源: http://www.w3cfuns.com/notes/27777/e5e1f58d7217d0308d8d2ecd6553da2d.html