一、效果展示
本文实现了一个类似 Matrix 电影中的代码雨效果,具体表现为:
- 🎬 绿色字符从屏幕顶部向下流动
- 🌊 字符流动速度和长度随机变化
- 🔄 字符内容随机生成
- 📱 自适应窗口大小
二、技术实现
1. 核心原理
使用 Canvas API 实现代码雨效果,主要原理:
- 创建 Canvas 元素并设置为全屏
- 初始化字符数组和位置数组
- 每帧更新字符位置并绘制
- 使用半透明黑色背景实现轨迹效果
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);
三、技术要点
Canvas 初始化:
- 获取 Canvas 元素和 2D 上下文
- 设置 Canvas 大小为窗口大小,实现全屏效果
字符设置:
- 定义字符集
str,可根据需要修改为中文、日文或其他符号 - 将字符串分割为字符数组,方便随机选择
- 定义字符集
位置数组:
- 创建长度为屏幕宽度/10的数组
arr,记录每列字符的当前位置 - 初始值都设为 0,表示从屏幕顶部开始
- 创建长度为屏幕宽度/10的数组
动画循环:
- 使用
setInterval每 40ms 执行一次run函数 - 每次循环先绘制半透明黑色背景,实现轨迹渐隐效果
- 使用
字符绘制:
- 遍历位置数组,为每列绘制随机字符
- 字符位置为
(index * 10, item + 10),实现等间距排列
位置更新:
- 每次循环将字符位置下移 10 像素
- 当字符超出屏幕高度或随机条件满足时,重置位置为 0
四、代码优化建议
性能优化:
- 使用
requestAnimationFrame替代setInterval,获得更平滑的动画效果 - 减少不必要的计算,如字符集长度可缓存
- 使用
功能扩展:
- 添加颜色渐变效果,使字符从上到下逐渐变亮或变色
- 实现字符大小随机变化
- 添加鼠标交互,鼠标移动时影响字符流动
响应式设计:
- 添加窗口大小变化事件监听,动态调整 Canvas 尺寸
- 适配不同屏幕分辨率
视觉效果:
- 添加字符发光效果,增强视觉冲击力
- 实现字符流动速度的随机变化
- 添加偶尔出现的高亮字符,模拟 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>