书接上文,再来欣赏一下要实现的效果
一、绘制球体
实现Canvas动画,我们要学会使用面向对象的编程思想,这里要写一个球体的类,每个球都是new出来的一个个实例对象,这些实例对象上面有很多的属性和方法,这样在操作每个球时只需要调用它们各自的方法就可以了,所以下面我们写一个球体的类出来,通过new实例化几个小球,并将它们绘制在画布上。
-
在开发时我们将各种属性值或配置项都写成变量,这样如果我们以后想要调整或修改值就非常方便,不用去修改逻辑代码。
-
这里为了让小球看起来更立体,咱们给小球添加径向渐变的背景颜色
-
我们习惯性是将垂直向下作为y轴的正方向,所以在开发时一定要注意y值向下为正值。
-
在绘制小球上的文字时由于使用的Georgia字体,计算出的文字与小球的中心点有偏差,所以我们将它的位置修正一下即可。
const canvasWidth = window.innerWidth; // 画布宽度
const canvasHeight = window.innerHeight; // 画布高度
const canvasBgColor = "#222222"; // 画布背景颜色
+ const globuleNum = 8; // 球的总数量
+ const oneRowGlobuleNum = 4; // 每行显示球的数量
+ const globuleHorizontalMargin = 34; // 两球之间水平方向的距离
+ const globuleVerticalMargin = 50; // 两球之间垂直方向的距离
+ const globuleRadius = 80; // 球半径
+ const globuleColor = '#005CC9'; // 球的颜色
+ const fontSize = 40; // 文字大小
+ const textColor = "#E76F5A"; // 文字颜色
const canvas = document.getElementById("myCanvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext("2d");
+ const globuleList = []; // 保存所有小球实例
+ class Globule {
+ constructor(x, y, radius, color = "blue", text = "", fontSize = 30, textColor = "red") {
+ this.initX = x; // 保存小球的初始x位置
+ this.initY = y; // 保存小球的初始y位置
+ this.x = x; // x位置
+ this.y = y; // y位置
+ this.radius = radius; // 小球半径
+ this.color = color; // 小球颜色
+ this.vx = 0; // 小球在水平方向的速度
+ this.vy = 0; // 小球在垂直方向的速度
+ this.text = text; // 小球上显示的文字
+ this.fontSize = fontSize; // 文字大小
+ this.textColor = textColor; // 文字颜色
+ }
+
+ // 绘制
+ draw() {
+ ctx.beginPath();
+ const grd = ctx.createRadialGradient(this.x, this.y, 1, this.x, this.y, this.radius - 2);
+ grd.addColorStop(0, "#ffffff");
+ grd.addColorStop(1, this.color);
+ ctx.fillStyle = grd;
+ ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
+ ctx.fill();
+ if(this.text) {
+ ctx.font = `bold ${fontSize}px Georgia`;
+ ctx.fillStyle = this.textColor;
+ ctx.fillText(this.text, this.x - this.fontSize / 2 + 8, this.y + this.fontSize / 2 - 10);
+ }
+ }
+ // 移动
+ move() {
+ this.draw();
+ }
+ }
function loop() {
// 绘制整个画布的背景色
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.fillStyle = canvasBgColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+ globuleList.forEach(function(globule) {
+ globule.move();
+ });
requestAnimationFrame(loop);
}
+ // 需要显示小球的行数
+ const rowNum = Math.ceil(globuleNum / oneRowGlobuleNum);
+ // 计算出第一个小球所在的x位置
+ const firstGlobuleX = (canvasWidth - oneRowGlobuleNum * 2 * globuleRadius - (oneRowGlobuleNum - 1) * globuleHorizontalMargin) / 2 + globuleRadius;
+ // 计算出第一个小球所在的y位置
+ const firstGlobuleY = (canvasHeight - rowNum * 2 * globuleRadius - (rowNum - 1) * globuleVerticalMargin) / 2 + globuleRadius;
+ // 实例出所有小球,并保存到数组中
+ let xNow = firstGlobuleX;
+ let yNow = firstGlobuleY;
+ for(let i = 0; i < globuleNum; i++) {
+ const globule = new Globule(xNow, yNow, globuleRadius, globuleColor, (i + 1).toString(), fontSize);
+ globuleList.push(globule);
+ if((i + 1) % oneRowGlobuleNum === 0) {
+ xNow = firstGlobuleX;
+ yNow += 2 * globuleRadius + globuleVerticalMargin;
+ } else {
+ xNow += 2 * globuleRadius + globuleHorizontalMargin;
+ }
+ }
// 循环绘制(loop函数中继续调用requestAnimationFrame函数)
requestAnimationFrame(loop);
二、数学基础
由于后面的内容大量地使用到了数学里的三角函数、勾股定理和相似三角形的相关知识,所以这里专门用一个小节来简单地一起回顾一下我们上学时学的这些知识。因为两球之间连线的方向或小球受到的作用力不一定都是水平或者是垂直的,后面计算作用力或者小球速度时都要将其拆分成水平方向和垂直方向两个值,到时理解起来就不会很吃力。
勾股定理:直角三角形的两条直角边的平方和等于斜边的平方 (如下图所示,即a ² + b ² = c ²)
三角函数公式
相似三角形的判定定理之一:平行于三角形一边的直线和其他两边或两边的延长线相交,所构成的三角形与原三角形相似
相似三角的的性质定理之一:各个对应边的比例相等
更多个人文章