Canvas 代码雨效果实现


一、效果展示

本文实现了一个类似 Matrix 电影中的代码雨效果,具体表现为:

  • 🎬 绿色字符从屏幕顶部向下流动
  • 🌊 字符流动速度和长度随机变化
  • 🔄 字符内容随机生成
  • 📱 自适应窗口大小

二、技术实现

1. 核心原理

使用 Canvas API 实现代码雨效果,主要原理:

  1. 创建 Canvas 元素并设置为全屏
  2. 初始化字符数组和位置数组
  3. 每帧更新字符位置并绘制
  4. 使用半透明黑色背景实现轨迹效果

2. 核心代码分析

HTML 结构

<canvas width="1914" height="936"></canvas>

JavaScript 实现

let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let str = "zheshiyigemumao".split("");
let arr = Array(Math.ceil(canvas.width / 10)).fill(0);
let run = () => {
    ctx.fillStyle = "rgba(0,0,0,0.05)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "#0f0";
    arr.forEach((item, index) => {
        ctx.fillText(str[Math.floor(Math.random() * str.length)], index * 10, item + 10);
        arr[index] = item > canvas.height || item > 10000 * Math.random() ? 0 : item + 10;
    });
};
setInterval(run, 40);

三、技术要点

  1. Canvas 初始化

    • 获取 Canvas 元素和 2D 上下文
    • 设置 Canvas 大小为窗口大小,实现全屏效果
  2. 字符设置

    • 定义字符集 str,可根据需要修改为中文、日文或其他符号
    • 将字符串分割为字符数组,方便随机选择
  3. 位置数组

    • 创建长度为屏幕宽度/10的数组 arr,记录每列字符的当前位置
    • 初始值都设为 0,表示从屏幕顶部开始
  4. 动画循环

    • 使用 setInterval 每 40ms 执行一次 run 函数
    • 每次循环先绘制半透明黑色背景,实现轨迹渐隐效果
  5. 字符绘制

    • 遍历位置数组,为每列绘制随机字符
    • 字符位置为 (index * 10, item + 10),实现等间距排列
  6. 位置更新

    • 每次循环将字符位置下移 10 像素
    • 当字符超出屏幕高度或随机条件满足时,重置位置为 0

四、代码优化建议

  1. 性能优化

    • 使用 requestAnimationFrame 替代 setInterval,获得更平滑的动画效果
    • 减少不必要的计算,如字符集长度可缓存
  2. 功能扩展

    • 添加颜色渐变效果,使字符从上到下逐渐变亮或变色
    • 实现字符大小随机变化
    • 添加鼠标交互,鼠标移动时影响字符流动
  3. 响应式设计

    • 添加窗口大小变化事件监听,动态调整 Canvas 尺寸
    • 适配不同屏幕分辨率
  4. 视觉效果

    • 添加字符发光效果,增强视觉冲击力
    • 实现字符流动速度的随机变化
    • 添加偶尔出现的高亮字符,模拟 Matrix 效果

五、应用场景

  • 网站背景动画
  • 技术主题活动的视觉元素
  • 个人博客的动态背景
  • 黑客风格的网页设计
  • 终端模拟器的视觉效果

六、扩展实现

以下是一个增强版的代码雨实现,添加了颜色渐变和鼠标交互:

let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 字符集
let str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 可替换为中文字符
let arr = Array(Math.ceil(canvas.width / 15)).fill(0);
let speeds = Array(Math.ceil(canvas.width / 15))
    .fill(0)
    .map(() => Math.random() * 3 + 1);
let mouseX = -1,
    mouseY = -1;

// 鼠标移动事件
canvas.addEventListener("mousemove", e => {
    mouseX = e.clientX;
    mouseY = e.clientY;
});

function run() {
    // 半透明背景,实现轨迹效果
    ctx.fillStyle = "rgba(0,0,0,0.05)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 遍历每列
    arr.forEach((item, index) => {
        // 计算列位置
        const x = index * 15;
        const y = item;

        // 鼠标交互:鼠标附近的字符速度加快
        let speed = speeds[index];
        if (mouseX > 0 && Math.abs(x - mouseX) < 100) {
            speed = 5;
        }

        // 随机字符
        const char = str[Math.floor(Math.random() * str.length)];

        // 颜色渐变:越往下越亮
        const brightness = Math.min(1, (y / canvas.height) * 2);
        ctx.fillStyle = `rgba(0, ${Math.floor(255 * brightness)}, 0, ${brightness})`;

        // 绘制字符
        ctx.font = "14px monospace";
        ctx.fillText(char, x, y);

        // 更新位置
        arr[index] = y > canvas.height || y > 10000 * Math.random() ? 0 : y + speed * 5;
    });

    requestAnimationFrame(run);
}

// 窗口大小变化时重新设置 Canvas 尺寸
window.addEventListener("resize", () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    arr = Array(Math.ceil(canvas.width / 15)).fill(0);
    speeds = Array(Math.ceil(canvas.width / 15))
        .fill(0)
        .map(() => Math.random() * 3 + 1);
});

// 启动动画
run();

七、总结

Canvas 代码雨效果是一个经典的前端动画案例,通过简单的 Canvas API 调用和定时器,就能实现出令人印象深刻的视觉效果。本文介绍的实现方法虽然简单,但包含了 Canvas 动画的核心原理:

  • 帧动画循环
  • 状态管理
  • 视觉效果优化

通过理解这些原理,你可以创建出更多复杂的 Canvas 动画效果,如粒子系统、流体模拟等。

完整代码

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>代码雨效果</title>
    </head>

    <body>
        <canvas width="1914" height="936"></canvas>

        <script>
            // 代码雨效果实现代码
            let canvas = document.querySelector("canvas");
            let ctx = canvas.getContext("2d");
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;

            // 字符集
            let str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 可替换为中文字符
            let arr = Array(Math.ceil(canvas.width / 15)).fill(0);
            let speeds = Array(Math.ceil(canvas.width / 15))
                .fill(0)
                .map(() => Math.random() * 3 + 1);
            let mouseX = -1,
                mouseY = -1;

            // 鼠标移动事件
            canvas.addEventListener("mousemove", e => {
                mouseX = e.clientX;
                mouseY = e.clientY;
            });

            function run() {
                // 半透明背景,实现轨迹效果
                ctx.fillStyle = "rgba(0,0,0,0.05)";
                ctx.fillRect(0, 0, canvas.width, canvas.height);

                // 遍历每列
                arr.forEach((item, index) => {
                    // 计算列位置
                    const x = index * 15;
                    const y = item;

                    // 鼠标交互:鼠标附近的字符速度加快
                    let speed = speeds[index];
                    if (mouseX > 0 && Math.abs(x - mouseX) < 100) {
                        speed = 5;
                    }

                    // 随机字符
                    const char = str[Math.floor(Math.random() * str.length)];

                    // 颜色渐变:越往下越亮
                    const brightness = Math.min(1, (y / canvas.height) * 2);
                    ctx.fillStyle = `rgba(0, ${Math.floor(255 * brightness)}, 0, ${brightness})`;

                    // 绘制字符
                    ctx.font = "14px monospace";
                    ctx.fillText(char, x, y);

                    // 更新位置
                    arr[index] = y > canvas.height || y > 10000 * Math.random() ? 0 : y + speed * 5;
                });

                requestAnimationFrame(run);
            }

            // 窗口大小变化时重新设置 Canvas 尺寸
            window.addEventListener("resize", () => {
                canvas.width = window.innerWidth;
                canvas.height = window.innerHeight;
                arr = Array(Math.ceil(canvas.width / 15)).fill(0);
                speeds = Array(Math.ceil(canvas.width / 15))
                    .fill(0)
                    .map(() => Math.random() * 3 + 1);
            });

            // 启动动画
            run();
        </script>
    </body>
</html>

文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录