canvas 实现代码雨

113 阅读1分钟

效果

image.png

实现

首次渲染,每列的字母下落的速度是否需要一致(实际上这么形容是不严谨的,但是这样从视觉的角度来描述更方便理解)具体见 注释@Mark1

重绘的过程可以放在requestAnimationFrame()中,这样性能上会更好,但是缺点是:由于rAF的触发频率是根据浏览器的渲染帧率来决定的(一般是60次/s),所以导致重绘函数的调用间隔无法由我们自定义,见 注释@Mark2

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>代码雨背景</title>
  </head>
  <canvas id="canvas"></canvas>

  <script>
    let intervalId
    function initCanvas() {
      const can = document.getElementById('canvas')
      const winWidth = window.innerWidth
      const winHeight = window.innerHeight

      can.width = winWidth
      can.height = winHeight

      const ctx = can.getContext('2d')
      const columnsWidth = 20
      // 列数
      const columnsCount = ~~(winWidth / columnsWidth)

      //  @Mark1 记录每列写到了第几个文字
      //  采用下面方式初始化columNextIndex数组的话,首次渲染时每列字母将处在同一水平线上
      // const columNextIndex = new Array(columnsCount).fill(1)
      
      // 采用下面方式初始化columNextIndex数组的话,从首次渲染开始每列字母的垂直位置(y值)就将随机
      const columNextIndex = Array.from(
        { length: columnsCount },
        (item) => (item = ~~(Math.random() * 100))
      )
      // console.log('columNextIndex', columNextIndex)
      
      const getRandomColor = () => {
        const r = Math.floor(Math.random() * 256)
        const g = Math.floor(Math.random() * 256)
        const b = Math.floor(Math.random() * 256)
        const color = `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`
        return color
      }

      const getRandomLetter = () => {
        const str = 'abcdefghijklmnopqrstuvwxyz'
        return str[Math.floor(Math.random() * str.length)]
      }
      
      // 字体大小,自定义即可
      const fz = 20
      
      // 主要绘制函数
      const draw = () => {
        ctx.fillStyle = `rgba(240,240,240,0.2)`
        ctx.fillRect(0, 0, winWidth, winHeight)
        ctx.fillStyle = getRandomColor()
        ctx.font = `${fz}px "Roboto Mono"`
        for (let i = 0; i < columnsCount; i++) {
          const x = i * columnsWidth
          const y = fz * columNextIndex[i]
          ctx.fillText(getRandomLetter(), x, y)
          // 如果不添加上Math.random()>0.9,那么每列上渲染出来的字母位置将始终处于同一水平线上
          if (y > winHeight && Math.random() > 0.9) {
            columNextIndex[i] = 0
          } else {
            columNextIndex[i]++
          }
        }
      }
      // //  @Mark2 使用闭包函数配合RAF实现画布的持续重绘,缺点:不能自定义渲染间隔
      // !(function animationLoop() {
      //   draw()
      //   requestAnimationFrame(animationLoop)
      // })()
      if (intervalId) clearInterval(intervalId)
      intervalId = setInterval(() => draw(), 60)
    }
    
    // 当窗口大小改变时触发画布重绘
    const winResizeObserver = new ResizeObserver((entries) => {
      initCanvas()
    })
    winResizeObserver.observe(document.querySelector('body'))
  </script>
  <body></body>
</html>