Track Image Loading效果代码分析

目的

在图片的加载过程中,提供定义图片加载成功或加载失败/超时时的回调函数,并确保执行。 

动机

原生JavaScript已经对 Image 对象提供了 onload 和 onerror 注册事件。但在浏览器缓存及其他因素的影响下,用户在使用回退按钮或者刷新页面时 onload 事件常常未能稳定触发。在我开发的相册系统中,我希望图片能根据自定义的大小显示以免导致页面变形,例如最宽不得超过500px,而小于500px宽度的图片则按原大小显示。CSS2 提供了 max-width 属性能够帮组我们实现这一目的。但很遗憾,挨千刀的IE6并不支持。 

IE6一个弥补的办法就是通过注册 img.onload 事件,待图片加载完成后自动调整大小。以下代码取自著名的Discuz!论坛系统4.1版本对显示图片的处理。 

<img src="http://img8.imagepile.net/img8/47104p155.jpg" border="0"

onload="if(this.width>screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; 

this.alt='Click here to open new windownCTRL+Mouse wheel to zoom in/out';}" 

onmouseover="if(this.width>screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.style.cursor='hand'; this.alt='Click here to open new windownCTRL+Mouse wheel to zoom in/out';}" 

onclick="if(!this.resized) {return true;} else {window.open('http://img8.imagepile.net/img8/47104p155.jpg');}" 

onmousewheel="return imgzoom(this);">

前文已述,浏览器并不保证事件处理函数执行。所以需要一个更稳定的方式跟踪图片加载过程,并执行设定的回调函数。 

实现

image.complete 属性标示的是图片加载状态,其值如果为ture,则表示加载成功。图片不存在或加载超时则值为false。利用 setInterval() 函数定时检查该状态则可以实现跟踪图片加载的状况。代码片断如下: 

ImageLoader = Class.create();

ImageLoader.prototype = {

  initialize : function(options) {

    this.options = Object.extend({

      timeout: 60, //60s

      onInit: Prototype.emptyFunction,

      onLoad: Prototype.emptyFunction,

      onError: Prototype.emptyFunction

    }, options || {});

    this.images = [];

    this.pe = new PeriodicalExecuter(this._load.bind(this), 0.02);

  },

       ........

}

利用Prototype 的PeriodicalExecuter类创建一个定时器,每隔20毫秒检查一次图片的加载情况,并根据状态执行 options 参数中定义的回调函数。 

使用

var loader = new ImageLoader({

  timeout: 30,

  onInit: function(img) {

    img.style.width = '100px';

  },

  onLoad: function(img) {

    img.style.width = '';

    if (img.width > 500) 

      img.style.width = '500px';

  },

  onError: function(img) {

    img.src = 'error.jpg'; //hint image

  }

});loader.loadImage(document.getElementsByTagName('img'));

上面的代码定义图片最初以100px显示,加载成功后如果图片实际宽度超过500px,则再强制定义为500px,否则显示原大小。如果图片不存在或加载超时(30秒为超时),则显示错误图片。 

同理,可以应用 ImageLoader 的回调函数来根据需求自定义效果,例如默认显示loading,加载完成后再显示原图;图片首先灰度显示,加载完成后再恢复亮度等等。例如: 

//need scriptaculous effects.js

var loader = new ImageLoader({

  onInit: function(img) {

    Element.setOpacity(img, 0.5); //默认5级透明

  },

  onLoad: function(img) {

    Effect.Appear(img);  //恢复原图显示

  }

});

附示例中包含完整的代码及使用pconline图片为例的测试, 注意 范例中使用了最新的Prototype 1.5.0_rc1。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 //EN">

<html>

<head>

<script src="prototype1.5.0_rc1.js"></script>

<script src="validation1.5.3/effects.js"></script>

</head>

<body>

<img id="img0" src="http://img.pconline.com.cn/images/photoblog/2026024/20069/14/1158169144171.jpg" />

<img id="img1" src="http://img.pconline.com.cn/images/photoblog/2026024/20069/14/1158169158366.jpg" />

<img id="img2" src="http://img.pconline.com.cn/images/photoblog/2026024/20069/14/1158169169983_mthumb.jpg" />

<br />加载失败测试<br />

<img id="img2" src="http://img.pconline.com.cn/images/photoblog/2026024/20069/14/000000000000.jpg" />

<script type="text/javascript">

ImageLoader = Class.create();

ImageLoader.prototype = {

  initialize : function(options) {

    this.options = Object.extend({

      timeout: 60, //60s

      onInit: Prototype.emptyFunction,

      onLoad: Prototype.emptyFunction,

      onError: Prototype.emptyFunction

    }, options || {});

    this.images = [];

    this.pe = new PeriodicalExecuter(this._load.bind(this), 0.02);

  },

  loadImage : function() {

    var self = this;

    $A(arguments).each(function(img) {

      if (typeof(img) == 'object')

        $A(img).each(self._addImage.bind(self));

      else

        self._addImage(img);

    });

  },

  _addImage : function(img) {

    img = $(img);

    img.onerror = this._onerror.bind(this, img);

    this.options.onInit.call(this, img);

    if (this.options.timeout > 0) {

      setTimeout(this._ontimeout.bind(this, img), this.options.timeout*1000);

    }

    this.images.push(img);

    if (!this.pe.timer)

      this.pe.registerCallback();

  },

  _load: function() {

    this.images = this.images.select(this._onload.bind(this));

    if (this.images.length == 0) {

      this.pe.stop();

    }

  },

  _checkComplete : function(img) {

    if (img._error) {

      return true;

    } else {

      return img.complete;

    }

  },

  _onload : function(img) {

    if (this._checkComplete(img)) {

      this.options.onLoad.call(this, img);

      img.onerror = null;

      if (img._error)

        try {delete img._error}catch(e){}

      return false;

    }

    return true;

  },

  _onerror : function(img) {

    img._error = true;

    img.onerror = null;

    this.options.onError.call(this, img);

  },

  _ontimeout : function(img) {

    if (!this._checkComplete(img)) {

      this._onerror(img);

    }

  }

}

var loader = new ImageLoader({

  timeout: 30,

  onInit: function(img) {

    img.style.width = '100px';

  },

  onLoad: function(img) {

    img.style.width = '';

    if (img.width > 500) { 

      img.style.width = '500px';

    }

  },

  onError: function(img) {

    img.src = 'http://img.pconline.com.cn/nopic.gif';

  }

});

loader.loadImage(document.getElementsByTagName('img'));

/*

var loader = new ImageLoader({

  timeout: 30,

  onInit: function(img) {

    Element.setOpacity(img, 0.5);

  },

  onLoad: function(img) {

    Effect.Appear(img);

  },

  onError: function(img) {

    img.src = 'http://img.pconline.com.cn/nopic.gif';

  }

});

*/

/*

$A(document.getElementsByTagName('img')).each(

function(img) {

  img.onload = function() {

    img.style.width = '300px';

  }

}

);

*/

</script>

</body>

</html>

相关推荐