本文深入解析JavaScript图片预加载的三种主流方案:基于CSS背景图的轻量级预加载、纯JavaScript实现的多图批量预加载,以及利用Ajax预加载CSS与JS的综合方案。重点探讨了不同浏览器对缓存图片onload事件处理的差异,特别是IE与Opera的兼容性问题,并提供了基于complete属性的通用解决方案,旨在提升网站加载速度与用户体验。
图片预加载概述
图片预加载是提升用户体验的有效手段。通过将图片提前加载至浏览器缓存,访问者在浏览网站时能获得流畅、快速的视觉体验。这对于以图片展示为主的画廊或内容型网站尤为重要,它能确保图片无缝、即时地呈现,从而优化用户的整体浏览感受。本文将介绍三种不同的预加载技术,旨在增强网站性能与可用性。
方法一:CSS与JavaScript协同预加载
方法一:利用CSS与JavaScript协同实现预加载
实现图片预加载的方式多样,涵盖纯CSS、纯JavaScript以及两者的混合应用。这些技术可根据具体的设计需求灵活组合,提供高效且适配各种场景的解决方案。
仅使用CSS即可轻松高效地完成图片预加载,具体实现代码如下:
#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; }
#preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; }
#preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }
将上述三个ID选择器应用于(X)HTML元素,即可通过CSS的background属性将图片加载至屏幕外的背景区域。只要图片路径固定,当这些图片在页面其他位置被调用时,浏览器便会直接使用已缓存的预加载图片进行渲染。这种方式简单且高效,无需编写任何JavaScript代码。
尽管该方法高效,但仍存在改进空间。使用此法时,预加载图片会与页面其他资源同步加载,可能增加页面的整体加载时间。为解决这一问题,可引入JavaScript代码,将预加载动作推迟至页面完全加载后再执行。具体代码如下:
function preloader() {
if (document.getElementById) {
document.getElementById("preload-01").style.background = "url(http://domain.tld/image-01.png) no-repeat -9999px -9999px";
document.getElementById("preload-02").style.background = "url(http://domain.tld/image-02.png) no-repeat -9999px -9999px";
document.getElementById("preload-03").style.background = "url(http://domain.tld/image-03.png) no-repeat -9999px -9999px";
}
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(preloader);
在脚本的第一部分,我们获取具有特定类选择器的元素,并为其设置background属性,从而触发不同图片的预加载。
脚本的第二部分利用addLoadEvent()函数,将preloader()函数的执行时间延迟至页面加载完毕之后。
若用户的浏览器不支持JavaScript,结果如何?答案很简单:图片不会被预加载,但在页面正常调用图片时,仍能正常显示,不影响基本功能。
方法二:纯JavaScript预加载方案
方法二:纯JavaScript实现图片预加载
上述方法虽高效,但在实际开发中可能耗时较多。相比之下,我更倾向于使用纯JavaScript来实现图片预加载。下文将提供两种基于纯JavaScript的预加载方案,它们能在所有现代浏览器中良好运行。
JavaScript代码段1
只需简单编辑并指定所需图片的路径与名称即可实现,操作十分简便:
<div class="hidden">
<script type="text/javascript">
var images = new Array()
function preload() {
for (i = 0; i < preload.arguments.length; i++) {
images[i] = new Image()
images[i].src = preload.arguments[i]
}
}
preload(
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
)
</script>
</div>
该方法特别适用于需要预加载大量图片的场景。例如,我的画廊网站利用该技术预加载了超过50张图片。将此脚本应用于登录页面,当用户输入账号时,大部分画廊图片即可被预加载。
JavaScript代码段2
该方法与前述方法类似,同样支持预加载任意数量的图片。将以下脚本添加至任意Web页面,并根据实际需求调整代码即可。
<div class="hidden">
<script type="text/javascript">
if (document.images) {
img1 = new Image();
img2 = new Image();
img3 = new Image();
img1.src = "http://domain.tld/path/to/image-001.gif";
img2.src = "http://domain.tld/path/to/image-002.gif";
img3.src = "http://domain.tld/path/to/image-003.gif";
}
</script>
</div>
如代码所示,每加载一张图片都需要创建一个变量(例如“img1 = new Image();”),并声明图片源地址(例如“img3.src = "../path/to/image-003.gif";”)。参照此模式,可根据需要加载任意数量的图片。
我们对上述方法进行了进一步优化:将脚本封装进一个函数中,并使用addLoadEvent()函数,将预加载时间延迟至页面加载完毕后再执行。
function preloader() {
if (document.images) {
var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
img1.src = "http://domain.tld/path/to/image-001.gif";
img2.src = "http://domain.tld/path/to/image-002.gif";
img3.src = "http://domain.tld/path/to/image-003.gif";
}
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(preloader);
方法三:Ajax综合预加载
方法三:使用Ajax实现图片预加载
前述方法虽然实用,但或许不够“酷”。现在介绍一种基于Ajax的图片预加载方案。该方法利用DOM技术,不仅预加载图片,还能预加载CSS、JavaScript等相关资源。与纯JavaScript相比,Ajax方案的优越之处在于,JavaScript和CSS的加载不会阻塞或影响当前页面的渲染。该方法简洁且高效。
window.onload = function() {
setTimeout(function() {
// XHR to request a JS and a CSS
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.js');
xhr.send('');
xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.css');
xhr.send('');
// preload image
new Image().src = "http://domain.tld/preload.png";
}, 1000);
};
上述代码预加载了“preload.js”、“preload.css”和“preload.png”。其中1000毫秒的超时设置是为了防止脚本挂起,从而避免对正常页面功能造成干扰。
接下来,我们看看如何通过JavaScript实现该加载过程:
window.onload = function() {
setTimeout(function() {
// reference to <head>
var head = document.getElementsByTagName('head')[0];
// a new CSS
var css = document.createElement('link');
css.type = "text/css";
css.rel = "stylesheet";
css.href = "http://domain.tld/preload.css";
// a new JS
var js = document.createElement("script");
js.type = "text/javascript";
js.src = "http://domain.tld/preload.js";
// preload JS and CSS
head.appendChild(css);
head.appendChild(js);
// preload image
new Image().src = "http://domain.tld/preload.png";
}, 1000);
};
在此,我们通过DOM创建三个元素来实现对三个文件的预加载。正如前文所述,使用Ajax方式加载文件不会应用到当前加载页面上。从这一角度来看,Ajax方法优于纯JavaScript方案。
加载回调与浏览器兼容性处理
补充:加载完毕回调函数
我们编写如下代码:
function loadImage(url, callback) {
var img = new Image();
img.src = url;
img.onload = function(){ //图片下载完毕时异步调用callback函数。
callback.call(img); // 将callback函数this指针切换为img。
};
}
在Firefox中进行测试,效果符合预期:图片下载完成后,会弹出图片的宽度信息。无论点击多少次或刷新页面,结果均保持一致。
然而,此时切勿过早高兴——还需考虑浏览器的兼容性,因此立即在IE中进行测试。结果显示,初次点击确实弹出了图片宽度。但再次点击“load”按钮时,却没有任何反应,刷新页面后情况依旧。
经过对多个浏览器版本的测试,发现IE6和Opera均存在此问题,而Firefox和Safari表现正常。原因其实很简单,这与浏览器的缓存机制有关。当图片已被加载过一次后,若再次请求该图片,浏览器会直接从缓存中加载,而不再发起新的HTTP请求。对于Firefox和Safari,它们试图使这两种加载方式对用户透明,因此仍会触发图片的onload事件;而IE和Opera则忽略了这种一致性,不会触发图片的onload事件,导致上述代码在它们之中无法生效。
如何解决这一问题?理想情况下,Image对象应有一个状态值来表明图片是否已成功载入。从缓存加载时,由于无需等待,该状态值应直接表明“已下载”;而从HTTP请求加载时,因需等待下载,该值应显示为“未完成”。据此思路,问题便可迎刃而解。
经过深入分析,最终发现一个各浏览器均兼容的Image属性——complete。因此,在图片onload事件触发前,先对该值进行判断即可。最终代码如下:
function loadImage(url, callback) {
var img = new Image(); //创建一个Image对象,实现图片的预下载
img.src = url;
if (img.complete) { // 如果图片已经存在于浏览器缓存,直接调用回调函数
callback.call(img);
return; // 直接返回,不用再处理onload事件
}
img.onload = function () { //图片下载完毕时异步调用callback函数。
callback.call(img);//将回调函数的this替换为Image对象
};
};
虽然代码简洁,但非常实用。
进阶:快速获取图片尺寸技巧
附:再谈JavaScript图片预加载
Lightbox类效果为了实现图片居中显示而采用预加载,需等待图片完全加载完毕才能显示,用户体验不佳(例如filick相册的全屏效果)。JavaScript无法获取img文件头数据,真的是这样吗?本文通过一种巧妙的方法让JavaScript能够获取该数据。
这是大多数人使用预加载获取图片大小的示例代码:
var imgLoad = function (url, callback) {
var img = new Image();
img.src = url;
if (img.complete) {
callback(img.width, img.height);
} else {
img.onload = function () {
callback(img.width, img.height);
img.onload = null;
};
};
};
可以看到
上述代码必须等待图片加载完毕才能获取尺寸,速度令人不敢恭维,我们需要对其进行改进。
Web应用程序与桌面应用程序的区别在于,响应速度才是最佳的用户体验。若希望速度与优雅兼得,就必须提前获取图片尺寸。那么,如何在图片尚未加载完毕时就能获取其尺寸呢?
基于十多年的上网经验,我发现浏览器在加载图片时,会先占用一块区域,然后才慢慢加载完毕,且无需预设width与height属性,因为浏览器能够获取图片的头部数据。基于此原理,只需使用JavaScript定时侦测图片的尺寸状态,即可得知图片尺寸是否就绪。
当然,实际应用中存在一些兼容陷阱,例如各浏览器对width与height检测的不一致,以及WebKit中new Image()建立的图片会受到同URL图片加载进程的影响。经过反复测试,得出最佳处理方式:
// 更新:
// 05.27: 1、保证回调执行顺序:error > ready > load;2、回调函数this指向img本身
// 04-02: 1、增加图片完全加载后的回调 2、提高性能
/**
* 图片头数据加载就绪事件 - 更快获取图片尺寸
* @version 2011.05.27
* @author TangBin
* @see http://www.planeart.cn/?p=1121
* @param {String} 图片路径
* @param {Function} 尺寸就绪
* @param {Function} 加载完毕 (可选)
* @param {Function} 加载错误 (可选)
* @example imgReady('http://www.google.com.hk/intl/zh-CN/images/logo_cn.png', function () {
alert('size ready: width=' + this.width + '; height=' + this.height);
});
*/
var imgReady = (function () {
var list = [], intervalId = null,
// 用来执行队列
tick = function () {
var i = 0;
for (; i < list.length; i++) {
list[i].end ? list.splice(i--, 1) : list[i]();
};
!list.length && stop();
},
// 停止所有定时器队列
stop = function () {
clearInterval(intervalId);
intervalId = null;
};
return function (url, ready, load, error) {
var onready, width, height, newWidth, newHeight,
img = new Image();
img.src = url;
// 如果图片被缓存,则直接返回缓存数据
if (img.complete) {
ready.call(img);
load && load.call(img);
return;
};
width = img.width;
height = img.height;
// 加载错误后的事件
img.onerror = function () {
error && error.call(img);
onready.end = true;
img = img.onload = img.onerror = null;
};
// 图片尺寸就绪
onready = function () {
newWidth = img.width;
newHeight = img.height;
if (newWidth !== width || newHeight !== height ||
// 如果图片已经在其他地方加载可使用面积检测
newWidth * newHeight > 1024
) {
ready.call(img);
onready.end = true;
};
};
onready();
// 完全加载完毕的事件
img.onload = function () {
// onload在定时器时间差范围内可能比onready快
// 这里进行检查并保证onready优先执行
!onready.end && onready();
load && load.call(img);
// IE gif动画会循环执行onload,置空onload即可
img = img.onload = img.onerror = null;
};
// 加入队列中定期执行
if (!onready.end) {
list.push(onready);
// 无论何时只允许出现一个定时器,减少浏览器性能损耗
if (intervalId === null) intervalId = setInterval(tick, 40);
};
};
})();
调用示例:
imgReady('http://www.google.com.hk/intl/zh-CN/images/logo_cn.png', function () {
alert('size ready: width=' + this.width + '; height=' + this.height);
});
是否非常简单?这种方式获取摄影级别照片尺寸的速度往往是onload方式的几十倍,而对于Web普通(800×600以内)浏览级别的图片,则能达到“秒杀”效果。