/ 昌里大金猪的空间 / 改善用户体验:图片的预加载[翻译]

改善用户体验:图片的预加载[翻译]

2011-03-16 posted in [javascript]

本文地址:http://blog.163.com/jinlu_hz/blog/static/1138301522011216114141761/
原文地址:http://msdn.microsoft.com/en-us/scriptjunkie/gg681862.aspx
原文作者:Jan Lehmann

翻译能力有限,辞不达意的地方请自动跳过或者直接阅读原文

——以下为翻译——

目前对打造一个操作系统桌面般用户体验的关注度日益升高,用到的图片数量也越来越庞大。为了满足各种需求,对话框、标签栏、工具条、幻灯与日历组件等变得十分重要。
图片的预加载能够提升网站性能和可用性,同时也避免了一些页面的因图片未加载完毕而产生的闪烁感和片段感。


Frequently used Techniques(惯用伎俩)

有两种惯用伎俩实现图片预加载:
Css sprites
Css sprites有效限制了页面上的HTTP请求数,所有的小图片被拼在了一张大图片上,然后通过CSS来定位相应元素。
但有一个缺点,只有刚开始时(页面第一次载入时)出现的图片会被影响到。其他那些动态载入、计划稍后呈现的图片不会被加载,在此产生了一个延迟。所以这个技术的目标与静态网站一致,大大降低图片数量。

JavaScript
另一个手段就是JavaScript了,无非就是将每个图片的URL塞进一个数组。

var myImages = ["image_1.jpg', 'image_2.jpg', 'image_3.jpg', ...];

然后循环数组,为每个URL创建一个image对象。这么做的好处是保证所有的图片都已经被浏览器缓存完毕,并能够毫无延迟地为浏览器后期所用。
  1. for (var i = 0; i <= myImages.length; i++) {
  2. var img = new Image(); 
  3.      img.src = myImages[i];
  4. }
该解决方案的时间开销都用在了将每一个图片URL放进一个数组中去,并维护数组的一致性。网站每次修改(例如新增了图片或者图片的URL改变)都会影响数组,如果有无数图片要修改那么这个操作将非常耗时。



Automated image preloading with progress indicator(带有载入进度指示的图片自动预加载)

一个更有效的手段是自动收集图片URL。
第一步是分析页面上所有CSS link和内联样式。通过遍历"document.styleSheets"对象成员,访问每一个正在使用的样式表。

for (var i = 0; i < document.styleSheets.length; i++){

获取Css的基础路径很重要,这是为后期获取图片的绝对路径做准备。

var baseURL = getBaseURL(document.styleSheets[i].href);

出于浏览器兼容性考虑,很有必要检测是否每个样式表的"cssRules"或者"rules"都含有数据。
  1. if (document.styleSheets[i].cssRules) {
  2.     cssRules = document.styleSheets[i].cssRules
  3. }
  4. else if (document.styleSheets[i].rules) {
  5.     cssRules = document.styleSheets[i].rules
  6. }

每条Css规则会被解析是否为空值,或者说是否为一个与图片相关的规则。正则表达式正好能够做这个事情。

var cssRule = cssRules[j].style.cssText.match(/[^\(]+\.(gif|jpg|jpeg|png)/g);

然后,获取到的每个图片的URL会被塞进数组为后期所用。
  1. function analyzeCSSFiles() {
  2.     var cssRules; // CSS rules
  3.     
  4.     for (var i = 0; i < document.styleSheets.length; i++) { // loop through all linked/inline stylesheets
  5.        
  6.         var baseURL = getBaseURL(document.styleSheets[i].href); // get stylesheet's base URL 
  7.  
  8.         // get CSS rules
  9.         if (document.styleSheets[i].cssRules) {
  10.             cssRules = document.styleSheets[i].cssRules
  11.         }
  12.         else if (document.styleSheets[i].rules) {
  13.             cssRules = document.styleSheets[i].rules
  14.         }
  15.         
  16.         // loop through all CSS rules
  17.         for (var j = 0; j < cssRules.length; j++) {
  18.             if (cssRules[j].style && cssRules[j].style.cssText) {
  19.                 // extract only image related CSS rules 
  20.                 // parse rules string and extract image URL
  21.                 var cssRule = cssRules[j].style.cssText.match(/[^\(]+\.(gif|jpg|jpeg|png)/g);
  22.                 if (cssRule) {
  23.                     // add image URL to array
  24.                     cssRule = (cssRule + "").replace("\"""")
  25.                     imageURLs.push(baseURL + cssRule);
  26.                 }
  27.             }
  28.         }
  29.     }
  30. }

为获取每个图片的绝对路径,在此实现一个"getBaseUrl"的函数用来提取每个Css文件里的绝对路径。
  1. function getBaseURL(cssLink) {
  2.     cssLink = (cssLink) ? cssLink : 'window.location.href'// window.location.href for inline style definitions
  3.     
  4.     var urlParts = cssLink.split('/'); // split link at '/' into an array
  5.     urlParts.pop(); // remove file path from URL array
  6.     
  7.     var baseURL = urlParts.join('/'); // create base URL (rejoin URL parts)
  8.     
  9.     if (baseURL != ""
  10.     {
  11.         baseURL += '/'// expand URL with a '/'
  12.     }
  13.     
  14.     return baseURL;
  15. }

通过遍历集合,为页面上的每个图片创建一个image对象用以预加载,使得浏览器能够事先缓存住这些图片。切记这边使用"setTimeout"来确保每个图片在加载下一个前被完全加载,因为许多浏览器无法同时处理2个以上的请求。为避免可能出现的停顿现象,我们在此为"load","onreadystatechange"以及"error"事件增加一个处理函数。假如缺失的图片不能被正常加载,那么"errorTimer"会做一些弥补工作。
  1. function preloadImages() {
  2.     if (imageURLs && imageURLs.length && imageURLs[imagesLoaded]) { // if image URLs array isn't empty
  3.         var img = new Image(); // create a new imgage object
  4.         img.src = imageURLs[imagesLoaded]; // set the image source to the extracted image URL
  5.         
  6.         setTimeout(function () {
  7.             if (!img.complete) {
  8.                 jQuery(img).bind('onreadystatechange load error', onImageLoaded); // bind event handler
  9.             } else {
  10.                 onImageLoaded(); // image loaded successfully, continue with the next one
  11.             }
  12.                         
  13.             errorTimer = setTimeout(onImageLoaded, 1000); // handle missing files (load the next image after 1 second)
  14.         }25);
  15.     }
  16.     else {
  17.         showPreloadedImages();
  18.     }
  19. }
  20.  
  21. function onImageLoaded() {
  22.     clearTimeout(errorTimer); // clear error timer
  23.     
  24.     imagesLoaded++; // increase image counter
  25.     
  26.     preloadImages(); // preload next image
  27. }

为改善用户体验,我们修改"onImageLoaded"事件处理函数,目的与进度条进行交互。
首先,为显示进度条而加载jQuery UI样式表和所需的javascript文件:
  1. <link type="text/css" href="css/ui-lightness/jquery-ui-1.8.6.custom.css" rel="stylesheet" />
  2. <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
  3. <script type="text/javascript" src="js/jquery-ui-1.8.6.custom.min.js"></script>

然后初始化进度条:
  1. $(document).ready(function () {
  2.     $("#progressbar").progressbar({ value: 0 }); // initialize progress bar
  3. });

最后,扩展"onImageLoaded"事件处理函数。每个图片在加载完毕后,进度条会重新计算进度,调整UI。
这个预加载函数的成功之处在于每个图片都被加载到了页面,在整个加载操作完成之后,所有的图片会同时呈现在页面上。
  1. function onImageLoaded() {
  2.     clearTimeout(errorTimer); // clear error timer
  3.  
  4.     $("#imagelist").append("<span>" + imagesLoaded + ": </span><img src='http://blog.163.com/jinlu_hz/blog/" + imageURLs[imagesLoaded] + "'/>"); // append image tag to image list
  5.  
  6.     imagesLoaded++; // increase image counter
  7.  
  8.     var percent = parseInt(100 * imagesLoaded / (imageURLs.length - 1)); // calculate progress
  9.  
  10.     $("#progressbar").progressbar({ value: percent }); // refresh progress bar
  11.     $("#progressbarTitle").text("Preloading progress: " + percent + "%"); 
  12.  
  13.     preloadImages(); // preload next image
  14. }

A small step to get better(还能做的更好)
预加载图片是一个改善用户体验,使网站显得更为专业的一个简单手段。通过程序自动分析Css规则,你能够不费力气的处理大量图片。
进度条提示用户大概还要等待加载的时间,有效提高了用户满意度。jQuery还提供了许多像进度条一样超棒的插件,使用这些插件无疑能使你的网站蓬荜生辉。

这个链接包含本文所有实例,你可以略作修改再整合到自己的项目中去。


——翻译结束——
2011-03-16 12:18:35