【canvas】仿黑客帝国数字雨 404 页面

这个效果从我刚转行的时候就见过了,那时候觉得效果很棒,也一直收藏着。今天无事,就花点时间把这个效果自己实现了一遍,也算是回顾 canvas 开发细节。

效果

先上效果图,感觉还是挺不错的。

分析

从上图中,我们可以看到,每一列中,每个字符都没有相互覆盖,每一列也没有覆盖,所以我们可以把这些字符的位置坐标化,用一个二维数组来存储这些信息。

然后就是下雨的渐隐效果,其实也很简单,每一次重绘的时候先用一个具有透明度的颜色去覆盖整个画布,然后再绘制字符,这样就能实现渐隐效果。

最后就是中间的红色 404 三个大字,这里就需要我之前实现过的一个函数,将文字像素化,得到一个尺寸和上线提到的记录字符位置相同的二维数组,数组存储的是文字的像素信息。然后通过坐标读取像素数组,如果有颜色,就用红色显示;无颜色就用黄色显示。

实现

说的再多不如看代码直接,还是直接上代码吧

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas 仿黑客帝国 404 界面</title>
    <style>
        html, body {
            width: 100%;
            height: 100%;
            margin: 0;
            overflow: hidden;
        }
        #canvas {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        function RainBy404(elc, str, size){
            if(typeof elc == "string") elc = document.querySelector(elc);

            // 动画函数
            var fastAnimation = function (fn) {
                return requestAnimationFrame ||
                        webkitRequestAnimationFrame ||
                        mozRequestAnimationFrame ||
                        function (callback) {
                            setTimeout(callback, 1000 / 60);
                        };
            }();

            // 文字像素化
            function str2matrix(str, w, h){
                var matrix = [];
                for(var i = 0; i < w; i++){
                    matrix[i] = [];
                    for(var j = 0; j < h; j++){
                        matrix[i][j] = 0;
                    }
                }

                var canvas = document.createElement("canvas");
                canvas.width = w; 
                canvas.height = h;
                var context = canvas.getContext("2d"),
                    fontSize = w * 0.8 / str.length,
                    imgData = [];

                context.fillStyle = "white";
                context.fillRect(0, 0, w, h);

                context.fillStyle = "black";
                context.font = fontSize + "px Calibri";
                context.textBaseline = "middle";
                context.fillText(str, (w - context.measureText(str).width) / 2, h / 2);

                try {
                    imgData = context.getImageData(0 ,0, w, h).data;
                } catch(e){

                }

                for(var i = 0; i < imgData.length; i+=4){
                    r = imgData[i];
                    g = imgData[i+1];
                    b = imgData[i+2];
                    gray = r*0.2126 + g*0.7152 + b*0.0722; //RGB转YUV

                    matrix[parseInt(i / 4 % w)][parseInt(i / 4 / w)] = gray < 250 ? 1 : 0;
                }

                return matrix;
            }

            // 定义水滴对象
            function Drop(str, x, y, rows){
                this.i = 0;
                this.s = str;
                this.x = x || 0;
                this.y = y || 0;
                this.rows = rows;
            }
            Drop.prototype = {
                constructor: Drop,

                getChar: function(){
                    return this.s.charAt(this.i % this.s.length);
                },

                reset: function(){
                    this.i = 0;
                    this.y = 0;
                },

                drop: function(){
                    this.i++;
                    this.y++;
                    if(this.y > this.rows && Math.random() > 0.975) this.reset();
                }
            }

            // 初始化参数
            var cols = Math.round(elc.width / size);
            var rows = Math.round(elc.height / size);
            var drops = [];
            var matrix = null;
            var context = elc.getContext("2d");

            function draw(){
                // 画布覆盖
                context.fillStyle = "rgba(0,0,0,0.05)";
                context.fillRect(0, 0, elc.width, elc.height);

                // 水滴描绘
                drops.forEach(function(drop){
                    context.fillStyle = (matrix && matrix[drop.x][drop.y]) ? "red" : "yellow";
                    context.font = size + "px Calibri";
                    context.fillText(drop.getChar(), drop.x * size, drop.y * size);
                    drop.drop();
                })

                // 重复绘制
                fastAnimation(draw);
            }

            function start(){
                // 生成水滴
                for(var i = 0; i < cols; i++){
                    var drop = new Drop(str, i, 0, rows);
                    drops.push(drop);
                }

                // 生成像素信息
                matrix = str2matrix(str, cols, rows);

                // 画布覆盖
                context.fillStyle = "black";
                context.fillRect(0, 0, elc.width, elc.height);
                // 重复绘制
                draw();
            }

            return {
                start: start
            }
        }

        var canvas = document.querySelector("#canvas");
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        var rain = new RainBy404(canvas, "404", 12);
        rain.start();
    </script>
</body>
</html>