黑马程序员技术交流社区

标题: JS+定时器实现随机飘雪特效 [打印本页]

作者: 庭院深深深几许    时间: 2019-4-19 10:00
标题: JS+定时器实现随机飘雪特效
本帖最后由 庭院深深深几许 于 2019-4-19 15:03 编辑

  项目介绍
  随机飘雪的网页特效我们经常在一些文艺类网站或博客中看到,再配合背景音乐可以更好地营造温馨的氛围。我们先来看看最终运行效果如图所示。


  我们先来说明一下该项目要实现的功能主要包含哪些方面:
  (1) 通过代码来新增一片或多片雪花。
  (2) 雪花新增的位置是随机的。
  (3) 可以随时开始和暂停雪花的移动。
  (4) 可以删除所有或部分雪花。
  (5) 雪花往下移动的过程中还需要随时补充雪花,这样才能模拟下雪的效果。
  (6) 当雪花移出整个屏幕区域后,不应该继续保留该雪花,应该及时将其删除,否则会浪费浏览器的处理资源。
  (7) 背景音乐的播放。
  (8) 开始按钮和停止按钮应该交替使用,不能多次点击。
  开发思路
  首先,本项目演练所涉及到的知识点虽然逻辑不算复杂,但是面还是比较方的,CSS样式,DOM操作,BOM操作,定时器使用,雪花控制的细节等一应俱全,所以是一个值得大家去认真完成的项目。
  基于对以上8个功能点的梳理,我们来看看其核心实现思路:
  1.新增雪花
  新增一片雪花的操作其实跟新增一个普通HTML元素没有任何本质区别,我们可以通过两种方式来完成。第一种方式是直接通过document.write()方法往页面中输出一个标签,并设置好相应的属性即可。但是这种方式有一个问题,就是当我们往页面中输出内容的时候,页面中的之前存在的元素将会被覆盖,所以这并不是一种良好的解决方案。那么,我们自然会使用到第二种解决方案:通过调用document.createElement()的方法来向任意容器中增加元素,并对该元素设置CSS属性的方式完成元素的增加。在前面章节中,我们在讲解DOM元素的新增的知识点时专门为大家做过演示,所以自然我们会选择这一标准的做法。
  但是此处需要注意的是,我们新增的是一片雪花,而不是简单的一个普通元素,而且还得让该雪花能够移动起来。所以从细节上来说,我们必须先新增一个DIV,并设置相应的属性。同时在该DIV中,我们还得增加一张图片,同时为了保证雪花的真实效果和美观度,该雪花图片必须使用一张透明背景的图片,所以图片必须是PNG格式的。
  2.位置随机。
  要实现一片随机位置的雪花,那么必然我们需要考虑两个核心因素:一是必须使用固定定位,这样才可以实现位置的强制调整;二是必须考虑浏览器窗口的高度和宽度,因为如果让雪花飘在窗口之外没有任何意义。
  那么先来看看关于定位的问题,既然我们是将一片雪花放在一个DIV当中,所以我们只需要对该DIV设置“position: fixed”即可。进而再对其通过设置其left和top属性进行定位。
  另外,关于如何取得浏览器窗口的宽度和高度的问题,在介绍BOM的操作一节,我们为大家提到过,使用window.innerWidth或window.innerHeight即可取得,所以技术上不存在任何难点。关键点在于,取得窗口的最大宽度和高度后,我们还需要基于该数值来生成两个随机数,一个是横向的数值,用于设置雪花的left属性;一个是纵向的数值,用于设置雪花的top属性。这样,一个随机位置的雪花即完成。
  3.开始和暂停。
  当开始让雪花移动时,我们当然需要使用setInterval()定时器来实现该功能。定时器本身就像一个死循环的结构一样,在定时器任务代码中,每触发一次定时器,我们就让所有雪花的top属性基于该片雪花现有的位置再增加几个随机的像素值,这样就可以实现快慢不一的雪花飞舞的效果。
  当然,还得注意一点的是,定时器的时间间隔设为多久,雪花往下移动的单次距离在多少像素的范围内,能够让雪花飞舞的过程看上去更加自然,这是需要我们实现运行代码的过程中进行调试,找到一个最佳效果。其基本原则是在尽可能短的时间内移动的距离也尽可能短,这样可以让飞舞的效果更加平滑。
  4.删除雪花。
  要实现雪花的删除功能,我们首先必须要获取到某片雪花对应的元素,然后调用其方法:remove()即可实现删除。我们可以一次性删除所有的雪花,也可以实现一次性删除部分雪花,这个看我们自己的需要。本项目演练主要为大家提供一个可选的功能而已。
  5.补充新的雪花。
  由于雪花会一直往下移动,最终会消失在浏览器窗口中,所以为了保持雪花一直在下的效果,我们还必须在此过程中不停地自动增加雪花。要实现这一效果,方法有很多,但是其核心目的是,当触发到某个条件时,我们就应该考虑让雪花新增。比如当某片雪花距离浏览器顶部的距离(即元素的标准属性:offsetTop)超过了浏览器窗口的高度(即雪花已经消失在浏览器窗口的可视范围时),我们就应该新增一片雪花补上。也或者是在雪花移动到某个中间位置时,我们就触发新增的操作,进而实现雪一直下的效果。
  6.移出无效雪花。
  这是一个比较简单的实现方法,只需要在计时器代码中对所有雪花的位置进行一下判断,当其offsetTop属性对应的值超过浏览器窗口的高度时即可将该片雪花移除。当然,每当我们移出一片雪花时,我们就应该继续再新增一版雪花补上。
  7.背景音乐。
  背景音乐的使用可以直接使用HTML5自带的标签实现即可,并且设置该音频为自动加载,自动播放,也不需要在界面上显示控制条。
  8.开始停止按钮交替点击。
  首先我们需要明白,为什么开始按钮不能连续点击。因为我们会使用到定时器对象,而每当我们点击一次,就会生成一个新的定时器对象,如果不停地点击,就会生成很多定时器对象,这样会导致每个定时器对象就会去移动雪花,造成的结果就是雪花移动越来越快。
  同样,不停地点击停止按钮将毫无意义。所以将二者进行一下结合,比较好的解决方案就是当页面加载时,让“停止”按钮变成不可用,启用“开始”按钮。当点击“开始”后,“开始”按钮马上变灰,而“停止”按钮可用。同样的,当点击“停止”按钮后,将“停止”按钮变灰,而“开始”按钮变成可用。具体的实现只需要通过设置按钮的disabled属性即可。
  代码实现
  通过对上述开发思路的梳理和分解,相信我们现在的脑海里已经对实现该功能有一个大致的轮廓了。但是咱们不急于一口气做完,一步一步地来实现上述功能的代码。
  1. 完成页面的基本布局和样式设置,同时添加背景音乐,代码如下:

[HTML] 纯文本查看 复制代码

[mw_shl_code=html,true]<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>随机飘雪</title>
    <style>
        body {
            background-image: url("../image/snow-night.jpg");

/* 此处请大家自行在网上挑选一张喜欢的背景图片即可 */
            background-size: cover;   /* 让背景图自适应浏览器窗口大小 */
        }
        input {
            width: 80px;
            height: 30px;
            font-weight: bold;
        }
    </style>
</head>
<body>

<audio preload="auto" loop="loop" autoplay>

<source src="backmusic.mp3" type="audio/mpeg"></audio>
<input type="button" value="新增" style="background-color: #ff7e61;" />
<input type="button" value="开始" style="background-color: #8aff95;"

id="startButton" />
<input type="button" value="停止" style="background-color: #FC5753;"

id="stopButton" />
<input type="button" value="删除" style="background-color: #79d1ff;" />
</body>
</html>
[/mw_shl_code]
  2. 完成基本布局后,我们来实现雪花的新增效果,并响应“新增”按钮的单击事件,核心代码如下:
[JavaScript] 纯文本查看 复制代码
// 新增一片雪花
function createOneSnow() {
    var leftX = Math.random() * window.innerWidth;
    var topY = Math.random() * window.innerHeight;
    var snowDiv = document.createElement("div");
    snowDiv.style.position = "fixed";
    snowDiv.style.left = leftX + "px";
    snowDiv.style.top = topY + "px";
    // 为该新增的DIV元素内部添加一张雪花的图片
    snowDiv.innerHTML = "<img src='../image/white-snow.png' width='20' />";
    // 将该DIV元素增加到BODY中,浏览器才会对其进行渲染
    document.body.appendChild(snowDiv);
}

// 新增一批雪花
function createManySnow() {
    for (var i = 1; i <= 20; i++) {
        createOneSnow();
    }
}



  此处为大家提供了两个创建雪花的方案,当然其核心都是新增一片雪花。新增一片雪花的函数createOneSnow()同时也是为了在我们补充雪花时调用。而新增一批雪花的函数createManySnow()的主要目的则是为了响应“新增”按钮而设置的,这样我们在使用时可以一次性增加多片,省去频繁的点击操作。

  3. 开始让雪花飞舞,并且对何时删除雪花,何时新增雪花设定策略,代码如下:
[JavaScript] 纯文本查看 复制代码
// 开始让雪花移动,用定时器调用该函数

function startFly() {
    var allSnows = document.getElementsByTagName("div");
    for (var i=0; i<allSnows.length; i++) {
        var randomTop = Math.random() * 6;  // 每次移动的距离在6个像素以内
        allSnows.style.top = allSnows.offsetTop + randomTop + "px";
        // 当某个雪花的位置正好可以被200整除时,新增一片雪花
        if (allSnows.offsetTop % 200 == 0) {
            createOneSnow();
        }
        // 当某个雪花的位置已经超出浏览器窗口时,将该雪花删除,并再新增一片
        if (allSnows.offsetTop > window.innerHeight) {
            allSnows.remove();
            createOneSnow();
        }
    }
    // 开始按钮变成不可用,让停止按钮可用
    document.getElementById("startButton").disabled = "disabled";
    document.getElementById("stopButton").disabled = "";
}


  4. 最后,我们来实现“停止”和“删除”两个按钮的事件代码:
[JavaScript] 纯文本查看 复制代码
// 停止雪花的移动,并让开始按钮可用

function stopFly() {
    clearInterval(timer);   // 清除计时器效果:暂停计时,timer定义为全局变量
    document.getElementById("startButton").disabled = "";
    document.getElementById("stopButton").disabled = "disabled";
}

// 删除一半或全部雪花,注意其实现的不同之处
function removeSnow() {
    var allSnows = document.getElementsByTagName("div");
    var snowLength = allSnows.length;
    for (var i=0; i<snowLength; i++) {
        // allSnows.remove();  // 点击一次删除一半的雪花
        allSnows[0].remove();
    }
}



  通过上述代码的运行和调用,我们可以将开发思路中所罗列的所有功能实现。考虑到代码在实现的过程中对某些读者可能会显得比较零散,所以特将整个实现完成后的最终代码展示出来,供大家参考,代码如下:
[JavaScript] 纯文本查看 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
    body {
        background-image: url("../image/snow-night.jpg");
        /* 此处请大家自行在网上挑选一张喜欢的背景图片即可 */
        background-size: cover;   /* 让背景图自适应浏览器窗口大小 */
    }
    input {
        width: 80px;
        height: 30px;
        font-weight: bold;
    }
    </style>
    <script>
    var timer;  // 定义定时器全局变量

    // 新增一片雪花
    function createOneSnow() {
        var leftX = Math.random() * window.innerWidth;
        var topY = Math.random() * window.innerHeight;
        var snowDiv = document.createElement("div");
        snowDiv.style.position = "fixed";
        snowDiv.style.left = leftX + "px";
        snowDiv.style.top = topY + "px";
        // 为该新增的DIV元素内部添加一张雪花的图片
        snowDiv.innerHTML = "<img src='../image/snow.png' width='20' />";
        // 将该DIV元素增加到BODY中,浏览器才会对其进行渲染
        document.body.appendChild(snowDiv);
    }

    // 新增一批雪花
    function createManySnow() {
        for (var i = 1; i <= 20; i++) {
            createOneSnow();
        }
    }

    // 开始让雪花移动,用定时器调用该函数
    function startFly() {
        var allSnows = document.getElementsByTagName("div");
        for (var i=0; i<allSnows.length; i++) {
            var randomTop = Math.random() * 6;  // 每次移动的距离在6像素内
            allSnows.style.top=allSnows.offsetTop+randomTop+"px";
            // 当某个雪花的位置正好可以被200整除时,新增一片雪花
            if (allSnows.offsetTop % 200 == 0) {
                createOneSnow();
            }
            // 当某个雪花的位置已经超出浏览器窗口时,将该雪花删除,并再新增一片
            if (allSnows.offsetTop > window.innerHeight) {
                allSnows.remove();
                createOneSnow();
            }
        }
        // 开始按钮变成不可用,让停止按钮可用
        document.getElementById("startButton").disabled = "disabled";
        document.getElementById("stopButton").disabled = "";
    }

    // 停止雪花的移动,并让开始按钮可用
    function stopFly() {
        clearInterval(timer);   // 清除计时器效果:暂停计时
        document.getElementById("startButton").disabled = "";
        document.getElementById("stopButton").disabled = "disabled";
    }

    function removeSnow() {
        var allSnows = document.getElementsByTagName("div");
        var snowLength = allSnows.length;
        for (var i=0; i<snowLength; i++) {
            // allSnows.remove();  // 点击一次删除一半的雪花
            allSnows[0].remove();
        }
    }
    </script>
</head>
<body>
    <audio preload="auto" loop="loop"><source src="../basic/done.mp3" type="audio/mpeg"></audio>
    <input type="button" value="新增" style="background-color: #ff7e61;" />
    <input type="button" value="开始" style="background-color: #8aff95;" id="startButton"/>
    <input type="button" value="停止" style="background-color: #FC5753;" id="stopButton" disabled="disabled"/>
    <input type="button" value="删除" style="background-color: #79d1ff;" />
</body>
</html>


  思维拓展
  其实只要我们保持一个清晰的思路,要将上述代码完全吃透是没有多大问题的。逻辑上并不复杂,所用到的知识点也是比较小的知识点。只要大家对这些代码多加练习和应用,相信会很快在Web前端程序设计方面取得长足的进步。
  上述代码的实现过程中,存在一个比较严重的问题,就是随着时间的推移,雪花将会越来越多,因为新增的雪花要比删除的雪花多。而这样的会导致浏览器绘制很多很多的元素,浏览器的响应会变慢直到浏览器崩溃。那么该如何解决这一难题呢?我们只需要保证删除的元素和新增的元素大致相当,或者我们可以让定时器定时检查页面中的雪花总数量,只要将其控制在一定数量范围即可规避浏览器崩溃的风险。在此笔者也提醒大家,任何一个看似完美的程序,都有极大可能隐含着BUG,所以在软件产品的研发过程中,软件测试是非常重要的工作,也是研发环节不可或缺的一部分。
  我们现在再从另外一个角度来看看,上述的随机飘雪的特效是否还有更好的实现方式。另外一方面,是否还可以让“下雪”的场景变得更加真实。
  比如,在前面的章节中,我们学习到可以使用CSS动画来完成元素的移动甚至更多特效,那么对于雪花的移动,我们是否可以也考虑使用CSS动画来完成呢?答案当然是肯定的。只不过唯一的一点,CSS的属性都是固定的值,而且我们无法通过CSS属性来动态获取到浏览器窗口的宽度和高度,所以这种实现将非常死板。而且也无法通过随机移动多少个像素的方式让雪花快慢不一,所以真正实现时,我们仍然会选择使用JavaScript的编程方式来实现,更加的灵活可控。而且DOM和BOM的对象及其操作也同样是针对JavaScript来进行设计的,而非CSS样式。

  另外,生活经验告诉我们,雪花飘落在地上是不会无缘无故消失的,所以在网页中也应该如此。比如我们可以让雪花在飘落到浏览器窗口底部时,让其慢慢堆积起来。甚至定时模拟让一部分雪花消失,模拟雪花融化的过程,这些都是可以做到的。笔者为大家提供了这样两个挑战任务,各位读者朋友可以自行挑战一下,将飘雪的效果实现得更加真实,更加浪漫。






  传智的web前端培训课程一直紧随市场发展脚步,实时更新前沿、深度、覆盖面广的前端课程。
  传智全新升级的前端与移动开发课程新增了大热的小程序+小游戏、人脸识别、机器学习、大数据可视化等前沿技术,还加入了前所未有的全终端大型项目,让学员通过一个大型项目掌握从需求到开发再到上线部署的各项技能,为学员高薪就业增加砝码。
  更多的相关web前端培训课程请访问:http://www.itcast.cn/web/



作者: public_lei    时间: 2019-4-23 08:27
不错
-+





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2