ericdum / mujiang.info

一个拖更10年的博客,内容在 Issue 里
MIT License
469 stars 81 forks source link

简单的图片轮播,多多的优化 #3

Open ericdum opened 11 years ago

ericdum commented 11 years ago

今天分享一个很常见的东西——图片轮播。

虽然说这是一个很常见的东西,但真要论起来,其实有很多地方可以优化。

比如淘宝首页的图片轮播,如果一个顾客的鼠标像我这样傻逼得晃过去晃过来,被他经过的轮播就“停止”了。

注意鼠标移动的轨迹和轮播的大图片,如果用户在轮播周期内反复_经过_轮播元素,图片轮播就会“永远”停止。而下方的图片已经经过了几次轮播了。

xd_marque41

而同样的事情如果发生在心动游戏的官网上会,这不会有任何不符合预期的行为发生,鼠标悬停轮播停止,鼠标离开不影响轮播进程。

注意鼠标移动的轨迹和轮播的大图片

xd_marque31

引题结束,希望能引起你的兴趣,如若不然你应该不会看到这行字。。。或者下行字。。。

这个是我12年刚进入心动时写的,需求很简单——图片轮播、右下角链接。

为了实现这个需求,代码也可以很简单:

  1. 定义$.fn.xdMarquee,写成jquery扩展
  2. 函数中下载图片,并插入DOM
  3. 定义start、next、clear来控制轮播
  4. 定义右下角指示器click事件
  5. 开始轮播

源代码:

https://github.com/ericdum/mujiang.info/blob/master/share/marquee.js

捡重要的代码看:

    function marquee(data, options){
        //设置、初始化....
        //第二、三步
        var the = this;
        //第四步
        this.controller.find('span').live('click', function(){ 
            the.start(0, $(this).index()); 
        });
        //第五步
        this.start();
    }

    function start( timeout, index ) {
        this.clear(); //清除timeout
        this.promise(timeout, index);
    }

    function next(index) {
        // 执行动画 ....
        this.current = index;
        this.promise();//设置下次轮播
    }

    function promise( timeout, index ){
        timeout = timeout>=0 ? timeout : this.timeout;
        var the = this;
        this.tm = window.setTimeout(function(){
            the.next(index);
        }, timeout);
    }

    function clear() {
        window.clearTimeout(this.tm);
    }

悬停问题

因为我们认为用户鼠标悬停代表用户对图片产生了关注,所以悬停要暂停图片播放,就需要在上面初始化的方法中时候加入:

    this.hovering  = false;
    $(this).hover(function(){ 
        the.hovering = true; 
    }, function(){ 
        the.hovering = false; 
        the.start(); 
    });

然后再next方法中加入:

    if( this.hovering ) return;

这就可以了(另外要做好点击切换时的处理)。完成以后便发现了文章一开头提到的问题。

为了解决这个问题,首先想到的是在鼠标移走之后让图片立即改变。代码很简单,把上面第二段代码hover中对start的调用改成the.start(500)就好了——鼠标离开后500毫秒变换图片。

    //hover(function(){...},function(){...
    the.start(500)

但这就随之而来了另一个问题,如果用户老是经过这个地方,图片岂不是会不停地换——频率太快。

解决方案是如果图片播放的时间不足,则鼠标悬停对轮播不造成影响。实现的方法就是新增一个stopping状态。在next函数中判断hovering==true时,把它设置成true。而后在hover的第二个函数中判断:

    $(this).hover(function(){ 
        the.hovering = true; 
    }, function(){ 
        the.hovering = false; 
        if( the.stopping ){
            the.stopping = false;
            the.start(500); 
        }
    });

    //in next function
    if( this.hovering ) {
        this.stopping = true;
        return;
    }

性能问题

除了上面的工作以外还有个显而易见的问题:这种广告图片都是比较大的jpg图片(形状、色彩丰富),压缩的空间不大(jpg有损)。并且除了第一张,其他图片再3秒内是不需要显示的。所以决定分批下载,先下载第一张图,等第一张图下载完成之后再下载后续的图(这个时候这些图已经会排在下载队列的最后了)。

方案是扩展先前定义的initImages方法:

    function initImages(){
        var the = this;
        this.marquee.empty();//保持卫生
        this.appendImage(0, function(){
            for(var i=1; i<the.data.length; i++){
                the.appendImage(i);
            }
        });
    }

    function appendImage(index, callback){
        //检查参数,准备DOM对象... 
        img.load(callback);
        img.attr('src', data[0]);
        //插入DOM...
    }

菊花问题

图片延迟加载和以及本身就是靠js加载的第一张图片,必然会有概率发生图片还未加载完就要被用户观看的问题。所以必然要有野菊花。

解决方案更简单了,直接css加到了root元素的背景上,图片载入后自然盖住他,完美无缝,切换每张图没载入都可以显示到菊花。

js方面修改appendImage方法,防止未加载的图片被用户看到(那个飘渺的白框)。

    function appendImage(index, callback){
        //...
        img.hide();
        img.load(function(){
            $(this).show();
            callback();
        });
        img.attr('src', data[0]);
        //...
    }

其他问题

  1. 调整角标的显示逻辑,一次全部渲染,避免“跳”或乱序
  2. 调整轮播开始的逻辑,第一张图片加载完成后开始,保证广告的显示时间基本符合预期。
  3. 由于js放在页面底部,其实5张图片一起加载的性能差不多,兼容将第一张图片直接放在html中的情况。
zhanyuzhang commented 6 years ago

7777