不少网站在其门户页面的上方正中央都会放置一个滚动广告板,用于显示一些推荐信息,用户点击便可进入浏览。比较常见的就是各个公司的官网,电商网站的首页等。
下面是天猫的滚动广告板截图。
css
其实,不须要借助于什么复杂的技术,仅仅经过setTimeout函数,就能实现这种滚动广告板的效果。css3
仔细观察这个滚动广告版,咱们发现,其功能无非就两点:浏览器
先分析一下第一个功能点。
要作到无限循环的不停滚动显示每一条广告,那么只须要关注两个相邻广告页的切换(暂且称为函数doSwitch),而后经过设置定时任务不停的运行doSwitch就能够了。所以,咱们有了一个基本的雏形:函数
function doSwitch() { //switch adjacent ad pages } function autoSwitch() { doSwitch(); }; setTimeout(autoSwitch, someInterval)
如今,咱们只须要重点关注switch的实现就能够了。
上面截图中,广告页经过淡入淡出效果来进行切换,很容易就联想到css3中的opacity属性(天猫也是这么实现的),可是考虑到IE8的存在,opacity就不适用了,因为具体的切换效果与今天的主题关系不大,因此改为了横向滚动切换效果。
横向滚动有不少种实现效果,最直接的方式,就是经过margin-left来实现。设广告页的宽度为w,以当前广告页左边的边框为基准线,将当前广告页的margin-left由0逐渐递减至-w,将下一广告页的margin-left由w逐渐减至0(广告板的父容器设置overflow:hidden)。所以,doSwitch能够简单实现以下:组件化
function doSwitch() { var curPage = document.getElementById(curPageId), nextPage = document.getElementById(nextPageId); curPage.style.marginLeft -= step; // 此处忽略了单位处理 nextPage.style.marginLeft -= step; if (nextPage.style.marginLeft > 0) { setTimeout(doSwitch, someInnerInterval); } else { setTimeout(autoSwitch, someInterval); } }
因为切换效果的时间应该远小于广告停留的时间,因此此处someInnerInterval小于someInterval.网站
注意到,每次广告页的切换,广告页下方的索引按钮也会跟着切换,所以,上方的autoSwitch函数中还要增长一项设置当前选中索引的功能,暂且成为setCurIndex函数,则autoSwitch实现修改以下:线程
function autoSwitch() { setCurIndex(); doSwitch(); }
到此,无限循环滚动显示广告的功能就完成了.code
上面的代码只作基本原理介绍,实际操做过程当中还有不少细节须要处理。blog
再看第二个功能点。
当用户点击某个广告页的索引时,当前广告会当即被切换到选中的广告页,并从选中的广告页开始循环滚动。咋一看跟功能点一很像,彷佛只须要在用户点击某个索引的时候将当前广告页设置为用户选中的广告页就能够了。可是不少时候,最直接的想法每每是不对的,这里须要考虑到setTimeout的原理。
setTimeout函数的正确使用姿式应该像下面的代码同样:排序
var timeout = setTimeout(function () { //do something after at least 1s }, 1000); // clearTimeout(timeout); //cancel setTimeout handler function
咱们知道,JavaScript是单线程的,定时器只是计划代码在将来的某个时间点执行,可是,执行时机是不能保证的。在页面的生命周期中,不一样时间可能有其余代码控制着JavaScript线程。例如在页面下载完后的代码运行、事件处理程序、Ajax请求的回调函数等都必须使用一样的线程来执行。实际上,浏览器负责进行排序,指派某段代码在某个时间点运行的优先级。在浏览器内部管理着一个代码队列,随着页面生命周期的推移,代码会按照执行顺序添加入队列。例如,当某个按钮被按下时,它的事件处理程序代码就会被添加到队列中,并在下一个可能的时间里执行。当接到某个Ajax响应时,回调函数的代码会被添加到队列。浏览器经过事件循环的机制从这个队列中取出处理函数代码并执行。
定时器对队列的工做方式是,在指定时间过去后,将代码插入队列。请注意,给队列添加代码并不意味着它会当即执行,而只能表示它会尽快执行。与setTimeout函数成对出现的是clearTimeout函数,用于取消setTimeout设置的延时执行的函数。该函数只接受一个参数,那就是setTimeout返回的延时调用ID。
回到刚才的问题中,功能一中,经过setTimeout函数,不停的将doSwitch和autoSwitch函数添加到队列中,若是当用户点击某个广告页索引时,只是简单的将当前广告设置为选中的广告页,而后调用autoSwitch的话,将会致使两个无限循环滚动同时运行,这必将致使广告页显示的混乱。
那么如何解决这个问题呢?
当用户点击某个索引时,咱们只须要终止以前一直运行的循环滚动,而后开启新的循环滚动便可。那么问题就聚焦在如何终止上一个循环滚动上面来了。实际上,咱们能够经过clearTimeout来实现。咱们只须要在doSwitch中添加一个状态检测,若是用户当前点击了某个索引,则退出函数执行。样例以下:
function doSwitch() { if (paused) { return; } // other }
这时候,咱们只须要在用户点击某个广告索引时,设置paused为true,而后在新一轮的循环滚动开始前将paused设置为false就能够了。代码以下:
indexDom.onclick = function () { setCurIndex(); paused = true; setTimeout(function () { paused = false; autoSwitch(); }, someInterval); }
须要注意的一点是,这里的someInterval与功能点一中的someInterval要保持相同。因为someInnerInterval小于someInterval,因此在下一个循环滚动开始以前,上一个循环滚动已经中止了。
到此,实际上基本功能就已经结束了,可是在完成基本功能以后,咱们还要照顾到异常场景的存在。这里就须要处理短期内连续点击不一样索引的状况。
其实也很简单,只须要在onclick时间处理函数中保存一个curClick索引,而后在延时函数中比对curClick是否与当前的curIndex一致就能够了。
因此上面的onclick能够改写以下:
indexDom.onclick = function () { var curClick = indexDom["data-index"]; //get the index of indexDom setCurIndex(); curIndex = curClick; setTimeout(function () { if (curIndex !== curClick) { return; } paused = true; setTimeout(function () { paused = false; authSwitch(); }, someInterval) }, 0); }
到此,整个滚动广告板就作完了,可是实现功能仅仅是第一步,下一步还须要将其组件化,将一些内部状态隐藏,抽象出组件接口。在使用的时候,只须要实例化一个组件实例,而后使用必要的数据调用组件接口就能够了。
下面是实现的效果图:
源代码稍后放出:)