canvas 实现燃烧的线段,原来线段也能玩的这么出彩

1,866 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 9 天,点击查看活动详情

前言

前一篇文章中,我们通过在 canvas 中使用线段和粒子实现了一个炫酷的动感激光效果,今天我们就继续来学习使用 canvas 开发一个生动有趣的线段燃烧效果。老规矩,我们先来看一下最终实现的效果,如图:

demo1.gif

在这个效果中,可以看到一条持续运动的线段,并且线段的尾部不断的生成粒子,看起来就像是这条线被点燃了并且在不断的燃烧中,那么这个效果该如何实现呢?下面就让我们一起来学习一下吧!

运动的轨迹

在上面的例子中,我们可以分析一下页面中展现的内容有哪些。首先是有一条沿着轨迹不断运动的线段,其次在线段的尾部不断的生成粒子。基于这两点分析,我们可以先来实现这条动态的线段,看看它是如何沿着轨迹动起来的。

我们这里依旧采用和上一篇文章一样的编写方式,使用 ES6 + TS 来编写相关的 TS 代码,htmlcss 比较简单,这里就不做过多的介绍了,可以通过查看前面的文章来学习相关的 htmlcss

下面我们就一起来看一下这条动态的线段该如何生成吧!首先我们还是定义一个 BurnLine 类,并完成 constructor 相关的基础内容,代码如下:

class BurnLine {
    viewport: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D;
    r1: number;
    r2: number;
    r3: number;
    points: number[][];
    index: number;
    particlesArray: Particle[];  // 存放粒子的数组
    constructor(viewport: HTMLCanvasElement) {
        this.viewport = viewport;
        this.ctx = this.viewport.getContext('2d');
        this.viewport.width = 800;
        this.viewport.height = 800;
        this.r1 = 380; // 外圆半径
        this.r2 = 190; // 内切圆半径
        this.r3 = 180; // 画笔距离内切圆圆心
        this.points = [];
        this.particlesArray = [];
        this.index = 0;
    }
}

BurnLine 类的 constructor 函数中,我们定义了相关的基础属性,其中需求注意的分别是 this.r1this.r2this.r3,这三个属性分别代码的是外圆的半径内切圆半径,以及圆心距离内切圆圆心的距离,可能这么解释很多人会有些懵,先不要急,咱们接着往下看就能明白这三个属性的含义了。

BurnLine 类中还定义了一个 particlesArray 数组,它主要用于存放后续生成的粒子,也就是跟在线段后面持续燃烧的效果,这里先暂且放下不说,我们继续来看 BurnLine 类。

接下来我们就需要通过三角函数相关的知识点,生成一条动态的轨迹,并且通过 ctx.arc 将这条轨迹画在 canvas 中,让我们一起来看一下初始的数据准备,这里在 BurnLine 类中通过定义一个 init 方法来实现,代码如下:

class BurnLine {
    ...other code
    
    init() {
        // 如果内切圆半径能够被外切圆半径整除,则旋转一周路径是闭合的
        for (let i = 0; i < 360; i += 0.2) {
            // 生成内切圆圆心坐标
            const x = (this.r1 - this.r2) * Math.cos(i * Math.PI / 180) + this.viewport.width / 2;
            const y = (this.r1 - this.r2) * Math.sin(i * Math.PI / 180) + this.viewport.height / 2;
            // 根据内切圆的旋转角度以及画笔的位置生成对应的坐标点
            const t = -i * Math.PI / 180 * this.r1 / this.r2;
            const x1 = this.r3 * Math.cos(t) + x;
            const y1 = this.r3 * Math.sin(t) + y;
            // 将完整旋转一圈的所有坐标点存入数组
            this.points.push([x1, y1]);
        }
    }
}

init 方法中,通过循环动态生成内切圆圆心的坐标点,以及画笔位置对于的坐标点,最后将这两组坐标结合在一起存入到 this.points 数组中,这样我们初始的数据就获取到了,接下来我们就需要根据这些初始的数据在 canvas 中绘制出对应的点来,最终它们会连接在一起,形成一条完整的轨迹,让我们一起来看一下相关的代码,如下:

class BurnLine {
    ...other code
    
    draw() {
        this.ctx.clearRect(0, 0, this.viewport.width, this.viewport.height);
        // 绘制所有坐标点
        for (const i in this.points) {
            this.ctx.beginPath();
            this.ctx.arc(this.points[i][0], this.points[i][1], 3, 0, Math.PI, false);
            this.ctx.fillStyle = 'white';
            this.ctx.fill();
        }
    }
    animate() {
        setInterval(() => this.draw(), 1);
    }
}

BurnLine 类的 draw 方法中,我们遍历 init 中生成的所有的点的数据,并将它们绘制在 canvas 中,最终实现的效果如下所示:

image.png

现在应该就能够明白在前面定义的 this.r1this.r2this.r3 的作用了吧!我们可以通过随意修改 这三个值来改变轨迹的生成效果,这里我将 this.r2 修改为 18this.r3 修改为 36,最终实现的效果如下所示:

image.png

接下来我们就需要这个轨迹动起来了,要让整个轨迹点运动起来,其实也很简单,我们就不能一次性将 this.points 中所有的点都渲染出来,因此我们就需要修改 draw 方法中的代码了,如下所示:

class BurnLine {
    ...other code
    
    draw() {
        this.ctx.clearRect(0, 0, this.viewport.width, this.viewport.height);
        // 这里不需要遍历整个 this.points 数组
        const p = this.points[this.index];
        this.ctx.beginPath();
        this.ctx.arc(p[0], p[1], 3, 0, Math.PI, false);
        this.ctx.fillStyle = 'white';
        this.ctx.fill();
        
        // 绘制500个路径点
        for (let i = 0; i < 500; i++) {
            const l = this.points[(i + this.index) % this.points.length];
            this.ctx.beginPath();
            this.ctx.arc(l[0], l[1], 1, 0, Math.PI * 2, false);
            this.ctx.fillStyle = `rgba(${Math.random() * 255 | 0}, 255, ${Math.random() * 255 | 0}, ${1 - i / 500})`;
            this.ctx.fill();
        }
        this.index++;
        this.index = this.index % this.points.length;
    }
}

因为我们没有遍历整个 this.points 数组,因此在页面中只会生成一个点,而我们要让整个轨迹动起来,那么就需要多生成一些点,并且让这些点能够不断的渐隐渐现,因此我们在 draw 方法就再次生成 500 个路径点,这样整个轨迹就动起来了,最终实现的效果如下图所示:

demo2.gif

看到这里你是否明白了我们前面为什么要将所有点都绘制出来的原因了呢?通过上图可以看到整体线段是沿着我们前面绘制的轨迹在运动的,不同的是我们没有一次将整条轨迹全部渲染出来,并且通过 ctx.fillStyle 给运动的线段设置了一个颜色和透明值,这样在不断运动的过程中就会慢慢的淡出。

运动的轨迹已经实现了,接下来就还剩下燃烧的效果了,我们一起来看一下如何实现。

燃烧的效果

在上面已经完成了最困难的部分,而燃烧的效果其实就是动态生成粒子并不断的生成和删除粒子即可。粒子的创建已经重复了很多次了,这里就不做过多的讲解了,如果有不明白的地方,可以去看前面的文章,里面都写了相关的实现思路,这里就直接给出相关的代码,如下:

class Particle {
    x: number;
    y: number;
    ctx: CanvasRenderingContext2D;
    r: number;
    d: number;
    vx: number;
    vy: number;
    age: number;
    constructor(x: number, y: number, ctx: CanvasRenderingContext2D) {
        this.x = x;
        this.y = y;
        this.ctx = ctx;
        this.r = Math.random();
        this.d = Math.random();
        this.vx = this.r * Math.cos(this.d * Math.PI * 2);
        this.vy = this.r * Math.sin(this.d * Math.PI * 2);
        this.age = Math.random() * 100 | 0 + 100;
    }
    update() {
        this.x += this.vx;
        this.y += this.vy;
        this.vy += 0.01;
        this.age--;
    }
    draw() {
        this.ctx.beginPath();
        this.ctx.arc(this.x, this.y, 1, 0, Math.PI * 2, false);
        this.ctx.fillStyle = `hsl(${this.x}, 60%, 80%)`;
        this.ctx.fill();
    }
}

Particle 类中定义了 updatedraw 方法,这两个方法大家应该已经很熟悉了,接下来就需要在 BurnLine 类中去使用它们了,我们只需要修改 BurnLine 类的 draw 方法即可,相关代码如下:

class BurnLine {
    ...other code
    
    draw() {
        ...other code
        
        // 循环生成粒子
        this.particlesArray.push(new Particle(p[0], p[1], this.ctx));
        for (const i in this.particlesArray) {
            const p = this.particlesArray[i];
            p.draw();
            p.update();
            if (p.age < 0) {
                this.particlesArray.splice(Number(i), 1);
            }
        }
        
        // 绘制500个路径点
        ...other code
    }
}

通过调用 Particle 类的 drawupdate 方法,最终就能实现我们开头看到的效果了,完整的代码及效果可以在这里进行查看:

总结

最近的学习都是围绕着 canvas 的动画展开,虽然实现的效果不一致,但是里面的思想都是大差不差的,主要还是对三角函数运用的学习和理解。学好数理化,走遍天下都不怕,古人诚不欺我也!

最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家

往期回顾

又实现了一个酷炫的动感激光,不来看看?

canvas 动画真好玩,快来学一下这炫酷的效果吧!

妙啊,canvas 还能实现这么酷炫的旋转六边形

为了学会更多炫酷的 canvas 效果,我熬夜复习了三角函数相关的知识点

嚯,五角星还能这么玩?快摘下来送给你的她/他/ta😁

这个国庆,带老婆去看一场烟花雨