《canvas》之第14章 物理动画
第14章 物理动画
14.1 物理动画简介
烟花效果、雨滴效果、球体碰撞等动画。
模拟现实世界,物体遵循牛顿运动定律。
动画,清楚、重绘过程。
- clearRect()清除整个canvas。
- requestAnimationFrame()重绘。
14.2 三角函数简介
14.2.1 什么是三角函数
sin、cos、tan。
Math.sin(θ*Math.PI/180)
Math.cos(θ*Math.PI/180)
Math.tan(θ*Math.PI/180)
Math.asin(x/R)*(180/Math.PI)
Math.acos(y/R)*(180/Math.PI)
Math.atan(x/y)*(180/Math.PI)
Math.atan()与Math.atan2()
- Math.atan(),-PI/2~PI/2
- Math.atan2(),-PI~PI
Math.atan2(y, x)
- atan2(y, x)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script type="text/javascript">
window.onload = function () {
var txt = document.getElementById("txt");
txt.innerHTML = "Math.atan2(1,2)对应角度为:" + Math.atan2(1, 2) * 180 / Math.PI + "<br/>" + "Math.atan2(-1,-2)对应角度为:" + Math.atan2(-1, -2) * 180 / Math.PI;
}
</script>
</head>
<body>
<p id="txt"></p>
</body>
</html>
- arrow.js
function Arrow(x, y, color, angle) {
//箭头中心x坐标,默认值为0
this.x = x || 0;
//箭头中心y坐标,默认值为0
this.y = y || 0;
//颜色,默认值为“#FF0099”
this.color = color || "#FF0099";
//旋转角度,默认值为0
this.angle = angle || 0;
}
Arrow.prototype = {
stroke: function (cxt) {
cxt.beginPath();
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.angle);
cxt.strokeStyle = this.color;
cxt.stroke();
cxt.restore();
},
fill: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.angle);
cxt.fillStyle = this.color;
cxt.beginPath();
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();
cxt.fill();
cxt.restore();
}
};
- 随鼠标移动箭头
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/arrow.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个箭头,中点坐标为画布中心坐标
var arrow = new Arrow(cnv.width/2, cnv.height/2);
//获取鼠标坐标
var mouse = tools.getMouse(cnv);
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
var dx = mouse.x - cnv.width / 2;
var dy = mouse.y - cnv.height / 2;
//使用Math.atan2()方法计算出鼠标与箭头中心的夹角
arrow.angle = Math.atan2(dy, dx);
arrow.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.3 三角函数应用
14.3.1 两点间距离
dx = x2 - x1;
dy = y2 - y1;
distance = Math.sqrt(dx*dx+dy*dy);
- 获取两点间距离
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var text = document.getElementById("p1");
var x = cnv.width / 2;
var y = cnv.height / 2;
var mouse = tools.getMouse(cnv);
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
cxt.save();
cxt.beginPath();
cxt.moveTo(x, y);
//mouse.x表示鼠标的X轴坐标,mouse.y表示鼠标的Y轴坐标
cxt.lineTo(mouse.x, mouse.y);
cxt.closePath();
cxt.strokeStyle = "red";
cxt.stroke();
cxt.restore();
var dx = mouse.x - x;
var dy = mouse.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
text.innerText = "鼠标与中点距离为:" + distance;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
<p id="p1"></p>
</body>
</html>
14.3.2 圆周运动
- 正圆运动
x = centerX + radius*Math.cos(angle*Math.PI/180);
y = centerY + radius*Math.sin(angle*Math.PI/180);
- ball.js
function Ball(x, y, radius, color) {
//小球中心的x坐标,默认值为0
this.x = x || 0;
//小球中心的y坐标,默认值为0
this.y = y || 0;
//小球半径,默认值为12
this.radius = radius || 12;
//小球颜色,默认值为“#6699FF”
this.color = color || "#6699FF";
//缩放倍数
this.scaleX = 1;
this.scaleY = 1;
}
Ball.prototype = {
//绘制“描边”小球
stroke: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.scale(this.scaleX, this.scaleY);
cxt.strokeStyle = this.color;
cxt.beginPath();
cxt.arc(this.x, this.y, this.radius, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.stroke();
cxt.restore();
},
//绘制“填充”小球
fill: function (cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.scale(this.scaleX, this.scaleY);
cxt.fillStyle = this.color;
cxt.beginPath();
cxt.arc(0, 0, this.radius, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.fill();
cxt.restore();
}
}
- 圆周运动的小球
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个小球,中心坐标为(100,25),半径、颜色都取默认值
var ball = new Ball(100, 25);
var centerX = cnv.width / 2;
var centerY = cnv.height / 2;
var radius = 50;
var angle = 0;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//绘制圆形
cxt.beginPath();
cxt.arc(centerX, centerY, 50, 0, 360 * Math.PI / 180, false);
cxt.closePath();
cxt.stroke();
//计算小球坐标
ball.x = centerX + radius * Math.cos(angle);
ball.y = centerY + radius * Math.sin(angle);
ball.fill(cxt);
//角度递增
angle += 0.05;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 椭圆运动
x = centerX + radiusX*Math.cos(angle*Math.PI/180);
y = centerY + radiusY*Math.sin(angle*Math.PI/180);
- 椭圆运动的小球
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(100, 25);
var centerX = cnv.width / 2;
var centerY = cnv.height / 2;
var radiusX = 60;
var radiusY = 40;
var angle = 0;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//绘制椭圆
createEllipse(cxt, centerX, centerY, radiusX, radiusY)
cxt.stroke();
//计算小球坐标
ball.x = centerX + radiusX * Math.cos(angle);
ball.y = centerY + radiusY * Math.sin(angle);
ball.fill(cxt);
//角度递增
angle += 0.05;
})();
function createEllipse(cxt, centerX, centerY, radiusX, radiusY) {
var r = (radiusX > radiusY) ? radiusX : radiusY;
var ratioX = radiusX / r;
var ratioY = radiusY / r;
cxt.save();
cxt.translate(centerX, centerY);
cxt.scale(ratioX, ratioY);
cxt.beginPath();
cxt.arc(0, 0, r, 0, 2 * Math.PI);
cxt.closePath();
cxt.restore();
}
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.3.3 波形运动
- 作用于x轴坐标
左右摇摆。
x = centerX + range*Math.sin(angle*Math.PI/180);
angle += speed;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(cnv.width / 2, cnv.height / 2);
var angle = 0;
var range = 80;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x = cnv.width / 2 + Math.sin(angle) * range;
ball.fill(cxt);
//角度递增
angle += 0.05;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 作用于y轴坐标
上下摇摆。
y = centerY + range*Math.sin(angle*Math.PI/180);
angle += speed;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(cnv.width/2, cnv.height/2);
var angle = 0;
var range = 40;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//ball.x += 0.1;
ball.y = cnv.height/2 + Math.sin(angle) * range;
ball.fill(cxt);
//角度递增
angle += 0.05;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 作用于缩放属性
物体不断放大然后缩小,呈现脉冲动画效果。
scaleX = 1 + range*Math.sin(angle*Math.PI/180);
scaleY = 1 + range*Math.sin(angle*Math.PI/180);
angle += speed;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball(cnv.width/2, cnv.height/2, 25);
var range = 0.5;
var angle = 0;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.scaleX = 1 + range * Math.sin(angle);
ball.scaleY = 1 + range * Math.sin(angle);
ball.fill(cxt);
//角度递增
angle += 0.05;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.4 匀速运动
14.4.1 匀速运动简介
匀速直线运动。
object.x += vx;
object.y += vy;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个小球
var ball = new Ball(0, cnv.height/2);
//定义X轴速度为2,也就是每帧向正方向移动2px
var vx = 0.2;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.4.2 速度的合成和分解
vx = speed*Math.cos(angle*Math.PI/180);
vy = speed*Math.sin(angle*Math.PI/180);
object.x += vx;
object.y += vy;
- 匀速运动小球
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个小球,球心坐标、半径以及颜色都采用默认值
var ball = new Ball();
var speed = 0.2;
//速度方向与X轴正方向角度为30°
var vx = speed * Math.cos(30 * Math.PI / 180);
var vy = speed * Math.sin(30 * Math.PI / 180);
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.y += vy;
ball.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 随鼠标匀速运动的箭头
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/arrow.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个箭头,箭头中心坐标为画布中心坐标
var arrow = new Arrow(cnv.width/2, cnv.height/2);
var mouse = tools.getMouse(cnv);
var speed = 0.5;
(function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, cnv.width, cnv.height);
//计算箭头中心指向鼠标的矢量的方向
var dx = mouse.x - arrow.x;
var dy = mouse.y - arrow.y;
if(dx !=0 || dy != 0) {
var angle = Math.atan2(dy, dx);
arrow.angle = angle;
var vx = speed * Math.cos(angle);
var vy = speed * Math.sin(angle);
arrow.x += vx;
arrow.y += vy;
if(dx*(mouse.x - arrow.x) < 0) {//水平移动距离超过箭头中心与鼠标的水平距离
arrow.x = mouse.x;
}
if(dy*(mouse.y - arrow.y) < 0) {
arrow.y = mouse.y;
}
}
arrow.fill(cxt);
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="800" height="600" style="border:1px solid silver;"></canvas>
</body>
</html>
14.5 加速运动
14.5.1 加速运动简介
vx += ax;
vy += ay;
object.x += vx;
object.y += vy;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//实例化一个小球
var ball = new Ball(0, cnv.height / 2);
//初始化X轴速度以及加速度
var vx = 8;
var ax = -0.2;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.fill(cxt);
vx += ax;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.5.2 加速度的合成和分解
ax = a*Math.cos(angle*Math.PI/180);
ay = a*Math.sin(angle*Math.PI/180);
vx += ax;
vy += ay;
object.x += vx;
object.y += vy;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
var ball = new Ball();
var a = -0.5;
//计算出X轴和Y轴2个方向的加速度
var ax = a * Math.cos(30 * Math.PI / 180);
var ay = a * Math.sin(30 * Math.PI / 180);
var vx = 8;
var vy = 8;
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.y += vy;
ball.fill(cxt);
vx += ax;
vy += ay;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.6 重力
14.6.1 重力简介
vy += gravity;
object.y += vy;
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/utils.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(0, cnv.height);
var vx = 4;
var vy = -5;
var gravity = 0.2;
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.y += vy;
ball.fill(cxt);
//变量递增
vy += gravity;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.6.2 重力应用
- 上下反弹
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(cnv.width / 2, 0);
//Y轴初始速度为0,重力加速度为0.2,反弹系数为-0.8
var vy = 0;
var gravity = 0.2;
var bounce = -0.8;
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.y += vy;
//边界检测
if (ball.y > cnv.height - ball.radius) {
ball.y = cnv.height - ball.radius;
//速度反向并且减小
vy = vy * bounce;
}
ball.fill(cxt);
vy += gravity;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 移动反弹
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(0, cnv.height);
var vx = 3;
var vy = -6;
var gravity = 0.2;
var bounce = -0.75;
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.y += vy;
//边界检测
if ((ball.y + ball.radius) > cnv.height) {
ball.y = cnv.height - ball.radius;
vy = vy * bounce;
}
ball.fill(cxt);
//变量递增
vy += gravity;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="300" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
14.7 摩擦力
vx *= friction;
vy *= friction;
object.x += vx;
object.y += vy;
- x轴或y轴方向运动
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball(0, cnv.height / 2);
//初始化X轴方向速度为2,摩擦系数为0.95
var vx = 8;
var friction = 0.95;
(function frame() {
window.requestAnimationFrame(frame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.fill(cxt);
//变量改变
vx *= friction;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>
- 任意方向运动
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta charset="utf-8" />
<script src="js/tools.js"></script>
<script src="js/ball.js"></script>
<script type="text/javascript">
function $$(id) {
return document.getElementById(id);
}
window.onload = function () {
var cnv = $$("canvas");
var cxt = cnv.getContext("2d");
//初始化数据
var ball = new Ball();
var speed = 8;
var vx = speed * Math.cos(30 * Math.PI / 180);
var vy = speed * Math.sin(30 * Math.PI / 180);
var friction = 0.95;
(function drawFrame() {
window.requestAnimationFrame(drawFrame);
cxt.clearRect(0, 0, cnv.width, cnv.height);
ball.x += vx;
ball.y += vy;
ball.fill(cxt);
//变量改变
vx *= friction;
vy *= friction;
})();
}
</script>
</head>
<body>
<canvas id="canvas" width="200" height="150" style="border:1px solid silver;"></canvas>
</body>
</html>