这个效果从我刚转行的时候就见过了,那时候觉得效果很棒,也一直收藏着。今天无事,就花点时间把这个效果自己实现了一遍,也算是回顾 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>