canvas 实现七彩炫酷圆环,快来看看

6,407 阅读4分钟

我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!

前言

最近在逛 B站 的时候看到一个用 canvas 实现的效果,特别的炫酷,刚好学习了一下 canvas 相关的知识点,今天就一起来学习一下这个炫酷的效果吧!首先我们还是先来看一下具体实现的效果,如图:

demo4.gif

在这个效果中,有一个旋转的圆,会跟随鼠标的移动而移动,当鼠标不动时,它会围绕这个圆的圆心进行旋转,具体的效果已经知晓,下面就一起来看看如何实现吧!

基础准备工作

我们先将基础东西都准备好,后续直接讲解相关的思路,这样更利于大家学习和理解。先将基础的 html 代码写好,如下:

<canvas id="canvas"></canvas>

基础的 html 中只包含一个 canvas 标签,其它什么都没有,相关的样式如下:

 body {
    margin: 0;
    overflow: hidden;
    background: black;
}
#canvas {
    background: rgba(0, 0, 0, 1);
    position: relative;
    z-index: 1;
}

基础的准备工作完成了,接下来就该讲解一下实现的相关思路了。

实现思路

canvas 中,一个小球如果要顺着某个点进行旋转,我们需要借助 Math.sin()Math.cos() ,为什么需要借助这个两个方法呢?我们可以一起看一下 sincos 相关的图,如下所示:

11.png

222.png

我们实现一个最简单的圆,然后让它进行旋转,相关代码如下:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

canvas.width = innerWidth;  // window.innerwidth 可以省略 window
canvas.height = innerHeight; // 同上
let theta = 0;
let speed = 0.1;
    
function animate() {
    requestAnimationFrame(animate);
    theta += speed;
    const x = canvas.width / 2 + Math.cos(theta) * 50;
    const y = canvas.height / 2 +  Math.sin(theta) * 50;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.fillStyle = 'red';
    ctx.arc(x, y, 5, 0, Math.PI * 2);
    ctx.fill();
    ctx.closePath();
}

animate();

我们借用 Math.cos() 函数修改小球的 x轴 方向,借用 Math.sin() 函数修改小球的 y轴 方向,通过 requestAnimationFrame 方法来执行动画,从而不断的修改小球的运动轨迹,最终实现的效果如下图所示:

face2.gif

一个小球的运动轨迹实现了,那么要实现多个小球运动,该怎么做呢?只需要多创建一些小球,让它们按上面的方法进行运动即可,通过循环遍历创建多个小球,这里我们使用面向对象的方式来定义小球,相关代码如下:

class Ball {
    constructor(obj) {
        this.x = obj.x;
        this.y = obj.y;
        this.radius = obj.radius;
        this.color = obj.color;
        this.canvas = obj.canvas;
        this.ctx = obj.ctx;
        this.mouse = obj.mouse;
        this.theta = Utils.randomDoubleFromRange(0, Math.PI * 2);
        this.speed = 0.05;
        this.dragSpeed = 0.05;
        this.distance = Utils.randomDoubleFromRange(70, 100);
        this.lastMouse = { x: obj.x, y: obj.y };
    }

    draw() {
        this.ctx.beginPath();
        this.ctx.fillStyle = this.color;
        this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        this.ctx.fill();
        this.ctx.closePath();
    }

     update() {
        this.x = this.canvas.width / 2 + Math.cos(this.theta) * this.distance;
        this.y = this.canvas.height / 2 + Math.sin(this.theta) * this.distance;
        this.theta += this.speed;
        this.draw();
    }
}

通过面向对象的方式来定义小球,在调用的地方只需要通过 new 关键词实例化对象即可,我们通过循环来创建小球,相关代码如下:

const balls = [];
const colorArray = ["#97A7F8", "#C957CA", "#76E2FE"];
function init() {
    for (let i = 0; i < 50; i++) {
        // 随机创建小球颜色
        let color = Utils.randomColors(colorArray);
        balls.push(
            new Ball({
                x: canvas.width / 2,
                y: canvas.height / 2,
                radius: 3,
                color,
                canvas: anvas,
                ctx: ctx
            })
        );
    }
}

通过循环动态创建 50个 小球,最终实现的效果如下所示:

face2.gif

小球有了,但是还记得我们前面实现的效果吗?是一个拖尾的效果,其实要实现这个拖尾效果很简单,只需要修改 animate 中的 clearRect 方法即可,相关代码如下:

animate() {
    requestAnimationFrame(() => this.animate());
    this.ctx.fillStyle = "rgba(255, 255, 255, 0.1)";
    this.ctx.fillRect(
        0,
        0,
        this.canvas.width,
        this.canvas.height
    );
    for (let ball of this.balls) {
        ball.update();
    }
}

clearRect 方法替换为 fillRect,并且添加 fillStyle 属性,设置相关的背景色为白色透明度为0.1的值,实现的效果如下所示:

face2.gif

虽然已经实现了旋转的效果,但是这里用 ctx.arc() 的小球线条看起来太粗了,因此我们继续对代码进行改造,相关代码如下:

draw(lastPosition) {
    this.ctx.beginPath();
    this.ctx.strokeStyle = this.color;
    this.ctx.lineWidth = this.radius;
    this.ctx.moveTo(lastPosition.x, lastPosition.y);
    this.ctx.lineTo(this.x, this.y);
    this.ctx.stroke();
    this.ctx.closePath();
}

update() {
    let lastPosition = { x: this.x, y: this.y };
    this.x = this.canvas.width / 2 + Math.cos(this.theta) * this.distance;
    this.y = this.canvas.height / 2 + Math.sin(this.theta) * this.distance;
    this.theta += this.speed;
    this.draw(lastPosition);
}

我们将 ctx.arc() 替换为 ctx.lineWidth() ,使用线条的方式来渲染圆环,最终实现的效果如下图所示:

face2.gif

到这里,我们的效果已经实现了一大半了,当然目前还剩下当鼠标在页面中移动时圆环要跟着一起移动的效果了。

圆环跟随鼠标

要实现圆环跟随鼠标移动而移动,其实也不难,首先我们需要给 window 添加 mousemove 事件,相关代码如下:

eventFn() {
    addEventListener("mousemove", (event) => {
        this.mouse.x = event.clientX;
        this.mouse.y = event.clientY;
    });

    addEventListener("resize", () => {
        this.canvas.width = innerWidth;
        this.canvas.height = innerHeight;
    });
}

因为 addEventListener 默认是绑定在 window 上面的,因此不需要在前面添加 window。鼠标事件有了,接下来就该实现移动鼠标时相关的逻辑了,看一下相关代码:

update() {
    let lastPosition = {
        x: this.x,
        y: this.y,
    };

    this.lastMouse.x += (this.mouse.x - this.lastMouse.x) * this.dragSpeed;
    this.lastMouse.y += (this.mouse.y - this.lastMouse.y) * this.dragSpeed;

    this.x = this.lastMouse.x + Math.cos(this.theta) * this.distance;
    this.y = this.lastMouse.y + Math.sin(this.theta) * this.distance;
    this.theta += this.speed;
    this.draw(lastPosition);
}

鼠标移动的时候,我们可以获取到当前的位置,通过 event.clientXevent.clientY,并把这两个值保存在 this.mouse 中,这样当鼠标移动的时候,调用 update 方法,从而让选择的小球跟随鼠标移动而移动。最终的实现效果可以在这里进行查看:

总结

通过学习 canvas 的基础 api,实现了一个炫酷的圆环效果,其中需要注意的主要有 Math.sin()Math.cos() 的调用,以及如何通过 canvasctx 属性来渲染不同的图形,其实 canvas 相关的 api 只有使用熟练了,就可以实现各种炫酷的效果。

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

往期回顾

还记得2048怎么玩吗?快来玩会儿(摸鱼)吧!