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