3 title: "改善用户体验:图片的预加载[翻译]"
9 [这家伙](https://github.com/nomospace/nomospace.github.com/blob/master/_posts/2011-03-16-image-preload.md)写的什么Markdown,完全是堆HTML,还带一堆`style`,一点都不简洁。差评!(后来发现163那边作者是同一人,所以,自己的163博客也是直接dump么……)
11 原文那个zip例子现在找不到了,而且jQuery出来后自己写轮子就显得过时了。
12 一个功能相反的例子是下面这个,它出现在原文评论中。
14 https://github.com/tuupola/jquery_lazyload
17 本文地址:http://blog.163.com/jinlu_hz/blog/static/1138301522011216114141761/ <br>
18 原文地址:http://msdn.microsoft.com/en-us/scriptjunkie/gg681862.aspx <br>
19 原文作者:Jan Lehmann [@lehmjan](http://twitter.com/lehmjan)
21 翻译能力有限,辞不达意的地方请自动跳过或者直接阅读[原文](http://msdn.microsoft.com/en-us/scriptjunkie/gg681862.aspx)。
25 目前对打造一个操作系统桌面般用户体验的关注度日益升高,用到的图片数量也越来越庞大。为了满足各种需求,对话框、标签栏、工具条、幻灯与日历组件等变得十分重要。
26 图片的预加载能够提升网站性能和可用性,同时也避免了一些页面的因图片未加载完毕而产生的闪烁感和片段感。
28 ###Frequently used Techniques(惯用伎俩)
33 Css sprites有效限制了页面上的HTTP请求数,所有的小图片被拼在了一张大图片上,然后通过CSS来定位相应元素。
34 但有一个缺点,只有刚开始时(页面第一次载入时)出现的图片会被影响到。其他那些动态载入、计划稍后呈现的图片不会被加载,在此产生了一个延迟。所以这个技术的目标与静态网站一致,大大降低图片数量。
37 另一个手段就是JavaScript了,无非就是将每个图片的URL塞进一个数组。
39 {% highlight JavaScript %}
40 var myImages = ["image_1.jpg", 'image_2.jpg', 'image_3.jpg', ...];
43 然后循环数组,为每个URL创建一个image对象。这么做的好处是保证所有的图片都已经被浏览器缓存完毕,并能够毫无延迟地为浏览器后期所用。
45 {% highlight JavaScript %}
46 for (var i = 0; i <= myImages.length; i++) {
47 var img = new Image();
48 img.src = myImages[i];
52 该解决方案的时间开销都用在了将每一个图片URL放进一个数组中去,并维护数组的一致性。网站每次修改(例如新增了图片或者图片的URL改变)都会影响数组,如果有无数图片要修改那么这个操作将非常耗时。
54 ###Automated image preloading with progress indicator(带有载入进度指示的图片自动预加载)
57 第一步是分析页面上所有CSS link和内联样式。通过遍历`document.styleSheets`对象成员,访问每一个正在使用的样式表。
58 {% highlight JavaScript %}
59 for (var i = 0; i < document.styleSheets.length; i++){
62 获取Css的基础路径很重要,这是为后期获取图片的绝对路径做准备。
63 {% highlight JavaScript %}
64 var baseURL = getBaseURL(document.styleSheets[i].href);
67 出于浏览器兼容性考虑,很有必要检测是否每个样式表的"cssRules"或者"rules"都含有数据。
68 {% highlight JavaScript %}
69 if (document.styleSheets[i].cssRules) {
70 cssRules = document.styleSheets[i].cssRules
72 else if (document.styleSheets[i].rules) {
73 cssRules = document.styleSheets[i].rules
77 每条Css规则会被解析是否为空值,或者说是否为一个与图片相关的规则。正则表达式正好能够做这个事情。
78 {% highlight JavaScript %}
79 var cssRule = cssRules[j].style.cssText.match(/[^\(]+\.(gif|jpg|jpeg|png)/g);
82 然后,获取到的每个图片的URL会被塞进数组为后期所用。
83 {% highlight JavaScript %}
84 function analyzeCSSFiles() {
85 var cssRules; // CSS rules
87 for (var i = 0; i < document.styleSheets.length; i++) { // loop through all linked/inline stylesheets
89 var baseURL = getBaseURL(document.styleSheets[i].href); // get stylesheet's base URL
92 if (document.styleSheets[i].cssRules) {
93 cssRules = document.styleSheets[i].cssRules
95 else if (document.styleSheets[i].rules) {
96 cssRules = document.styleSheets[i].rules
99 // loop through all CSS rules
100 for (var j = 0; j < cssRules.length; j++) {
101 if (cssRules[j].style && cssRules[j].style.cssText) {
102 // extract only image related CSS rules
103 // parse rules string and extract image URL
104 var cssRule = cssRules[j].style.cssText.match(/[^\(]+\.(gif|jpg|jpeg|png)/g);
106 // add image URL to array
107 cssRule = (cssRule + "").replace("\"", "")
108 imageURLs.push(baseURL + cssRule);
116 为获取每个图片的绝对路径,在此实现一个"getBaseUrl"的函数用来提取每个CSS文件里的绝对路径。
117 {% highlight JavaScript %}
118 function getBaseURL(cssLink) {
119 cssLink = (cssLink) ? cssLink : 'window.location.href'; // window.location.href for inline style definitions
121 var urlParts = cssLink.split('/'); // split link at '/' into an array
122 urlParts.pop(); // remove file path from URL array
124 var baseURL = urlParts.join('/'); // create base URL (rejoin URL parts)
128 baseURL += '/'; // expand URL with a '/'
135 通过遍历集合,为页面上的每个图片创建一个image对象用以预加载,使得浏览器能够事先缓存住这些图片。切记这边使用"setTimeout"来确保每个图片在加载下一个前被完全加载,因为许多浏览器无法同时处理2个以上的请求。为避免可能出现的停顿现象,我们在此为"load","onreadystatechange"以及"error"事件增加一个处理函数。假如缺失的图片不能被正常加载,那么"errorTimer"会做一些弥补工作。
136 {% highlight JavaScript %}
137 function preloadImages() {
138 if (imageURLs && imageURLs.length && imageURLs[imagesLoaded]) { // if image URLs array isn't empty
139 var img = new Image(); // create a new imgage object
140 img.src = imageURLs[imagesLoaded]; // set the image source to the extracted image URL
142 setTimeout(function () {
144 jQuery(img).bind('onreadystatechange load error', onImageLoaded); // bind event handler
146 onImageLoaded(); // image loaded successfully, continue with the next one
149 errorTimer = setTimeout(onImageLoaded, 1000); // handle missing files (load the next image after 1 second)
153 showPreloadedImages();
157 function onImageLoaded() {
158 clearTimeout(errorTimer); // clear error timer
160 imagesLoaded++; // increase image counter
162 preloadImages(); // preload next image
166 为改善用户体验,我们修改"onImageLoaded"事件处理函数,目的与进度条进行交互。
168 首先,为显示进度条而加载jQuery UI样式表和所需的javascript文件:
169 {% highlight JavaScript %}
170 <link type="text/css" href="css/ui-lightness/jquery-ui-1.8.6.custom.css" rel="stylesheet" />
171 <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
172 <script type="text/javascript" src="js/jquery-ui-1.8.6.custom.min.js"></script>
176 {% highlight JavaScript %}
177 $(document).ready(function () {
178 $("#progressbar").progressbar({ value: 0 }); // initialize progress bar
182 最后,扩展"onImageLoaded"事件处理函数。每个图片在加载完毕后,进度条会重新计算进度,调整UI。
183 这个预加载函数的成功之处在于每个图片都被加载到了页面,在整个加载操作完成之后,所有的图片会同时呈现在页面上。
184 {% highlight JavaScript %}
185 function onImageLoaded() {
186 clearTimeout(errorTimer); // clear error timer
188 $("#imagelist").append("<span>" + imagesLoaded + ": </span><img src='" + imageURLs[imagesLoaded] + "'/>"); // append image tag to image list
190 imagesLoaded++; // increase image counter
192 var percent = parseInt(100 * imagesLoaded / (imageURLs.length - 1)); // calculate progress
194 $("#progressbar").progressbar({ value: percent }); // refresh progress bar
195 $("#progressbarTitle").text("Preloading progress: " + percent + "%");
197 preloadImages(); // preload next image
201 ###A small step to get better(还能做的更好)
202 预加载图片是一个改善用户体验,使网站显得更为专业的一个简单手段。通过程序自动分析Css规则,你能够不费力气的处理大量图片。
203 进度条提示用户大概还要等待加载的时间,有效提高了用户满意度。jQuery还提供了许多像进度条一样超棒的插件,使用这些插件无疑能使你的网站蓬荜生辉。
205 [这个链接](http://gallery.msdn.microsoft.com/scriptjunkie/Improving-User-Experience-7a95c238)包含本文所有实例,你可以略作修改再整合到自己的项目中去。