Skip to content
📈0️⃣

代码雨

1. 效果

1.1. 代码雨效果演示

字体:  速度:  持久: 

1.2. 工具栏配置

字体: 代码雨代码大小 速度: 代码雨速度 持久: 代码飘落持续度

2. 实现

2.1. 实现步骤

代码雨是一种炫酷的动态效果,可以通过使用 HTML5 <canvas> 元素和 JavaScript 来实现。在以下的代码中,我们使用了 Vue 3 的 <script setup> 语法来编写组件逻辑。

首先,在模板中,我们创建了一个具有特定类名 .canvas-wrap 的容器元素,用于容纳 <canvas> 元素和工具栏。然后,我们在 <canvas> 元素上设置了 ID 为 "canvas"。

接下来,在 <script setup> 部分,我们引入了 onMountedref 方法,它们都是 Vue 3 提供的功能。我们创建了三个响应式引用:fontSizespeedlong,分别用于控制字体大小、下落速度和持续时间。

然后,我们定义了一个 initCanvas 函数,用于初始化画布。在该函数中,我们首先通过 querySelector 获取到容器元素和 <canvas> 元素,并设置了画布的宽度和高度为容器元素的宽度和高度。

接下来,我们使用 <canvas> 的上下文对象 getContext("2d") 获取 2D 渲染上下文。然后,我们清除画布,以便绘制新的帧。

然后,我们设置了文字的颜色、字体大小,并使用循环遍历数组 arr,该数组长度等于容器宽度除以字体大小,用于存储每个字符下落的位置。

在循环中,我们使用 fillText 方法绘制随机选取的字符,在合适的位置上,初始位置为 index * 字体大小item + 字体大小。其中,item 为数组中的元素,表示每个字符下落的位置。

然后,我们更新数组中的元素值,如果字符超出容器高度或随机数大于持续时间,则重置其位置为 0;否则,将字符位置增加字体大小。

最后,我们根据设定的速度刷新画布,可以通过 setTimeout 或 requestAnimationFrame 来实现动画效果。

onMounted 生命周期钩子函数中,我们调用了 initCanvas 函数,以确保在组件挂载后执行初始化画布的操作。

此外,我们还提供了一个工具栏,可以通过 Vue 的双向数据绑定来控制代码雨效果的参数,例如:字体大小、下落速度和持续时间。点击“刷新”按钮时,会重新初始化画布,从而应用新的参数设置。

通过以上步骤,我们成功实现了一个简单的代码雨效果!

2.2. 代码

vue
<script setup>
import { onMounted, ref } from "vue";
const [fontSize, speed, long] = [ref(8), ref(40), ref(960)];
const text = "abcdefgasdkflasdflasfkls8491042890128039".split("");

const initCanvas = () => {
  const wrap = document.querySelector(".canvas-wrap");
  const arr = new Array(Math.ceil(wrap.clientWidth / fontSize.value)).fill(0);

  const canvas = document.querySelector("#canvas");
  canvas.width = wrap.clientWidth;
  canvas.height = wrap.clientHeight;
  const ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);

  const initRain = () => {
    ctx.fillStyle = "rgba(0, 0, 0, 0.05)"; // 填充背景颜色
    ctx.fillRect(0, 0, canvas.clientWidth, canvas.clientHeight); // 背景
    ctx.fillStyle = "rgba(0, 255, 0, 1)"; // 文字颜色
    ctx.font = `${fontSize.value}px Arial`;
    arr.forEach((item, index) => {
      ctx.fillText(
        text[Math.floor(Math.random() * text.length)],
        index * fontSize.value,
        item + fontSize.value
      );
      arr[index] =
        item > canvas.clientHeight || Math.random() * 1000 > long.value
          ? 0
          : item + fontSize.value;
    });
    speed.value > 0
      ? setTimeout(initRain, speed.value)
      : requestAnimationFrame(initRain);
  };
  initRain();
};

onMounted(() => {
  initCanvas();
});
</script>
vue
<template>
  <div class="canvas-wrap">
    <canvas id="canvas"></canvas>
    <div class="tools">
      字体:<el-input-number
        v-model="fontSize"
        :min="1"
        :max="50"
        size="small"
      />&nbsp; 速度:<el-input-number
        v-model="speed"
        :min="0"
        :max="1000"
        :step="10"
        size="small"
      />&nbsp; 持久:<el-input-number
        v-model="long"
        :min="0"
        :max="1000"
        :step="10"
        size="small"
      />&nbsp;
      <el-button @click="initCanvas" size="small">刷新</el-button>
    </div>
  </div>
</template>
vue
<style lang="scss">
.canvas-wrap {
  width: 100%;
  height: calc(100vh - 135px);
  border: 1px solid silver;
  box-sizing: border-box;
  font-size: 12px;
  .tools {
    padding: 5px 10px;
    margin-top: 5px;
    border: 1px solid silver;
  }
}
</style>