JS+定时器实现随机飘雪特效
作者:强哥   类别:Web前端    日期:2018-10-29 15:58:15    阅读:2823 次   消耗积分:0 分




项目介绍



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


20181029_173113_357.png 

 

我们先来说明一下该项目要实现的功能主要包含哪些方面:

(1) 通过代码来新增一片或多片雪花。

(2) 雪花新增的位置是随机的。

(3) 可以随时开始和暂停雪花的移动。

(4) 可以删除所有或部分雪花。

(5) 雪花往下移动的过程中还需要随时补充雪花,这样才能模拟下雪的效果。

(6) 当雪花移出整个屏幕区域后,不应该继续保留该雪花,应该及时将其删除,否则会浪费浏览器的处理资源。

(7) 背景音乐的播放。

(8) 开始按钮和停止按钮应该交替使用,不能多次点击。




开发思路


首先,本项目演练所涉及到的知识点虽然逻辑不算复杂,但是面还是比较方的,CSS样式,DOM操作,BOM操作,定时器使用,雪花控制的细节等一应俱全,所以是一个值得大家去认真完成的项目。

基于对以上8个功能点的梳理,我们来看看其核心实现思路:


1.新增雪花

新增一片雪花的操作其实跟新增一个普通HTML元素没有任何本质区别,我们可以通过两种方式来完成。第一种方式是直接通过document.write()方法往页面中输出一个<img>标签,并设置好相应的属性即可。但是这种方式有一个问题,就是当我们往页面中输出内容的时候,页面中的之前存在的元素将会被覆盖,所以这并不是一种良好的解决方案。那么,我们自然会使用到第二种解决方案:通过调用document.createElement()的方法来向任意容器中增加元素,并对该元素设置CSS属性的方式完成元素的增加。在前面章节中,我们在讲解DOM元素的新增的知识点时专门为大家做过演示,所以自然我们会选择这一标准的做法。

但是此处需要注意的是,我们新增的是一片雪花,而不是简单的一个普通元素,而且还得让该雪花能够移动起来。所以从细节上来说,我们必须先新增一个DIV,并设置相应的属性。同时在该DIV中,我们还得增加一张图片,同时为了保证雪花的真实效果和美观度,该雪花图片必须使用一张透明背景的图片,所以图片必须是PNG格式的。

 

2.位置随机。

要实现一片随机位置的雪花,那么必然我们需要考虑两个核心因素:一是必须使用固定定位,这样才可以实现位置的强制调整;二是必须考虑浏览器窗口的高度和宽度,因为如果让雪花飘在窗口之外没有任何意义。

那么先来看看关于定位的问题,既然我们是将一片雪花放在一个DIV当中,所以我们只需要对该DIV设置“position: fixed”即可。进而再对其通过设置其lefttop属性进行定位。

另外,关于如何取得浏览器窗口的宽度和高度的问题,在介绍BOM的操作一节,我们为大家提到过,使用window.innerWidthwindow.innerHeight即可取得,所以技术上不存在任何难点。关键点在于,取得窗口的最大宽度和高度后,我们还需要基于该数值来生成两个随机数,一个是横向的数值,用于设置雪花的left属性;一个是纵向的数值,用于设置雪花的top属性。这样,一个随机位置的雪花即完成。

 

3.开始和暂停。

当开始让雪花移动时,我们当然需要使用setInterval()定时器来实现该功能。定时器本身就像一个死循环的结构一样,在定时器任务代码中,每触发一次定时器,我们就让所有雪花的top属性基于该片雪花现有的位置再增加几个随机的像素值,这样就可以实现快慢不一的雪花飞舞的效果。

当然,还得注意一点的是,定时器的时间间隔设为多久,雪花往下移动的单次距离在多少像素的范围内,能够让雪花飞舞的过程看上去更加自然,这是需要我们实现运行代码的过程中进行调试,找到一个最佳效果。其基本原则是在尽可能短的时间内移动的距离也尽可能短,这样可以让飞舞的效果更加平滑。

 

4.删除雪花。

要实现雪花的删除功能,我们首先必须要获取到某片雪花对应的元素,然后调用其方法:remove()即可实现删除。我们可以一次性删除所有的雪花,也可以实现一次性删除部分雪花,这个看我们自己的需要。本项目演练主要为大家提供一个可选的功能而已。

 

5.补充新的雪花。

由于雪花会一直往下移动,最终会消失在浏览器窗口中,所以为了保持雪花一直在下的效果,我们还必须在此过程中不停地自动增加雪花。要实现这一效果,方法有很多,但是其核心目的是,当触发到某个条件时,我们就应该考虑让雪花新增。比如当某片雪花距离浏览器顶部的距离(即元素的标准属性:offsetTop)超过了浏览器窗口的高度(即雪花已经消失在浏览器窗口的可视范围时),我们就应该新增一片雪花补上。也或者是在雪花移动到某个中间位置时,我们就触发新增的操作,进而实现雪一直下的效果。

 

6.移出无效雪花。

这是一个比较简单的实现方法,只需要在计时器代码中对所有雪花的位置进行一下判断,当其offsetTop属性对应的值超过浏览器窗口的高度时即可将该片雪花移除。当然,每当我们移出一片雪花时,我们就应该继续再新增一版雪花补上。

 

7.背景音乐。

背景音乐的使用可以直接使用HTML5自带的<audio>标签实现即可,并且设置该音频为自动加载,自动播放,也不需要在界面上显示控制条。

 

8.开始停止按钮交替点击。

首先我们需要明白,为什么开始按钮不能连续点击。因为我们会使用到定时器对象,而每当我们点击一次,就会生成一个新的定时器对象,如果不停地点击,就会生成很多定时器对象,这样会导致每个定时器对象就会去移动雪花,造成的结果就是雪花移动越来越快。

同样,不停地点击停止按钮将毫无意义。所以将二者进行一下结合,比较好的解决方案就是当页面加载时,让“停止”按钮变成不可用,启用“开始”按钮。当点击“开始”后,“开始”按钮马上变灰,而“停止”按钮可用。同样的,当点击“停止”按钮后,将“停止”按钮变灰,而“开始”按钮变成可用。具体的实现只需要通过设置按钮的disabled属性即可。


代码实现


通过对上述开发思路的梳理和分解,相信我们现在的脑海里已经对实现该功能有一个大致的轮廓了。但是咱们不急于一口气做完,一步一步地来实现上述功能的代码。

 

1. 完成页面的基本布局和样式设置,同时添加背景音乐,代码如下:

<!DOCTYPE html>
<
html>
<
head>
    <
meta charset="UTF-8">
    <
title>随机飘雪</title>
    <
style>
        
body {
            
background-imageurl("../image/snow-night.jpg");

/* 此处请大家自行在网上挑选一张喜欢的背景图片即可 */
            
background-sizecover;   /* 让背景图自适应浏览器窗口大小 */
        }
        
input {
            
width: 80px;
            
height: 30px;
            
font-weightbold;
        }
    </
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>

 

2. 完成基本布局后,我们来实现雪花的新增效果,并响应“新增”按钮的单击事件,核心代码如下:

// 新增一片雪花
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. 开始让雪花飞舞,并且对何时删除雪花,何时新增雪花设定策略,代码如下:

// 开始让雪花移动,用定时器调用该函数

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

 

4. 最后,我们来实现“停止”和“删除”两个按钮的事件代码:

// 停止雪花的移动,并让开始按钮可用

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[i].remove();  // 点击一次删除一半的雪花
        allSnows[0].remove();
    }
}

 

通过上述代码的运行和调用,我们可以将开发思路中所罗列的所有功能实现。考虑到代码在实现的过程中对某些读者可能会显得比较零散,所以特将整个实现完成后的最终代码展示出来,供大家参考,代码如下:

<!DOCTYPE html>
<
html>
<
head>
    <
meta charset="UTF-8">
    <
title></title>
    <
style>
    
body {
        
background-imageurl("../image/snow-night.jpg");
        
/* 此处请大家自行在网上挑选一张喜欢的背景图片即可 */
        background-sizecover;   /* 让背景图自适应浏览器窗口大小 */
    }
    
input {
        
width: 80px;
        
height: 30px;
        
font-weightbold;
    }
    </
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[i].style.top=allSnows[i].offsetTop+randomTop+"px";
            
// 当某个雪花的位置正好可以被200整除时,新增一片雪花
            if (allSnows[i].offsetTop % 200 == 0) {
                
createOneSnow();
            }
            
// 当某个雪花的位置已经超出浏览器窗口时,将该雪花删除,并再新增一片
            if (allSnows[i].offsetTop window.innerHeight) {
                allSnows[i].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[i].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;" onclick="createManySnow()/>
    <
input type="button" value="开始" style="background-color#8aff95;" id="startButton" onclick="timer=setInterval(startFly,100)"/>
    <
input type="button" value="停止" style="background-color#FC5753;" id="stopButton" disabled="disabled" onclick="stopFly()"/>
    <
input type="button" value="删除" style="background-color#79d1ff;" onclick="removeSnow()/>
</
body>
</
html>

 

思维拓展


其实只要我们保持一个清晰的思路,要将上述代码完全吃透是没有多大问题的。逻辑上并不复杂,所用到的知识点也是比较小的知识点。只要大家对这些代码多加练习和应用,相信会很快在Web前端程序设计方面取得长足的进步。


上述代码的实现过程中,存在一个比较严重的问题,就是随着时间的推移,雪花将会越来越多,因为新增的雪花要比删除的雪花多。而这样的会导致浏览器绘制很多很多的元素,浏览器的响应会变慢直到浏览器崩溃。那么该如何解决这一难题呢?我们只需要保证删除的元素和新增的元素大致相当,或者我们可以让定时器定时检查页面中的雪花总数量,只要将其控制在一定数量范围即可规避浏览器崩溃的风险。在此笔者也提醒大家,任何一个看似完美的程序,都有极大可能隐含着BUG,所以在软件产品的研发过程中,软件测试是非常重要的工作,也是研发环节不可或缺的一部分。


我们现在再从另外一个角度来看看,上述的随机飘雪的特效是否还有更好的实现方式。另外一方面,是否还可以让“下雪”的场景变得更加真实。

比如,在前面的章节中,我们学习到可以使用CSS动画来完成元素的移动甚至更多特效,那么对于雪花的移动,我们是否可以也考虑使用CSS动画来完成呢?答案当然是肯定的。只不过唯一的一点,CSS的属性都是固定的值,而且我们无法通过CSS属性来动态获取到浏览器窗口的宽度和高度,所以这种实现将非常死板。而且也无法通过随机移动多少个像素的方式让雪花快慢不一,所以真正实现时,我们仍然会选择使用JavaScript的编程方式来实现,更加的灵活可控。而且DOMBOM的对象及其操作也同样是针对JavaScript来进行设计的,而非CSS样式。

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

 





为了答谢大家对蜗牛学院的支持,蜗牛学院将会定期对大家免费发放干货,敬请关注蜗牛学院的官方微信。


20181009_153045_341.jpg



   
版权所有,转载本站文章请注明出处:蜗牛笔记, http://www.woniunote.com/article/205
上一篇: 蜗牛学院成都校区第三届程序员节完美落幕,我们来年再聚!
下一篇: JS函数应用与TDD测试驱动开发
提示:登录后添加有效评论可享受积分哦!
最新文章
    最多阅读
      特别推荐
      回到顶部