Canvas如何实现代码雨

1,334 阅读3分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

前言

走得最慢的人,

只要他不丧失目标,

也比漫无目的地徘徊的人走得快。

—— 莱 辛

介绍

相信好多刚入行的小伙伴都见过代码雨效果,当时是不是觉得跟那些大神望尘莫及,我也不例外,感觉一辈子都追逐不上,其实只要脚踏实地坚持不懈的走,加上一些自信心那么也自己可以实现各式各样的效果。意思就是大家稳住不要慌,猥琐发育下。

VID_20210825_085103.gif

这是我入行前见到的第一个前端效果,最近实现了下,发现是众多效果动画种最简单的一个,我们本次的代码雨效果将从结构搭建,文字参数,绘制文字下落等步骤来实现。

出发

1.结构搭建

<canvas id="canvas"></canvas>
<script type="module" src="./app.js"></script>

我们还是放个画布元素,再通过module模式来引入主逻辑。

* {
    padding: 0;
    margin: 0;
}

html,
body {
    width: 100%;
    height: 100vh;
    position: relative;
    overflow: hidden;
}
#canvas {
    width: 100%;
    height: 100%;
}

再写点css使其填充满屏幕。

/*app.js*/
class Application {
  constructor() {
    this.canvas = null;         // 画布
    this.ctx = null;            // 环境
    this.w = 0;                 // 画布宽
    this.h = 0;                 // 画布高
    this.size = 18;             // 字体大小
    this.codeList = [];         // 位置列表
    this.init();
  }
  init() {
    // 初始化
    this.canvas = document.getElementById("canvas");
    this.ctx = this.canvas.getContext("2d");
    window.addEventListener("resize", this.reset.bind(this));
    this.reset();
    this.render();
  }
  reset() {
    // 窗口尺寸改变
    this.w = this.canvas.width = this.ctx.width = window.innerWidth;
    this.h = this.canvas.height = this.ctx.height = window.innerHeight;
  }
  render() {
    // 主渲染
    this.step()
  }
  drawCodeRain() {
     // 绘制代码雨
  }
  drawBackground() {
     // 绘制过渡背景
  }
  step(delta) {
    // 重绘
    requestAnimationFrame(this.step.bind(this));
    this.drawBackground();
    this.drawCodeRain();
  }
}

window.onload = new Application();

2.文字参数

我们绘制代码文字先要考虑,他有那些文字,这里我们也可以自己定义,当然我为了更简洁(偷懒),用代码生成的字符串,后面绘制字体就直接说了。我们前期定义的只有他的初始化位置和生成数量。

/*app.js*/
reset() {
    this.w = this.canvas.width = this.ctx.width = window.innerWidth;
    this.h = this.canvas.height = this.ctx.height = window.innerHeight;
    const { w, h, ctx, size, codeList } = this;
    ctx.fillStyle = 'rgba(0,0,0,1)';
    ctx.fillRect(0, 0, w, h);
    codeList.length = 0;
    for (let i = 0, len = ~~(w / size); i < len; i++) {
        codeList.push({
            x: i,
            y: ~~(Math.random()*h*.05)*-size
        })
    }
}

我们把这些参数的生成定义在初始化里的窗口改变事件种,这样我们每次改变窗体都会重新定义当前的位置和数量并且覆盖一张黑背景。获取数量我们就利用当前屏幕宽度除以当前字体大小,就会获得一屏填充满需要多少个了。这样x轴就用i做标记,y轴后面绘制会讲他是逐个累加的,但是初始化期望是让他起始位置不同,这样看上去更散乱一些,不是一开始都从一个位置出发到结束。

3.文字绘制

/*app.js*/
drawCodeRain() {
    const { w, h, ctx, codeList, size } = this;
    ctx.fillStyle = '#0f0';
    ctx.font = `${size}px serif`;
    codeList.forEach(point => {
        let _code = String.fromCharCode(97 + ~~(29 * Math.random()));
        ctx.fillText(_code, size * point.x + size/2, point.y);
        if (point.y > size * h * Math.random() + h / 3) {
            point.y = 0;
        } else {
            point.y += size;
        }
    })
  }

我们这里要绘制代码字,所以我们更习惯用绿色,然后把字体大小也写上去。再由刚才的生成的文字位置数组去遍历他,这里就用了刚才为了偷懒弄的fromCharCode通过对应ascii码去生成文字(97代表字母a),从里面随机取个值。然后绘制上。绘制完毕后当前列的y轴会增加一个固定值使其下次绘制就到了下一行位置依次类推。当然我们不会让他无限进行下去,所以我们给他一个随机高度值,达成条件后再从0开始绘制。

此时我们发现,一个问题:

微信截图_20210825093829.png

字体是有了,但是他不消失,重叠到一起了。我们如何解决呢?

这里有两个思路,一个是绘制文字加个透明动画的定时器。

还有个最简单的利用绘制一个透明黑色背景层不断盖住他,障眼法来实现。

我们接下来用第二种方案:

/*app.js*/
drawBackground() {
    const { w, h, ctx } = this;
    ctx.fillStyle = 'rgba(0,0,0,.075)';
    ctx.fillRect(0, 0, w, h);
}

每次绘制盖一层0.075透明的度黑层,就这样我们实现了效果灰长滴简单吧~


这个效果蛮唬人的,但是特别简单就这样实现了,在线演示

拓展

我们利用叠层这种障眼法可以完成很多效果,比如水果忍者的拖尾效果,比如烟花粒子的效果。可以去尝试,蛮有成就感的~


当初刚入行被这样的效果唬住吓尿了,但是胆子大一点给自己点自信,这样的效果其实也就那回事~~