JavaScript – 简单的鼠标点击波纹效果

为什么这么写就可以正常运作?我也不知道

因为刚考完试想摸鱼,而且确实对之前的theme不太满意,因此重新装修了一下博客,装修完了发现有点空空的,所以想到了去写一个点击动画。但市面上的点击动画,比如什么点击出爱心、点击炸烟花、点击出文字之类的,都不太符合我的个人喜好,所以我决定自己学自己写

先看效果,就像水波纹或者雨滴一样,我自己非常喜欢

代码部分如下,仿照了这个代码的逻辑框架,注释部分ai-assisted

首先是clickEffect()函数,整个代码的框架。前面进行了变量的定义,后面添加事件监听,这样可以获取鼠标点击的事件

这块遇到过的问题:

①页面无法滚动:<canvas>元素覆盖了整个页面,导致鼠标事件被<canvas>捕获,而无法传递到页面的其他元素上。解决方法:pointer-events: none;确保鼠标事件可以穿透<canvas>元素,传递到下面的元素。

②页面切换回来后失效:页面失去焦点(例如切换到其他标签页)时,requestAnimationFrame会暂停执行,以节省资源。当页面重新获得焦点时,requestAnimationFrame不会自动恢复执行。解决方法:在页面重新获得焦点时手动触发一次loop()函数,以恢复动画效果。

function clickEffect() {
    // 存储圆形元素的数组
    let circles = [];
    // 画布的宽度和高度
    let width, height;
    // 2D 绘图上下文
    let ctx;
    // 创建一个 canvas 元素
    const canvas = document.createElement("canvas");
    // 将 canvas 元素添加到文档的 body 中
    document.body.appendChild(canvas);
    // 设置 canvas 的样式,使其覆盖整个屏幕,且位于最上层,不接收指针事件
    canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");

    // 检查是否支持 canvas 绘图上下文和添加事件监听器
    if (canvas.getContext && window.addEventListener) {
        // 获取 2D 绘图上下文
        ctx = canvas.getContext("2d");
        // 初始化 canvas 的大小
        updateSize();
        // 监听窗口大小改变事件,调整 canvas 大小
        window.addEventListener('resize', updateSize, false);
        // 开始动画循环
        loop();
        // 监听点击事件,点击时创建新的圆形
        window.addEventListener("click", function(e) {
        circles.push(new Circle(e.clientX, e.clientY));
        }, false);
        // 监听文档可见性变化事件,可见时继续动画循环
        document.addEventListener("visibilitychange", function() {
            if (document.visibilityState === "visible") {
                loop();
            }
        }, false);
    } else {
        // 不支持 canvas 或事件监听时输出错误信息
        console.log("canvas or addEventListener is unsupported!");
    }


    // 更新 canvas 大小的函数
    function updateSize() {
        //...
    }

    // Circle类
    class Circle {
        //...
    }

    // 动画循环函数
    function loop() {
        //...
    }

    // 移除不透明度为 0 的圆形
    function removeCircles() {
        //...
    }
}
JavaScript

然后是updateSize()函数,用来更新canvas的尺寸

function updateSize() {
    // 设置 canvas 的宽高,扩大两倍以保证高分辨率显示
    canvas.width = window.innerWidth * 2;
    canvas.height = window.innerHeight * 2;
    // 设置 canvas 的显示宽度和高度
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
    // 缩放绘图上下文,使绘制的内容在视觉上保持正常大小
    ctx.scale(2, 2);
    // 存储窗口的实际宽度和高度
    width = window.innerWidth;
    height = window.innerHeight;
}
JavaScript

Circle类,设置圆的初始半径、不透明度和颜色,然后在update()方法里面线性变化,然后用draw()画出来

class Circle {
    // 构造函数,初始化圆形的属性
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.radius = 0;
        this.lineWidth = 7;
        this.opacity = 1;
        this.color = "rgb(114, 134, 212)";
        this.startTime = Date.now();
    }
    // 更新圆形的属性
    update() {
        // 计算从圆形创建开始经过的时间
        const elapsedTime = Date.now() - this.startTime;
        if (elapsedTime < 600) {
            // 半径随时间增长
            this.radius = elapsedTime / 600 * 40; //这个40指的是半径
            // 线宽随时间减小
            this.lineWidth = 6 - (6 * elapsedTime) / 600;
            // 不透明度随时间减小
            this.opacity = 1 - (elapsedTime / 600);
        } else {
            // 超过 600 毫秒后,将圆形的属性设为 0
            this.radius = 0; 
            this.lineWidth = 0; 
            this.opacity = 0; 
        }
    }
    // 绘制圆形
    draw() {
        // 开始绘制路径
        ctx.beginPath();
        // 绘制圆形路径
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
        // 设置线宽
        ctx.lineWidth = this.lineWidth;
        // 设置描边颜色及不透明度
        ctx.strokeStyle = `rgba(114, 134, 212, ${this.opacity})`;
        // 绘制圆形的轮廓
        ctx.stroke();
    }
}
JavaScript

核心的函数loop(),这里也出过问题:效果会莫名其妙失效,尤其是当页面中有其他css或者是媒体加载的时候。加try catch也是为了调这个,前面的代码我也不觉得有什么问题。然后发现,还是得要一个函数定期地去清理Circle数组才行。

function loop() {
    try {
        // 清除整个 canvas
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        // 更新并绘制每个圆形
        for (let i = 0; i < circles.length; i++) {
            circles[i].update();
            circles[i].draw();
        }
        // 当文档可见时,继续请求动画帧进行动画循环
        if (document.visibilityState === "visible") {
            requestAnimationFrame(loop);
        }
    } catch (error) {
        // 捕获并输出错误信息
        console.error("Error in loop:", error);
    }
    // 移除已消失的圆形
    removeCircles();
}
JavaScript

removeCircles()函数,神奇地解决了我的问题(

function removeCircles() {
    for (let i = circles.length - 1; i >= 0; i--) {
        let circle = circles[i];
        if (circle.opacity <= 0) {
            circles.splice(i, 1);
        }
    }
    // 定时调用该函数,确保不断移除已消失的圆形
    setTimeout(removeCircles, 100);
}
JavaScript
最后贴一个完整代码~!
<script>
function clickEffect() {
    // 存储圆形元素的数组
    let circles = [];
    // 画布的宽度和高度
    let width, height;
    // 2D 绘图上下文
    let ctx;
    // 创建一个 canvas 元素
    const canvas = document.createElement("canvas");
    // 将 canvas 元素添加到文档的 body 中
    document.body.appendChild(canvas);
    // 设置 canvas 的样式,使其覆盖整个屏幕,且位于最上层,不接收指针事件
    canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");
    // 检查是否支持 canvas 绘图上下文和添加事件监听器
    if (canvas.getContext && window.addEventListener) {
        // 获取 2D 绘图上下文
        ctx = canvas.getContext("2d");
        // 初始化 canvas 的大小
        updateSize();
        // 监听窗口大小改变事件,调整 canvas 大小
        window.addEventListener('resize', updateSize, false);
        // 开始动画循环
        loop();
        // 监听点击事件,点击时创建新的圆形
        window.addEventListener("click", function(e) {
        circles.push(new Circle(e.clientX, e.clientY));
        }, false);
        // 监听文档可见性变化事件,可见时继续动画循环
        document.addEventListener("visibilitychange", function() {
            if (document.visibilityState === "visible") {
                loop();
            }
        }, false);
    } else {
        // 不支持 canvas 或事件监听时输出错误信息
        console.log("canvas or addEventListener is unsupported!");
    }
    // 更新 canvas 大小的函数
    function updateSize() {
        // 设置 canvas 的宽高,扩大两倍以保证高分辨率显示
        canvas.width = window.innerWidth * 2;
        canvas.height = window.innerHeight * 2;
        // 设置 canvas 的显示宽度和高度
        canvas.style.width = window.innerWidth + 'px';
        canvas.style.height = window.innerHeight + 'px';
        // 缩放绘图上下文,使绘制的内容在视觉上保持正常大小
        ctx.scale(2, 2);
        // 存储窗口的实际宽度和高度
        width = window.innerWidth;
        height = window.innerHeight;
    }
    // 圆形类
    class Circle {
        // 构造函数,初始化圆形的属性
        constructor(x, y) {
            this.x = x;
            this.y = y;
            this.radius = 0;
            this.lineWidth = 7;
            this.opacity = 1;
            this.color = "rgb(114, 134, 212)";
            this.startTime = Date.now();
        }
        // 更新圆形的属性
        update() {
            // 计算从圆形创建开始经过的时间
            const elapsedTime = Date.now() - this.startTime;
            if (elapsedTime < 600) {
                // 半径随时间增长
                this.radius = elapsedTime / 600 * 40; 
                // 线宽随时间减小
                this.lineWidth = 6 - (6 * elapsedTime) / 600;
                // 不透明度随时间减小
                this.opacity = 1 - (elapsedTime / 600);
            } else {
                // 超过 600 毫秒后,将圆形的属性设为 0
                this.radius = 0; 
                this.lineWidth = 0; 
                this.opacity = 0; 
            }
        }
        // 绘制圆形
        draw() {
            // 开始绘制路径
            ctx.beginPath();
            // 绘制圆形路径
            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
            // 设置线宽
            ctx.lineWidth = this.lineWidth;
            // 设置描边颜色及不透明度
            ctx.strokeStyle = `rgba(114, 134, 212, ${this.opacity})`;
            // 绘制圆形的轮廓
            ctx.stroke();
        }
    }
    // 动画循环函数
    function loop() {
        try {
            // 清除整个 canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            // 更新并绘制每个圆形
            for (let i = 0; i < circles.length; i++) {
                circles[i].update();
                circles[i].draw();
            }
            // 当文档可见时,继续请求动画帧进行动画循环
            if (document.visibilityState === "visible") {
                requestAnimationFrame(loop);
            }
        } catch (error) {
            // 捕获并输出错误信息
            console.error("Error in loop:", error);
        }
        // 移除已消失的圆形
        removeCircles();
    }
    // 移除不透明度为 0 的圆形
    function removeCircles() {
        for (let i = circles.length - 1; i >= 0; i--) {
            let circle = circles[i];
            if (circle.opacity <= 0) {
                circles.splice(i, 1);
            }
        }
        // 定时调用该函数,确保不断移除已消失的圆形
        setTimeout(removeCircles, 100);
    }
}
// 调用点击效果函数
clickEffect();
</script>
JavaScript

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注