最简单太阳系H5动画canvas详解 零基础可入
最终结果:(实际为动画效果,金星绕轨道转动)
页面准备/html
要使用canvas,需要首先在页面中要绘制的位置放入canvas标签元素,在后期的绘制脚本中通过选择器获取元素,此后脚本在该元素绘制效果。
<canvas
id="myCanvas" // 非必要
width="300"
height="300"
style="border:1px solid #000000" // 非必要
></canvas>
本案例中使用id为方便获取,实际操作中只要成功获取到元素即可,通过类名,标签等方式都可
canvas中提供两个属性width与height,可在标签中直接规定其大小
本案例中写入直联样式border只为增强视觉效果,非必要
初始化函数/init
要做动画,首先我们需要先做一个静态案例,再重绘使它动起来。
首先我们制作动画的初始化函数,函数中,我们将绘制需要的元素准备好,同时初始化函数作为绘制函数的入口。
这里将绘制函数分离出来是为了方便后面的重绘时的重复调用绘制函数,如果只希望绘制静态页面,也可直接放在一起。
let moon; // 月球元素 带有月球图片地址的图片元素 可绘制在canvas上
let venus; // 金星元素 带有金星图片地址的图片元素 可绘制在canvas上
let ctx; // 画笔
// 以上元素需要在初始化及绘制中使用,所以在外部定义
function init(){
// 创建并引入月球图片
moon = new Image();
moon.src = "moon.jpg"
venus = new Image();
// 创建并引入金星图片
venus.src = "venus.jpg";
// 获得画板元素
let canvas = document.querySelector("#myCanvas");
// 激活画笔
ctx = canvas.getContext("2d");
// 在图片加载完成后开始绘制动画
moon.onload = function (){
draw();
}
}
案例中所用的moon图片:
案例中所用的venus图片:
如上,我们在init函数中做好了绘制前的准备,并激发了绘制函数。
绘制函数/draw
首先我们在绘制函数中,在canvas上简单的绘制一个在init中准备好的图片元素,以检验代码是否正常运作。
function draw(){
// 绘制带有月球的基底图案
ctx.drawImage(moon, 0, 0,300,300);
}
参数说明 drawImage(image, 位置坐标x, 位置坐标y, image_width, image_height))
此图片为基地图片,所以大小与canvas标签大小相同,为300*300
此刻效果:
接下来绘制轨道,绘制轨道需要用到画笔元素自带的方法arc
arc(x, y, r, startAngle, endAngle, anticlockwise) : 以
(x, y)为圆心,以r为半径,从startAngle弧度开始到endAngle弧度结束。anticlosewise是布尔值,true表示逆时针,false表示顺时针(默认是顺时针)。
当绘制轨道时,圆心在中央,也就是150*150处,此时我们可以选择移动画笔(移动原点),将画笔放置在中心,然后设置绘制圆心为0*0,也可以不移动画笔,设置圆心为150*150,案例中选择前者,因为画的是一个完整的圆,所以我们开始角度设置为0,结束角度设置为2 * Math.PI。
在draw中添加绘制轨道代码
function draw(){
... ...
ctx.translate(150, 150); // 绘制轨道 先将画笔移到中心,即月球的中心位置
ctx.beginPath(); // 设置画笔为 绘制路径模式 构建一个路径
ctx.strokeStyle = "rgba(255,255,255,.3)"; // 确定该路径颜色
ctx.arc(0, 0, 100, 0, 2 * Math.PI); // 确定该路径
ctx.stroke(); // 绘制该路径到canvas上
}
此刻效果:
接下来绘制金星,金星在案例中是一个动态元素,但是我们可以先绘制一个静态的元素,再将它改为动态,有助于我们理解案例。
function draw(){
... ...
// 绘制金星元素
ctx.translate(100, 0); // 将坐标原点在X轴上移动一个轨道半径的距离,使原点正好落在轨道上
ctx.drawImage(venus, -17, -12,35,40) // 绘制金星元素 图片大小和位置可以自己微调
}
drawImage(image, 位置坐标x, 位置坐标y, image_width, image_height))
此刻效果:
此时金星是不会动的,让其动作起来,要增加三个要素
首先,我们要多次循环激发绘制函数,引发重绘,动画的重绘有特定的api,该api会在下一次屏幕时激发重绘。
在draw函数最后一行添加该api即可。
function draw(){
... ...
requestAnimationFrame(draw); // 触发重绘
}
其次,刚刚我们在绘制中多次改变了原点状态,而重绘的时候状态需要还原,才能保证重绘时不会偏离原来的位置,对此我们有两个api用来保存canvas当前的状态和还原状态。
canvas的状态是通过栈来维护的。
function draw(){
ctx.save(); // 在第一次translate(改变坐标系或原点) 前保存初始状态
... ...
ctx.restore(); // 在完成绘制,激发重绘前还原状态
requestAnimationFrame(draw); // 触发重绘
}
最后,现在可以成功重绘了,但是仍然看不到效果,因为每次重绘都是一致的,而我们希望每次重绘金星的位置都有所变化,通过观察得知,金星围绕中心转动,也就是每次绘制对于中心的角度发生变化,我们可以通过原点在中心时,改变坐标系角度,再移动原点到外轨道上的方式。
function draw(){
... ...
// 绘制动态金星元素
let time = new Date();
ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds());
ctx.translate(100, 0); // 将坐标原点在X轴上移动一个轨道半径的距离,使原点正好落在轨道上
ctx.drawImage(venus, -17, -12,35,40) // 绘制金星元素 图片大小和位置可以自己微调
... ...
}
rotate(angle)
旋转坐标轴。
这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。
旋转的中心是坐标原点。
本案例中的角度是根据时间推算出的,旋转周期为一分钟,旋转角度由时间和一周的角度算出
此时,案例动起来了。
如果觉得动画过于单调,可以为金星也加一圈轨道。
最终结果:
旋转坐标轴。
这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。
旋转的中心是坐标原点。
本案例中的角度是根据时间推算出的,旋转周期为一分钟,旋转角度由时间和一周的角度算出
此时,案例动起来了。
如果觉得动画过于单调,可以为金星也加一圈轨道。
最终结果:(实际为动画效果)
整体代码:
let moon;
let venus;
let ctx;
function init(){
// 创建并引入月球图片
moon = new Image();
moon.src = "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3666816182,1910985606&fm=26&gp=0.jpg"
venus = new Image();
// 创建并引入金星图片
venus.src = "http://qqpublic.qpic.cn/qq_public/0/0-2694106759-1EC786EC7E4BD7758742CEB938698DD7/0?fmt=jpg&size=22&h=480&w=422&ppv=1/0";
// 获得画板元素
let canvas = document.querySelector("#myCanvas");
// 激活画笔
ctx = canvas.getContext("2d");
// 在图片加载完成后开始绘制动画
moon.onload = function (){
draw();
}
}
init();
function draw(){
// 绘制基底图案 moon
ctx.drawImage(moon, 0, 0,300,300);
ctx.save();
// 绘制轨道 先将画笔移到中心,即月球的中心位置
ctx.translate(150, 150);
ctx.beginPath();
ctx.strokeStyle = "rgba(255,255,255,.3)";
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke()
let time = new Date();
ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds());
ctx.translate(100, 0);
ctx.beginPath();
ctx.strokeStyle = "rgba(255,255,255,.3)";
ctx.arc(0, 0, 30, 0, 2 * Math.PI);
ctx.stroke()
ctx.drawImage(venus, -17, -20,35,40)
ctx.restore();
requestAnimationFrame(draw);
}
有网友在评论中指出,对代码中的角度计算公式不太理解,特此补充。
ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds());
上面代码公式是一个角度计算公式,需要一点数学思维。
首先我们代码中设定的是一分钟转一圈,也就是60秒转360度,
Math.PI是180度,乘2是360度,也就是一周,2 * Math.PI / 60是获得一秒钟转动的角度。
time.getSeconds() 为获取当前秒数 也就是一秒钟转动的角度乘以当前秒数 得出此时应该转动的最终角度 每过一秒 转动的角度大一些,
此时如果不加后面的也可以,就变成了秒针滴答的感觉,因为一秒变一次还是人眼可捕捉的。
为了视觉平滑效果,我们添加一个补充角度,也就是当前毫秒数,让位移变为毫秒级。
与前面相同 2 * Math.PI / 60000 获得每毫秒转动的角度 time.getMilliseconds() 取得当前毫秒。