持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情
前言
看了这么多 CSS 实现的效果,想必大家也看累了,今天我们来玩玩 Canvas —— 使用 canvas
绘制数字雨效果。
Canvas 可能有的小伙伴不怎么了解,它提供了一个通过 Javascript 和 HTML 的 canvas
元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。一般用于 2D 元素。
思路分析
接下来我们一步一步的分析实现过程。
初始化样式
首先为了美观以及统一,我们需要先将样式初始化一下:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #1d1f20;
min-height: 100vh;
overflow: hidden;
}
通配符 *
不建议大家在项目中使用哦,它是 效率很低 的一个操作符。我们可以使用 reset.css 或者 normalize.css 来统一我们的样式,使得各个浏览器展现效果一致。
这里不得不多提一嘴,咱们 码上掘金 的设置里,也是有这两个样式初始化的选项的,好!
创建 Canvas 标签
咱们可以通过 canvas
标签来创建一个画布。整个 HTML 就这行了,嫌麻烦的小伙伴们有福了嗷。
<body>
<canvas id="c"></canvas>
<body>
创建画布
有了 canvas
标签后,咱们可以通过 getContext
方法创建一个 context
,后续的绘制都是在 context
上进行的。
<script>
const c = document.querySelector("#c"); // 获取 canvas
const ctx = c.getContext("2d"); // 获取这个元素的 context——图像稍后将在此被渲染。
</script>
需要注意的是,如果不显式设置大小的话,canvas
画布的宽高分别是 300px 和 150px,咱们要创建的画布是屏幕大小,也就是所谓的 视口大小 ,可以通过 window.innerHeight
和 window.innerWidth
获取。
c.height = window.innerHeight;
c.width = window.innerWidth;
咱们看看效果:
没啥问题,同视口大小。接下来我们要实现数字雨的效果啦,激不激动!
数字雨
行列值
由于我们的数字雨是由 01 随机排列 组成的,因此我们创建一个数组 words = ['0', '1']
。
同时为了将视口大小的画布都铺满雨,我们要计算列数,这个列数怎么计算呢?
我们仔细想想,一个字的大小是多少?浏览器默认是 12px,为了眼睛不那么容易看花,我们设置为 16px。那视口能够放的数字列数是不是就是 c.width / 20 了?
const words = ["0", "1"];
const fontSize = 16;
const columns = Math.floor(c.width / fontSize);
这里我们要注意使用 Math.floor
向下取整。
随机抽取 01
我们的数字雨中,01 是随机排列的,因此我们要写一个函数,随机抽取 0 或 1。刚刚我们有个 words
数组存放了 01,我们只需要使用 Math.random
函数随机在 0 和 1 这两个值上就好了:
const getText = (words) => {
return words[Math.floor(Math.random() * words.length)];
};
数字雨本雨
大家是不是以为数字雨是一列一列画的?不是的,其实它是一行一行的绘制的。
具体的,我们第一次绘制第一行,然后一定时间间隔后绘制第二行,一直绘制到行高溢出视口高度后重新开始。
首先我们设置一个行数组 drops = new Array(columns).fill(0)
。
接下来为了实现 每次刷新都能绘制一行 的效果,我们需要使用 setInterval
函数。
const draw = () => {
ctx.fillStyle = "rgba(0,0,0,0.05)";
ctx.fillRect(0, 0, c.width, c.height);
};
setInterval(draw, 120);
细心的小伙伴应该注意到了 draw
函数里的两行代码,那是为了每次绘制时,画的东西不重叠,所以每次绘制之前,都要将整个画布都清空。fillRect
函数将画布以一个矩形来清空。
我们看看如果不加这两行会是什么效果:
接下来我们理理思路。
刚刚我们提到,数字雨实际上是一行一行绘制的,那么怎么绘制呢?这里我们要用到 fillText
函数来绘制文字。它可以接收三个参数,分别是待绘制的数字、绘制的 x 轴的位置以及绘制的 y 轴的位置。待绘制的文字我们可以用刚刚的 getText
函数获取。
为了实现每次一行的效果,我们可以用一个 for
循环,待绘制文字的 x 轴位置就是第几列,可以用 当前待绘制文字的索引 * fontSize 得到,同理,y 轴就是 当前行数 * fontSize 。
一开始 drops
数组都是第 0 行,随着绘制刷新不断递增,直到超出屏幕时重新从第 0 行开始绘制。
const draw = () => {
...
ctx.fillStyle = "red";
ctx.font = `${fontSize}px arial`;
for (let i = 0; i < drops.length; ++i) {
ctx.fillText(getText(words), i * fontSize, drops[i] * fontSize);
if (drops[i] * fontSize > c.height)
drops[i] = 0;
drops[i]++;
}
};
其中 fillStyle
可以设置待绘制的颜色。而 ctx.font
可以设置待绘制文字的大小和字体。
我们看看此时的效果:
可以嘛,这雨整整齐齐的。为啥呢?
因为我们是一行一行绘制的,因此它们必然是同时溢出视口的,意味着它们也会同时从第 0 行开始。
所以我们要使一些绊子。
我们不仅是超出屏幕时重新从第 0 行开始,还要给个随机数,超过这个随机数也要重新开始!
for (let i = 0; i < drops.length; ++i) {
...
if (drops[i] * fontSize > c.height && Math.random() > 0.95)
drops[i] = 0;
}
我们看看此时的效果:
OK,漏趴笨。
码上掘金
Github 源码地址
关于本文的所有代码及素材,都已上传至 Github 仓库,小伙伴们有需要的自取:
juejin-demo/digit-rain-demo at main · catwatermelon/juejin-demo (github.com)
结束语
本文就到此结束了,希望大家阅读本文能所收获。
如果小伙伴们有别的想法,欢迎留言,让我们共同学习进步💪💪。
如果文中有不对的地方,或是大家有不同的见解,欢迎指出🙏🙏。
如果大家觉得所有收获,欢迎一键三连💕💕。