我是如何用 canvas 实现一个自定义仙女棒的?

lxf2023-05-06 00:35:01

本文正在参加「 . 」

前言

大家好,最近由于疫情反反复复,导致只能居家办公,目前已经居家十来天了,说实话天天在家蹲着也确实难受,只能自己利用这些时间多学点东西,刚好最近又学到了一个非常不错且实用的效果,今天就来分享给大家。

我们都知道在浏览器中的鼠标样式是浏览器默认设置的,如果我们想要自定义鼠标的样式,就需要我们自己来开发了,今天就给大家分享如何利用 canvas 来自定义一个鼠标样式及鼠标移动的效果。首先我们还是先来看一下最终实现的效果,如下所示:

我是如何用 canvas 实现一个自定义仙女棒的?

可以看到我们自定义了一个鼠标的样式,就像一个仙女棒一样,并且当鼠标移动的时候还会参数很多粒子效果,鼠标被点击的时候也会生成很多粒子,而且我们也能随意的划选页面中的文字,不会造成文字被遮挡。这个效果在实际的开发中还是比较实用的,下面我们就一起来看一下如何实现这个效果吧!

自定义鼠标样式

首先我们先在页面中画出仙女棒,完成鼠标的自定义设置,毕竟浏览器的默认鼠标样式是一个小箭头,在一些页面中看起来就显得很呆,因此我们需要先完成自定义是仙女棒

这一次我们不需要先在页面中添加 canvas 元素,而是需要动态的插入 canvashtml 中,这么做的目的就是为了不让 canvas 遮挡页面中的文字,当然也需要一点点的 css 样式,我们先来看一下 css 代码,如下所示:

*{margin: 0; padding: 0;}
body {
    display: flex;
    width: 100%;
    height: 100vh;
    overflow: hidden;
    background: #333;
    align-items: center;
    justify-content: center;
}

css 代码很简单,只有几行,当然这只是这个 demo 中添加的样式,真实的开发中就可以安装你们自己的需求来编写相关的 css 代码了。

接下来我们就需要通过代码来动态插入 canvashtml 中了,我们依旧使用 TS + ES6 来开发,在前面的文章中已经反复说过为什么要用 TS 来开发,这里就不再继续说明了。我们来看一下如何动态创建 canvas 并插入到 html 中,代码如下:

class CustomMouse {
    canvas: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D;
    mouse: { x: number; y: number; };
    backgroundColor: string;
    particles: Particle[];
    constructor() {
        this.canvas = document.createElement('canvas');
        document.body.appendChild(this.canvas);
        this.canvas.style.position = 'fixed';
        this.canvas.width = innerWidth;
        this.canvas.height = innerHeight;
        this.canvas.style.zIndex = '999';
        this.canvas.style.pointerEvents = 'none';
        this.ctx = this.canvas.getContext('2d');
        this.backgroundColor = 'rgba(30, 144, 255, 0.8)';
        this.mouse = { x: -100, y: -100 };
        this.particles = [];

        this.init();
        this.animate();
    }
    init() {
        this.ctx.fillStyle = this.backgroundColor;
        this.ctx.strokeStyle = this.backgroundColor;

        document.styleSheets[0].insertRule(`* {cursor: none;}`, 0);
    }
}

在上述的代码中,我们通过 document.createElement 创建了一个 canvas 元素,并通过 document.body.appendChild 将这个 canvas 插入到了 body 元素中,之所以这个 canvas 元素能够不遮挡文字,主要就是给这个 canvas 设置了 position 属性为 fixed,最重要的是我们还给 canvas 添加了 pointerEvents 属性为 none,这样就可以让鼠标划选的时候穿透 canvas 直接划选到文字了。

如果大家看的仔细,会发现我们还添加了一个 init 方法,在这个方法中设置了 ctxfillStylestrokeStyle,并且动态插入了一条样式 * {cursor: none;},它主要用于将浏览器的默认鼠标样式设置为空,这样我们就可以实现自定义样式(仙女棒)了。准备工作已经完成了,接下来我们就开始画出仙女棒吧!

在前面的准备工作中,我们定义了一个 CustomMouse 类,接下来我们就需要继续完善这个类,并且在 canvas 中绘制出一个仙女棒。首先我们需要获取到当前鼠标移动的位置,通过监听鼠标的移动事件即可获取到当前的位置,让我们一起来看代码,如下:

class CustomMouse {
    ...other code
    
    init() {
        ...other code
        
        this.event();
    }
    
    event() {
        document.body.addEventListener('mousemove', (evt) => {
            this.mouse.x = evt.x;
            this.mouse.y = evt.y;
        });
    }
    
    animate() {
        requestAnimationFrame(() => this.animate());
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.stick();
    }
    
    stick() {
        const { x, y } = this.mouse;
        this.ctx.beginPath();
        this.ctx.moveTo(x, y);
        this.ctx.lineTo(x + 30, y + 30);
        this.ctx.lineWidth = 3;
        this.ctx.stroke();
        this.ctx.fillStyle = `rgba(255, 255, 255, .5)`;
        this.ctx.beginPath();
        this.ctx.arc(x, y, 5, 0, Math.PI * 2);
        this.ctx.fill();
        this.ctx.fillStyle = this.backgroundColor;
    }
}

上述代码中,我们可以看到通过 document.body.addEventListener 绑定了一个 mousemove 事件,在鼠标移动的过程中会实时记录当前的鼠标坐标点,然后我们还添加了一个 animatestick 方法,这两个方便主要用于绘制仙女棒及将这个绘制出来的仙女棒实时的展示在 canvas 中,最终实现的效果如下所示:

我是如何用 canvas 实现一个自定义仙女棒的?

自定义鼠标样式(仙女棒)已经实现了,但是这个效果不够炫酷,接下来我们就给鼠标移动的时候生成一些粒子,让这个仙女棒更仙一些吧!

跟随鼠标的粒子

上面的内容中,我们已经实现了自定义的鼠标样式,接下来我们给这个鼠标添加相应的粒子效果,让鼠标在移动的过程中不断的生成粒子。对于粒子效果,这已经是我们写了很多次的内容了,生成粒子的主要思路就是找到它的生成点,我们一起来看一下生成粒子的相关代码吧!这里我们还是先定义一个 Particle 类,然后在这个类里面定义相关的方法,外部去调用生成粒子即可。相关代码如下:

class Particle {
    x: number;
    y: number;
    vx: number;
    vy: number;
    age: number;
    ctx: CanvasRenderingContext2D;
    color: string;
    constructor(x: number, y: number, ctx: CanvasRenderingContext2D, mouseClick?: boolean) {
        this.x = x;
        this.y = y;
        if (!mouseClick) {
            this.vx = Math.random() - 0.5;
            this.vy = Math.random();
            this.age = Math.random() * 30;
        } else {
            let deg = Math.random() * Math.PI * 2;
            let r = Math.random();
            this.vx = r * Math.cos(deg);
            this.vy = r * Math.sin(deg);
            this.age = Math.random() * 30 + 60;
        }
        this.ctx = ctx;
        this.color = `hsl(${this.randomColor() * this.vx}, 50%, 80%)`;
    }
    update() {
        this.age--;
        this.x += this.vx;
        this.y += this.vy;
    }
    draw() {
        this.ctx.beginPath();
        this.ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
        this.ctx.fillStyle = this.color;
        this.ctx.fill();
    }
    randomColor() {
        return Math.random() * 255 | 0;
    }
}

Particle 类中,唯一需要注意的点就是在 constructor 方法中判断当前是鼠标移动还是鼠标点击,这是因为我们要复用这个粒子类来做鼠标和点击不同的效果,当然这里面也用到了三角函数相关的知识点,如果有对三角函数还不了解的同学,可以点击这里查看之前的文章。

有了 Particle 类,我们就需要在鼠标移动的时候来动态生成粒子了,还记得我们是在哪绑定了鼠标的移动事件吗?在 CustomMouse 类的 event 方法中就绑定了,接下来就需要修改 event 方法了,一起来看代码:

class CustomMouse {
    ...other code
    
    event() {
        document.body.addEventListener('mousemove', (evt) => {
            this.mouse.x = evt.x;
            this.mouse.y = evt.y;
            for (let i = 0; i < 30; i++) {
                this.particles.push(new Particle(this.mouse.x, this.mouse.y, this.ctx));
            }
        });
    }
    
    animate() {
        ...other code
        
        for (let i in this.particles) {
            const p = this.particles[i];
            p.update();
            p.draw();
            if (p.age < 0) {
                this.particles.splice(i as unknown as number, 1);
            }
        }
    }
}

我们在 CustomMouse 类的 event 方法中监听 mousemove ,并不断生成粒子,然后修改了 animate 方法。在 animate 中调用前面创建的粒子,最后还需要判断当前粒子的半径是否已经缩减到0,如果到0则需要将这个粒子从 canvas 中移除掉,最终实现的效果如下所示:

我是如何用 canvas 实现一个自定义仙女棒的?

到这里,我们的自定义鼠标样式基本已经算是完成了,但是在上图中可以看到,当鼠标停止移动后,点击页面的时候是不会生成新的粒子效果的,我们还需要完成最后一步,监听鼠标的点击事件,修改 event 方法即可,代码如下:

class CustomMouse {
    ...other code
        
    event() {
        ...other code
        
        document.body.addEventListener('click', () => {
            for (let i = 0; i < 100; i++) {
                this.particles.push(new Particle(this.mouse.x, this.mouse.y, this.ctx, true));
            }
        });
    }
}

在上述代码中,我们只是在 event 方法中监听了 bodyclick 事件,当监听到鼠标点击后,动态创建 100个 粒子,最终实现的完整代码及效果可以在这里进行查看:

最后

因为疫情的原因,导致居家办公,这反而让我可以有更多的时间去学习。总的来说,疫情居家办公,有利有弊,关键是看自己如何对待这个问题了。

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

往期回顾

又用 canvas 实现了一个旋转的伪3D球体,来瞧瞧?

canvas 实现多彩的圆环数字时钟

不得不说,这个 canvas 的旋转半圆效果可能会闪瞎你的双眼