基于setTimeout制作滚动广告板

很多网站在其门户页面的上方正中央都会放置一个滚动广告板,用于显示一些推荐信息,用户点击即可进入浏览。比较常见的就是各个公司的官网,电商网站的首页等。
下面是天猫的滚动广告板截图。
tmall滚动广告版

其实,不需要借助于什么复杂的技术,仅仅通过setTimeout函数,就能实现这种滚动广告板的效果。

分析

仔细观察这个滚动广告版,我们发现,其功能无非就两点:

  • 无限循环的不停滚动显示每一条广告
  • 当点击某一个广告的索引时,以那条广告为起点,无限循环滚动

先分析一下第一个功能点。
要做到无限循环的不停滚动显示每一条广告,那么只需要关注两个相邻广告页的切换(暂且称为函数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();
}

到此,无限循环滚动显示广告的功能就完成了.

上面的代码只做基本原理介绍,实际操作过程中还有很多细节需要处理。

再看第二个功能点。
当用户点击某个广告页的索引时,当前广告会立即被切换到选中的广告页,并从选中的广告页开始循环滚动。咋一看跟功能点一很像,似乎只需要在用户点击某个索引的时候将当前广告页设置为用户选中的广告页就可以了。但是很多时候,最直接的想法往往是不对的,这里需要考虑到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);
}

到此,整个滚动广告板就做完了,但是实现功能仅仅是第一步,下一步还需要将其组件化,将一些内部状态隐藏,抽象出组件接口。在使用的时候,只需要实例化一个组件实例,然后使用必要的数据调用组件接口就可以了。
下面是实现的效果图:
滚动广告板

源代码稍后放出:)

posted @ 2016-01-16 20:51  tbingooo  阅读(828)  评论(2编辑  收藏  举报