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>