随机飘雪的网页特效我们经常在一些文艺类网站或博客中看到,再配合背景音乐可以更好地营造温馨的氛围。我们先来看看最终运行效果如图所示。
我们先来说明一下该项目要实现的功能主要包含哪些方面:
(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”即可。进而再对其通过设置其left和top属性进行定位。
另外,关于如何取得浏览器窗口的宽度和高度的问题,在介绍BOM的操作一节,我们为大家提到过,使用window.innerWidth或window.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-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>
|
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-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[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的编程方式来实现,更加的灵活可控。而且DOM和BOM的对象及其操作也同样是针对JavaScript来进行设计的,而非CSS样式。
另外,生活经验告诉我们,雪花飘落在地上是不会无缘无故消失的,所以在网页中也应该如此。比如我们可以让雪花在飘落到浏览器窗口底部时,让其慢慢堆积起来。甚至定时模拟让一部分雪花消失,模拟雪花融化的过程,这些都是可以做到的。笔者为大家提供了这样两个挑战任务,各位读者朋友可以自行挑战一下,将飘雪的效果实现得更加真实,更加浪漫。
JS+定时器实现随机飘雪特效