模拟黑客帝国效果

提要:本demo涉及到CSS3与javascript的综合应用

最终效果:

http://siida.cn/book/demo8.html(请用电脑访问)

为了专注于核心代码,我并没有做兼容,因此请用高版本的chrome浏览器访问。

需求分析:

正如你所见,页面主要由两部分组成,一部分是中间的输入框,另一部分是背景的模拟黑客帝国动画,而难点是背景部分,下面我们就来一步一步对动画部分进行拆解分析。

选型

解决问题的第一步就是根据目标效果进行技术选型:即选择一个可以实现这个效果的最佳技术方案,如果有多种方法均可实现效果,则要从开发时间、性能、兼容性等多方面进行考虑,最终选定最合适的技术方案。在本例中至少有两种方案可以完成目标,一是Javascript+CSS3,另一是canvas,这两种方案的实现思路是完全不同的。在本例中,我们选用了Javascript+CSS3的方案。下面我们就用Javascript+CSS3的思路对本例进行分步实现,虽然动画看起来有些复杂,不过我们可以将复杂的动画分解成一个一个小的步骤,这在软件开发中很常见的技巧。等我们实现完毕,我想你对选择这种方案的原因也一定有了自己的理解。

问题拆解:整体的思路

根据效果,我们发现背景动画是分列的。也就是说,我们可以通过这样的思路实现:写好每一列的循环动画效果——>把每一列的效果通过绝对定位水平排列起来。这个思路是十分宝贵的,将指导你接下来的工作。

问题拆解:单列的效果如何实现

仔细观察其中的任意一列,你会发现下面的几个特征:

  1. 每一列的动画开始时间是随机的,并非所有的文字都齐刷刷的掉落下来。实际上,每一列动画都会在程序准备就绪后的五秒内随机一个开始时间。用怎样的代码实现呢?没错,我们要用setTimeout()来驱动动画开始,并随机一个小于5秒的开始时间。
    setTimeout(function(){
        //动画开始...
    },Math.random() * 5000)
  1. 每一列动画虽然开起来像是文字掉落,实际则不然。

请仔细观察上图,我们可以看到随着单列中动画效果的掉落,在这一列显示的文字是不断变化的。也就是说,文字是原本就写好的,只是被盖住了而已,掉落的并非文字,而是在文字上方的一个显示框(图中的红框)。框内下半部分是透明的,可以完全显示下面的文字,框内的上半部分逐渐变黑,不能显示文字的,随着这个显示框的移动而出现了文字掉落效果。

那么,这样的动画该如何实现呢?我们依然要分步解决

第一步,显示文字。

文字的竖排显示是我们将要面临的第一个难点。对于中文来说,只要文字的容器宽度被限定,那么文字如果显示超过容器宽度则会自动换行。所以,我们可以将文字容器的宽度设为文字宽度,使每个字都占满一行,实现竖排显示文字。对于英文来说,多个相邻的英文字母会被视作一个单词,为了完善阅读体验,即使超出了父容器的宽度也不会换行,直至遇到空格(表示这个单词结束)为止。而我们要求每个字母都换行,所以可以在生成字符串时即在每个字幕后增加一个空格。

如下是一列竖排字母对应的HTML和CSS代码

.code{font-size: 20px;font-weight: bolder;width:20px; line-height:1em;color: green;position: absolute;text-align: center;}
<div class="code"></div>

如下是生成随机字符串的函数,我们函数的返回结果直接使用innerHTML填入上面的div中,再将div放入body中即可。

    function getRanText(size){
        //根据窗口高度和字体计算每个字符串要包含多少个字母
        var textNum = Math.floor(window.innerHeight/window.fontSize) + 5;
        var str = "";
        for(var i=0;i < textNum;i++){
            //使用ASCII码随机生成26个字母
            var ascii = Math.floor(Math.random()*26) + 65;
            str += String.fromCharCode(ascii) + " ";
        }
        return str;
    }

将生成的竖排文字放入文档

    function createText(left){
        //获取随即字符串,传入窗口高度和每个字的大小
        var text = getRanText();
        //以下代码创建了一个div并放入body下
        var div = document.createElement("div");
        div.setAttribute("class","code");
        div.style.left=left+'px';
        div.innerHTML = text;
        document.body.appendChild(div)
    }

第二步:文字动画原理

首先,我们已经有了一列文字:

然后,我们需要用一个框体codeShadow,使其上下边框都与背景同色,与窗口同高,将文字盖住。

然后通过移动codeShadow,实现动画效果。

至于这段过程的实现思路是:使用绝对定位将显示框和文字定位到一起,使得文字被覆盖,同样使用绝对定位制作CSS3动画使显示框下移,实现效果。

至此我们已经能理解了大部分动画的思路。只是还有一点,那就是在目标效果中,文字是渐变的,每个文字都随着覆盖框的移动而逐渐变暗。这该如何实现呢?

实际上,这只需要一个CSS3渐变即可实现。背景属性支持背景渐变,比如,对于一个宽高都为100px的div,只需下面一行代码,即可实现背景的渐变。

<div style="background: linear-gradient(to right,lightgreen, lightblue)"></div>

如你所见,liner-gradient的基本用法包含三个参数,分别是渐变方向、原始颜色、渐变后的颜色。

所以我们也可以设置覆盖框的渐变为方向向上,原始颜色为透明色(可以显露被遮盖的文字),渐变后的颜色为黑色,和背景一致。即实现了目标动画效果。

.codeShadow{background: linear-gradient(to top,transparent, #000);}

第三步:动态设置CSS动画效果

接下来即可设置一个动画用于控制每列文字的动画效果,由于不同环境窗口高度不同,而CSS不能检测浏览器属性,所以需要用javascript动态设置CSS动态效果,下面的代码是一种很常见的动态设置CSS动画的技巧。

    /**
     * 创建动画效果
     * @param  {[type]} height [显示框高度]
     */
    function createAni(height){
        //创建动画
        var style = document.createElement("style");
        style.innerHTML = '@keyframes codeFlow{' +
            '0%{bottom:0}' +
            '100%{bottom: -'+ (height + window.innerHeight) +'px}' +
        '}';
        document.head.appendChild(style);
    }
    //这段代码调用一次即可

本例中的所有难点都在上面讲过,接下来是全部代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CSS3与javascript的共同应用</title>
    <style>
        html,body{height: 100%;margin:0;}
        body{background-color: #000;overflow: hidden;}
        .input{position: absolute;left: 50%;top: 50%;transform: translate(-50%,-50%);width: 500px;height: 80px;text-align: center;font-size: 50px;background-color: #000;border: none;color: #fff;outline:none;border-bottom: 1px solid #eee;z-index: 100;background-color: transparent;}
        .code{font-size: 20px;font-weight: bolder;width:20px; line-height:1em;color: green;position: absolute;text-align: center;}
        .codeShadow{width: 20px;height: 300px;position: absolute;background: linear-gradient(to top,transparent, #000);bottom: 0;animation-timing-function: linear;}
    </style>
</head>
<body>
    <input class="input" type="text" placeholder="Input Your Username" autofocus="autofocus">
    <script>
    window.shadowLen = 300;
    window.shadowWidth = 20;
    window.fontSize = 20;
    /**
     * 创建特效的函数
     * @param  {[number:pixel]} leftPos [距离屏幕左边的距离,需要调用时随机生成]
     */
    function createEffect(leftPos){
        createText(leftPos);
        createShadow(leftPos,window.shadowLen);
    }
    /**
     * 创建文字内容
     * @param  {[num]} left [距离屏幕左边的距离]
     */
    function createText(left){
        //获取随即字符串,传入窗口高度和每个字的大小
        var text = getRanText();
        //以下代码创建了一个div并放入body下
        var div = document.createElement("div");
        div.setAttribute("class","code");
        div.style.left=left+'px';
        div.innerHTML = text;
        document.body.appendChild(div)
    }
    /**
     * 创建动画效果
     * @param  {[type]} height [显示框高度]
     */
    function createAni(height){
        //创建动画
        var style = document.createElement("style");
        style.innerHTML = '@keyframes codeFlow{' +
            '0%{bottom:0}' +
            '100%{bottom: -'+ (height + window.innerHeight) +'px}' +
        '}';
        document.head.appendChild(style);
    }
    /**
     * 创建阴影和动画效果
     * @param  {[number]} left [阴影距离左边的距离]
     * @param  {[number]} height [显示框高度]
     */
    function createShadow(left,height){
        var div = document.createElement("div");
        div.setAttribute("class","codeShadow");
        div.style.left=left + "px";
        div.style.borderTop = window.innerHeight + 'px solid #000';
        div.style.borderBottom = window.innerHeight + 'px solid #000';
        div.style.animation = 'codeFlow 5s linear infinite';
        document.body.appendChild(div);
    }
    /**
     * 生成随即字符串的函数
     * @param  {[num]} size   [每个字的字体大小,这个和屏幕高度一起用于计算要生成多少个字]
     * @return {[string]}        [最终生成的字符串]
     */
    function getRanText(size){
        //根据窗口高度和字体计算每个字符串要包含多少个字母
        var textNum = Math.floor(window.innerHeight/window.fontSize) + 5;
        var str = "";
        for(var i=0;i < textNum;i++){
            //使用ASCII码随机生成26个字母
            var ascii = Math.floor(Math.random()*26) + 65;
            str += String.fromCharCode(ascii) + " ";
        }
        return str;
    }
    //  开始执行
    window.onload = function(){
        // 创建动画
        createAni(window.shadowLen);
        // 根据屏幕宽度,获取阴影个数
        var shadowNum = Math.floor(window.innerWidth / window.shadowWidth);
        // 依次为每个动画创建随机的开始时间
        for(let i=0;i<shadowNum;i++){
            setTimeout(function(){
                //创建效果
                createEffect(i * window.shadowWidth,window.shadowLen);
            },Math.random() * 5000)
        }
    }
    </script>
</body>
</html>