源码解读——Matrix-Code-Rain
最近自己的博客刚刚建起来,想好好经营一下。内容比较少,另外希望能产出一些高质量的文章,所以不想将CSDN博客上的文章迁移过来。那么就得自己发点干货了。废话不多说,转入正题。
数个月前在github上阅读过一个小项目的源码——Matrix-code-rain。其效果是黑客帝国中代码在屏幕上从上至下滑落。DEMO见此链接。如下截图所示即代码雨效果图:
首先观察效果图,大致分为三个部分:
左上角的帧频监测模块
、左下角的快照工具栏
、代码雨
主体。
项目的index.html文件中的body部分包含了两个主要的元素:
- id="info"的div元素
- id="canvas"的画布
前者提供了快照
功能,以旋转方式显示/隐藏的特效(该效果是以CSS3实现的);后者实现了帧频检测
功能以及主效果代码雨
。
下面先把JS主干代码贴出,并做简要解读。
主干代码
这里仅标出代码主干,具体细节请查看源码。代码的主干很简单,如下所示:
1 | var stats = new Stats(); |
先简单分析一下主干代码,然后再分析具体细节:
开头五行代码,引用了一个外部JS文件(stats.min.js)定义的一个构造函数Stats()
,然后初始化一个对象,这个对象的功能就是帧频检测
,最后把它放到左上角。
接着初始化一个对象M,这个对象内部定义了很多属性和方法:
1 | var M = { |
然后定义一个添加事件监听的函数eventListenerz
。
当页面加载后执行如下代码:
1
2
3
4window.onload = {
M.init(); // 初始化M
eventListenerz(); //添加事件监听
};M.init()
做了以下这些事:
- 将canvas元素赋值给M.c;
- 获取画布上的绘图环境,并赋值给M.ctx(后面称之为
画布
); - 获取页面的高度、宽度并设置画布的高度和宽度,让画布充满整个页面;
- 设置画布背景色为黑色;
- 设置画布的字体为
30px matrix-code
; - 创造屏幕质感,画一条条的横线。这里动态创建了一个canvas元素,并设置画布的宽高与页面一致,通过调用
M.createLines()
方法来绘制满屏的横线,其实为了效果更好每条横线下面紧挨一条颜色更淡的横线,达到色差缓冲的效果;
- 根据网页的宽度、预设的canvas宽度,计算网页横向能放多少个canvas;
- 针对每一栏canvas初始化一个codes数组,数组的0索引的值是一个对象:
1 | { |
- 调用
M.loop()
方法,该方法内部调用了requestAnimationFrame
,并把其任务ID赋值给M.animation
。然后是loop方法的主体,调用M.draw()
——想必就是绘制代码雨的效果。此外,同时更新帧频检测器
的数据状态,达到循环动画的效果。 - 调用
M.createCode()
方法,该方法什么作用?请继续往下阅读。
这时候,代码的大体流程已经知道了,我们只要了解M.draw()
是如何绘制代码雨,以及M.createCode()
是如何初始化的即可。
代码雨
先来看我绘制的一张图,改图简要的介绍了代码雨的组成,在看具体分析之前大家可以先自己想想其实现方式。
要了解代码雨原理,首先了解M.draw()
是如何工作的。
M.draw()
M.draw()
做了以下工作:
- 清理画布,避免之前绘制的图像遗留在画布上产生重影;
- 设置如何将新图像绘制到已有图像之上,默认为
source-over
; - 对每一canvas进行处理。
1). 当其codes[0]包含canvas属性时,获取其速度值、canvas的高度,x、y坐标,canvas元素,canvas画布。然后根据其位置将这个canvas添加到主canvas上。
a. 当y坐标小于网页高度时(即canvas的y坐标还在网页范围内),更新y坐标(y减去速度值); b. 否则,将y坐标设为0,这就达到了同一列不停的循环的效果。
看到第3步,对比代码主干一节中第8步,我们发现:M.draw()
阶段时每一栏的canvas的codes数组根本没有canvas属性。这种情况下,第3步中的处理条件根本无法达到。所以肯定缺少初始化的一步,这肯定包含在M.createCode()
中,其实看方法名也能看出来。
M.createCode()
M.createCode()
做了以下工作:
- 判断
M.codesCounter
是否大于canvas列数。如果是,清除M.createCodeLoop的定时任务,并返回。否则,继续往下执行。推断一下:毕竟网页横向被分解成了很多个canvas,有多少个canvas,M.createCode()
就会被调用多少次吧。
- 给局部变量randomInterval赋值为
M.randomFromInterval(0,100)
的执行结果。直接跳到这段代码看看,哦,生成一个0到100之间的随机数。
- 给局部变量colum赋值为
M.assignColumn()
的执行结果。直接跳到这段代码看看:随机获取一个canvas的索引,如果该canvas的codes[0]的open属性为true,则置为false,并返回canvas的索引;否则直接返回false。仔细想想,这段代码把open置为false后,并没有还原成true。
- 根据column索引值,对对应一列的canvas进行处理。
- 随机获取一个代码雨长度值,并赋值给codeLength;
- 随机获取一个代码雨速度值,并赋值给codeVelocity;
- 获取代码雨字符表的长度,并赋值给lettersLength;
- 设置该canvas的codes[0].position属性,起始的x坐标与列索引有关,y坐标都为0;
- 设置该canvas的codes[0].velocity属性,为随机获取的速度值;
- 设置该canvas的codes[0].strength属性,为其速度/速度上限值,这个到底什么作用呢?先放着继续往下看;
- 根据代码雨长度值,获取相应数量的字符,这是通过在字符表中随机获取的,并将字符依次赋值给该canvas的codes[1], codes[2]...
- 调用
M.createCanvii(column)
。这里把canvas的列索引值传递进去了,想必就是进行绘制操作了。 M.codesCount
++,这一步验证了第1步的猜想。
- 根据局部变量randomInterval来设置另一列canvas的初始化。
M.createCanvii()
上面一节第8步对M.createCanvii(col)
的作用进行了猜想,下面我们来看看是不是符合我们的猜想。
M.createCanvii(col)
做了以下工作:
- 获取该列canvas要显示的字符数,赋值给codeLen;
- 获取该列canvas的高度,通过字符数*每个字符的高度即可得到;
- 获取该列canvas的速度,通过codes[0].velocity即可得到;
- 获取该列canvas的strength,此时我们还是不知道这是个什么参数;
- 创建一个canvas元素,并获取其画布环境,并设置其宽度和高度;
- 根据codeLen,绘制所有字符,这里并不是单纯的绘制。前5个和最后4个不太一样,哪里不一样呢?之前不明白其含义的strength出现了,原来是为了让两端的颜色变淡一些。
- 绘制完毕后,将该列canvas的codes[0].canvas的值赋为这里绘创建的canvas元素。哦,这时候,M.draw()一节的第3步的条件就成立了,就可以把这个创建的canvas添加到网页的主canvas了。
现在一切就明了了,接下来有兴趣的话可以看看快照工具栏
的效果实现。
快照工具栏
先来看看CSS3提供的几个动画方法和特性:
transform: none | transform-functions
1
2
3
4
5
6matrix(): /*定义转换*/
translate(): /*原点坐标偏移*/
scale(): /*缩放*/
rotate(): /*沿轴旋转*/
skew(): /*沿轴倾斜*/
perspective(): /*定义透视*/
transition: property duration timing-function
delay
1
2
3transition-property: none|all|property;
transition-duration: time(s/ms);
transition-timing-function: linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n)
很明显,快照工具栏的旋转显示/隐藏方式是以tramsform
分别对显示时定义一个状态
,隐藏时定义一个状态
,然后通过CSS3的transition
属性来进行状态切换设置。
果然代码中也是以这种方式实现的:
显示状态
1 | transform-origin: bottom center; |
隐藏状态
1 | transform: rotate(180deg); |
状态转移方式
1 | transition: transform 1s ease-in-out; |
两个状态、转移方式都定义好了,那么就可以通过事件了切换这两者的状态了。用toggle的方法来添加/删除类来达到状态切换的效果。果然,JS代码中有一个函数eventListenerz()
就包含了状态切换的处理。
此外,快照工具栏还有一个主要功能:
快照。还是在上面那个函数里面,包含了这两行代码:
1
2var snapshotBtn = document.getElementById('snapshot');
snapshotBtn.addEventListener('click', M.snapshot, false);快照
按钮的buttn元素;第二行代码则是对该按钮添加了一个click事件监听以及相应的回调函数。
M.snapshot
是其内部定义的一个方法。其源码如下:
1
2
3
4snapshot: function(){
M.createLines(M.ctx);
window.open(M.c.toDataURL());
}toDataURL()
方法,该方法将canvas进行转化成一个特定格式的图片(默认PNG),并返回一个data
URI。最后新开一个窗口显示该图片。
(完)