Canvas 实现黑客帝国代码雨:从零开始,带你玩转经典特效!

3 阅读2分钟

黑客帝国中的经典代码雨场景一直是许多开发者心中的梦想。在本文中,我将带大家一步步使用 HTML5 的 Canvas 技术来实现这个特效,并且额外加入一个控制面板,方便实时调整颜色、速度、字体大小等参数。

效果预览

  • 绿色字符如雨般流下,随机切换字符内容。
  • 实时调整颜色、字符集、速度、字体大小和字符密度。
  • 炫酷的控制面板,鼠标悬浮即可操作。

实现过程

1、基础 HTML 和 CSS

我们先创建一个简单的 HTML 页面,并用 CSS 设置画布和控制面板的样式:

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Canvas 黑客帝国代码雨</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body,
    html {
      height: 100%;
      margin: 0;
      background-color: #000;
      font-family: Arial, sans-serif;
    }
    canvas {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
    .controls {
      position: absolute;
      top: 20px;
      right: 20px;
      background: rgba(0, 0, 0, 0.8);
      padding: 20px;
      border-radius: 12px;
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
      color: #fff;
      font-size: 14px;
      opacity: 0; /* 默认隐藏 */
      transition: opacity 0.3s ease-in-out;
      width: 250px;
    }
    .controls:hover {
      opacity: 1;
    }
    .controls label {
      display: flex;
      justify-content: space-between;
      margin-bottom: 12px;
      align-items: center;
    }
    .controls input,
    .controls select {
      margin-left: 10px;
      flex: 1;
      padding: 5px;
      border-radius: 5px;
      border: 1px solid #ccc;
    }
    .controls input[type="range"] {
      width: 70%;
    }
    .controls input[type="color"],
    .controls select {
      width: 30%;
    }
    .controls .range-container {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .controls h3 {
      font-size: 18px;
      margin-bottom: 15px;
      text-align: center;
    }
  </style>
</head>
<body>
  <canvas id="rain"></canvas>
  <div class="controls">
    <h3>代码雨设置</h3>
    <label>
      颜色:
      <input type="color" id="color-picker" value="#0F0">
    </label>
    <label>
      字符集:
      <select id="char-set">
        <option value="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()">默认</option>
        <option value="ABCDEFGHIJKLMNOPQRSTUVWXYZ">字母</option>
        <option value="0123456789">数字</option>
      </select>
    </label>
    <label class="range-container">
      速度:
      <input type="range" id="speed-range" min="5" max="50" value="30">
      <span id="speed-value">30</span> 毫秒
    </label>
    <label class="range-container">
      字体大小:
      <input type="range" id="font-size-range" min="10" max="30" value="16">
      <span id="font-size-value">16</span> px
    </label>
    <label class="range-container">
      字符密度:
      <input type="range" id="density-range" min="1" max="3" value="2">
      <span id="density-value">2</span>
    </label>
  </div>
</body>
</html>

2、编写 JavaScript 动画逻辑

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

let config = {
  color: '#0F0',
  charSet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$%^&*()',
  speed: 30,
  fontSize: 16,
  density: 2,
};

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const updateCanvasSettings = () => {
  const spacing = config.fontSize / config.density;
  return Math.floor(canvas.width / spacing);
};

let columns = updateCanvasSettings();
let drops = Array(columns).fill(0);

const colorPicker = document.getElementById('color-picker');
const charSetSelect = document.getElementById('char-set');
const speedRange = document.getElementById('speed-range');
const fontSizeRange = document.getElementById('font-size-range');
const densityRange = document.getElementById('density-range');
const speedValue = document.getElementById('speed-value');
const fontSizeValue = document.getElementById('font-size-value');
const densityValue = document.getElementById('density-value');

colorPicker.addEventListener('input', (e) => config.color = e.target.value);
charSetSelect.addEventListener('change', (e) => config.charSet = e.target.value);

speedRange.addEventListener('input', (e) => {
  config.speed = parseInt(e.target.value);
  speedValue.textContent = config.speed;
});

fontSizeRange.addEventListener('input', (e) => {
  config.fontSize = parseInt(e.target.value);
  fontSizeValue.textContent = config.fontSize;
  columns = updateCanvasSettings();
  drops = Array(columns).fill(0);
});

densityRange.addEventListener('input', (e) => {
  config.density = parseInt(e.target.value);
  densityValue.textContent = config.density;
  columns = updateCanvasSettings();
  drops = Array(columns).fill(0);
});

function draw() {
  ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = config.color;
  ctx.font = `${config.fontSize}px monospace`;

  const spacing = config.fontSize / config.density;

  for (let i = 0; i < columns; i++) {
    const char = config.charSet[Math.floor(Math.random() * config.charSet.length)];
    ctx.fillText(char, i * spacing, drops[i] * config.fontSize);

    if (drops[i] * config.fontSize > canvas.height || Math.random() > 0.975) {
      drops[i] = 0;
    }
    drops[i]++;
  }

  setTimeout(() => requestAnimationFrame(draw), config.speed);
}

draw();

总结

通过简单的 HTML、CSS 和 JavaScript,你不仅可以复刻经典的黑客帝国代码雨,还能通过加入控制面板让这个特效更加灵活和炫酷。如果你喜欢这篇文章,不妨点赞、收藏或分享,让更多人看到!

🎉 快去尝试下吧,制作属于你的代码雨特效!