持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 7 天,点击查看活动详情
前言
在前面一节中,我们使用 canvas
实现了一个炫酷的全屏旋转六边形。通过学习三角函数相关的知识点,我发现 canvas
的动画是真的好玩,刚好今天又在小破站学到了一个更加炫酷的三角形旋转效果,那就让我们一起来学习一下吧!
首先还是先看一下最终实现的效果,如下图所示:
这个效果中运用到的很多知识点,包括获取三角形的坐标,三角函数相关的知识点,在前面一节中也都有介绍过,接下来咱们就一起来实现这个效果吧!
旋转的大三角形
看到这个效果,我们首先应该想到的是,如何定位到外部的三角形的三个顶点,以及如何定位到生成的小三角形的顶点。如果你能想到这里,那就证明你已经懂了一点关于 canvas
绘图的原理。其实跟上一节一样,我们只需要找到外部三角形的中心点,以及它的三个顶点,就能够画出外部的三角形了。
我们先来画出外面的三角形,并实现旋转的效果。这里需要的 html
和 css
跟前面一节是一样的,就不做过多的描述了,我们主要还是关注 js
相关的内容,我们依旧使用 ES6
中的 class
语法糖,相关代码如下:
class RotationTriangle {
constructor() {
/** @type {HTMLCanvasElement} */
this.canvas = document.getElementById('canvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = 800;
this.canvas.height = 800;
this.tris = []; // 保存所有的三角形对象
}
}
首先定义一个 RotationTriangle
类,并在 constructor
函数中定义好初始化的相关数据,因为我们后面要生成很多三角形,因此我们继续定义一个新的 Triangle
类,通过实例化 Triangle
类,实现三角形的绘制,Triangle
类的相关代码如下:
class Triangle {
constructor(x, y, r, d, ctx) {
this.x = x; // 三角形中心的x坐标
this.y = y; // 三角形中心的y坐标
this.r = r; // 中心到顶点的距离
this.d = d; // 初始角度
this.td = this.d; // 旋转角度
this.ctx = ctx;
}
// 更新方法
update() {
// 增加旋转角度
this.td++;
}
// 绘制方法
draw() {
this.drawTri(this.x, this.y, this.r, this.d);
this.drawTri(this.x, this.y, this.r, this.td);
}
// 绘制三角形
drawTri(x, y, r, d) {
this.ctx.beginPath();
for (let i = 0; i < 3; i++) {
const { x: x1, y: y1 } = this.getCoordinate(x, y, r, d + i * 120);
this.ctx.lineTo(x1, y1);
}
this.ctx.closePath();
this.ctx.stroke();
}
// 根据三角函数计算出对应的坐标点
getCoordinate(x, y, r, d) {
let x1 = r * Math.cos(d * Math.PI / 180) + x;
let y1 = r * Math.sin(d * Math.PI / 180) + y;
return { x: x1, y: y1 };
}
}
Triangle
类主要是用于生成三角形,其中我们需要传入五个参数,这五个参数在代码中已经添加了相关的注释。接下来我们就需要在 RotationTriangle
类中通过实例化 Triangle
类来渲染外部的三角形,并让它们旋转起来,我们一起来给 RotationTriangle
类添加几个方法,相关代码如下:
class RotationTriangle {
constructor() {
...other code
this.init();
this.animate();
}
init() {
this.tris.push(
new Triangle(this.canvas.width / 2, this.canvas.height / 2, this.canvas.width / 2, -90, this.ctx)
);
}
draw() {
for (const n in this.tris) {
const tris = this.tris[n];
tris.update();
tris.draw();
}
}
animate() {
requestAnimationFrame(() => this.animate());
this.draw();
}
}
new RotationTriangle();
在 RotationTriangle
类中,我们添加了一个 init
方法,主要用于初始化生成的三角形,最后不要忘了实例化 RotationTriangle
类,实现的效果如下图所示:
通过上图可以看到,我们的外部的三角形已经绘制出来了,并且也在旋转,但是我们这里没有加 clearRect
,因此会绘制无限个三角形,我们在 RotationTriangle
类的 draw
方法中添加一下 clearRect
,效果如下所示:
当我们添加了 clearRect
方法后,可以看到多余的三角形都已经被清除了,但是在 canvas
中的三角形却一直旋转不会停下来,我们需要让它旋转到一定的角度后就停止下来,然后继续下一步的操作。
再说这个旋转的三角形停下来之前,我们先来看一下它是如何旋转起来的。还记得在前面定义的 Triangle
类吗?在 Triangle
类中,我们定义了一个 this.d
的变量,这个变量主要用于保存初始的旋转角度,并且还定义了一个 this.td
的变量,这个变量主要用于旋转时角度的改变。
在 Triangle
类的 update
方法中,我们通过 this.td++
不断的改变旋转角度,然后执行 this.draw
方法来绘制三角形,就实现了三角形的旋转。那么我们该如何让这个旋转的三角形到达一定角度后就停下来呢?其实也很简单,我们只需要修改 Triangle
类中的 update
方法即可,让我们一起来看一下修改后的代码,如下:
class Triangle {
...other code
update() {
// 限制旋转角度
if (this.td < this.d + 60) {
// 增加旋转角度
this.td++;
}
}
...other code
}
我们只需要在 Triangle
类的 update
方法中添加一个简单的逻辑判断,当旋转的角度小于初始角度加60度时,旋转的角度才不断的变化,当超过这个值时,旋转的角度就不会变化了,这样整个旋转的三角形就会停止了,让我们一起来看一下实现的效果:
在上图中可以看到,当旋转到距离初始角度的60度后,三角形就会停止旋转了。这里在GIF图中没有停下来是因为图片会反复的播放,大家可以将代码拷到本地跑跑就知道了。
外部的大三角实现了,接下来我们就需要实现两个大三角形的六个顶角出生成N个小三角形的效果了,该如何实现呢?让我们一起来看一下吧!
旋转的小三角
在上面我们已经实现了一个旋转的大三角形,那么要实现在这两个三角形六个顶点出生成新的 N个 小三角形,我们只需要找到这六个顶点,然后再使用跟上面一样生成大三角形的方法,就可以生成 N个 小三角形了,让我们一起来看代码吧,这里只需要修改 RotationTriangle
类中的 draw
方法即可,相关代码如下:
class RotationTriangle {
...other code
draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (const n in this.tris) {
const tris = this.tris[n];
tris.update();
tris.draw();
// 渲染两个大三角形的六个顶点周围的小三角形
// 首先需要判断一下当前大三角形的旋转是否已经停止了
if (tris.td === tris.d + 60) {
// 因为是两个大三角形,因此有6个顶点
for (let m = 0; m < 6; m++) {
// 根据三角函数,小三角的中心到外部三角的中心距离相当于外部三角距离顶点的2/3
const nr = tris.r * 2 / 3;
// 新的三角形的旋转角度,默认为外部三角形的角度
const d = tris.d + m * 60;
// 计算坐标,根据外部三角行的角度旋转六次,即可获得新的三角形中心的坐标
const x = nr * Math.cos(d * Math.PI / 180) + tris.x;
const y = nr * Math.sin(d * Math.PI / 180) + tris.y;
// 计算新的三角形的顶点,距离相当于外部三角形距离顶点的1/3
const r = tris.r / 3;
this.tris.push(new Triangle(x, y, r, d, this.ctx));
}
}
}
}
}
我们在 RotationTriangle
类的 draw
方法中,首先要在两个大三角形的六个顶点周围找到 N个 小三角形的坐标点,然后通过实例化 Triangle
类来生成 N个 小三角形,最终实现的效果如下所示:
还是跟前面一样,我们也需要限制一下小三角形的生成,否则它会无限的生成,产生的效果就如上图所示。我们需要在 Triangle
类中定义一个旋转的开关,用它来判断当前的N个三角形是否已经旋转到指定的位置了,这样就能防止三角形无限的旋转下去,我们先来看一下 Triangle
相关的代码,如下:
class Triangle {
constructor(x, y, r, d, ctx) {
...other code
// 添加一个开关即可
this.act = true; // 旋转开关,当旋转到指定的位置后,才开始生成新的小三角形
}
}
在 Triangle
类中有了旋转的开关后,我们就需要修改一下 RotationTriangle
类中的 draw
方法内的判断条件了,相关代码如下:
class RotationTriangle {
... other code
draw() {
... other code
// 判断一下当前大三角是否旋转到了指定的角度,并且需要限制一下生成的三角形尺寸,不能无限制的生成下去
if (tris.td === tris.d + 60 && tris.r > 10 && tris.act) {
...other code
// 开关设置为false,停止旋转
tris.act = false;
}
}
}
我们在 RotationTriangle
类的 draw
方法中更新了生成三角形的条件后,当旋转到一定的角度时,就不会再继续旋转了,让我们一起来看一下修改后的效果:
到这里,这个旋转的三角形的效果已经完成的差不多了,但是目前这个效果只会执行一遍就自动停止下来,并且这个颜色也很丑。接下来我们就让整个旋转的三角形变得更加炫丽一些,并且可以无限的动起来。
炫丽的旋转三角形
要给一个图像添加颜色,我们有可以通过 rgba
或者 hsl
,这里我依旧使用 hsl
,在上一节中也使用过这个属性,我们只需要在 Triangle
类的 drawTri
方法中添加 hsl 属性,并根据旋转的角度自动来变换颜色以及自动淡出即可,让我们看一下修改后的代码,如下:
class Triangle {
constructor(x, y, r, d, ctx) {
...other code
// 旋转180度后,“清空” canvas
this.opt = 180; // 当旋转到对应角度时,画布中的三角形淡出画布
}
...other code
drawTri(x, y, r, d) {
this.ctx.beginPath();
for (let i = 0; i < 3; i++) {
const { x: x1, y: y1 } = this.getCoordinate(x, y, r, d + i * 120);
this.ctx.lineTo(x1, y1);
// 添加 strokeStyle 即可,颜色值属性为 hsl
this.ctx.strokeStyle = `hsl(${this.td}deg, 30%, 50%, ${this.opt / 60})`;
}
this.ctx.closePath();
this.ctx.stroke();
}
}
在 Triangle
类的 drawTri
方法中,我们添加了 strokeStyle
属性值,并且使用 hsl
作为颜色的变换属性,如果不了解 hsl
相关的参数,可以点击这里进行查看。hsl
前三个参数分别就对于了颜色的色调值、饱和度以及颜色的亮度,而最后一个参数则代表当前的透明度,当我们在前面定义的 this.opt
遍历值除以 60 后,canvas
中的三角形就会逐渐淡出画布,看起来就像被“清除”了一样。
然后我们还要让整个旋转的效果一直持续下去,接下来就需要在 RotationTriangle
类中添加一个 this.count
变量,它主要用于记录当前的旋转次数,当旋转次数小于0时,就重新绘制全屏的三角形,这样就可以无限的旋转下去了,让我们一起来看一下最后的代码:
class RotationTriangle {
constructor() {
...other code
this.count = 0;
this.init();
}
init() {
this.count = 480;
...other code
}
draw() {
...other code
this.count--;
if (this.count < 0) {
this.tris.length = 0;
this.init();
}
}
}
在 draw
方法中,通过不断的减小 this.count
的值,当它小于 0 时,我们就将 this.tris
数组的长度重置为 0,这样就减少 canvas
绘制时浏览器内存的消耗;其次我们重新执行 this.init()
方法,这样就能够让整个效果无限的执行下去了,最终完整的代码及执行效果可以在这里进行查看:
总结
在这一节总,我们又对前面学到的关于三角函数相关的知识点进行了复习,并且实现的这个效果中,用到的获取图像坐标的方法,也是在前面一节中学到过的,如果对这一节中获取图像坐标点的方法还不了解,可以去看一下前一篇的文章。
不得不说的是,学好三角函数,真的可以畅快淋漓的把玩 canvas
的动画。那么,你心动了吗?快来跟我一起学习吧!
最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家