〇、适用场景
在前端开发中,为了优化用户体验,我们经常有等背景图完成之后显示内容、加个遮罩等待页面加载完成之后消失等需求。
本文以下述的特殊情况(监听 background-image 的加载)为主,当然也通用于各种需要监听加载完成、失败事件的场景。
特例—— background-image 的特殊处理
由于 css 中的 background-image 会在 DOM 加载完成之后,再进行加载,所以我们无法直接使用 document 的加载完成事件进行监听。
目前看来,主流的解决方案是在文档流中添加一个隐藏的图片,这个图片和背景图片的路径是相同的。然后利用以下两个特性来实现:
- 文档流中的元素会先于 css 中的图片加载
- 图片加载完成一次之后,再次使用会读取缓存
一、原生JavaScript解决方案
1. 在 HTML 中加入 img 标签实现预加载
HTML部分
<img id="preload" src="background设置的图片路径">
JS部分
var preload = document.getElementById('preload');
preload.onload = preload.onreadystatechange = function(){
if(!this.readyState||this.readyState==='loaded'||this.readyState==='complete'){
// 加载完成
alert('ok');
}
};
封装版本
/**
* 监听图片加载完成事件
*
* @param {String} element_id 图片元素的id
* @param {VoidFunction} successCallback 成功后的回调
* @param {VoidFunction} errorCallback 失败后的回调
*
* @event onload
* @version js
*/
function onload_image (element_id,successCallback,errorCallback) {
// 获取选择器
var preload = document.getElementById(element_id);
// 绑定加载成功事件
preload.onload = preload.onreadystatechange = function(){
// 兼容 IE 低版本
if(!this.readyState||this.readyState==='loaded'||this.readyState==='complete'){
// 加载完成
successCallback();
}
};
// 绑定加载失败事件
preload.onerror = function (e) {
// 如果存在则调用
errorCallback && errorCallback(e);
// 清空已创建的Image对象及其绑定了的事件
preload = preload.onload = preload.onerror = null;
}
}
2. 模拟插入图片
众所周知,DOM 操作会影响性能(参见 Virtual Dom)。
所以我们可以利用原生 JS 创建一个 Image 对象,模拟在 DOM 中创建图片标签的行为,这样也会触发图片的加载。
既可以达到和 DOM 中插入图片标签相同的效果,又避免了直接操作 DOM 浪费的性能,而且不用对原有的 DOM 产生污染。
遗憾的是,在 jQuery 中并没有找到替代直接操作 DOM 的办法。
JS
/**
* 监听图片加载完成事件
*
* @param {String} url 要加载的图片地址
* @param {VoidFunction} successCallback 成功后的回调
* @param {VoidFunction} errorCallback 失败后的回调
*
* @event onload
* @version js
*/
function onload_image (url, successCallback, errorCallback) {
var img = new Image() //创建一个Image对象,实现图片的预下载
img.src = url
if (img.complete)//如果图片已经存在于浏览器缓存,直接调用回调函数
{
successCallback(img, img.width, img.height)
return // 直接返回,不用再处理onload事件
}
// 给图片添加 onload 事件
img.onload = function () {
successCallback && successCallback(img)//将回调函数的this替换为Image对象
}
// 给图片添加 onerror 事件
img.onerror = function () {
errorCallback && errorCallback(img)
// 清空已创建的Image对象及其绑定了的事件
img = img.onload = img.onerror = null
}
}
二、JQuery解决方案
通用写法
截止到当前(git build版本v4.0.0-pre),本写法适用于 v1.7.0 及之后的所有 jQuery 版本。
HTML 部分
<img id="preload" src="background设置的图片路径">
<script scr="大于等于 1.7.0 版本的 jQuery 地址"></script>
JS部分
$(function () {
// 如果有缓存,直接触发
if ($('#preload')[0].complete) {
// do something
alert("图片加载完成");
}
// 如果没有缓存,在加载成功之后再执行
$("#preload").on("load", function (e) {
// do something
alert("图片加载完成");
})
})
兼容写法
Note: This API has been removed in jQuery 3.0; please use
https://api.jquery.com/on/.on( "load", handler )
instead of.load( handler )
and.trigger( "load" )
instead of.load()
.
如上所述,jQuery 在 1.8 版本弃用、并于 3.0 版本正式移除了 .load()
的写法。
移除后,改用 .on("load", 事件触发的回调)
来替代加载事件的监听;用 .trigger("load")
来触发加载事件。
经测试,1.0.0 及以上版本,3.0.0 以下版本均适用。
HTML 部分
<img id="preload" src="background设置的图片路径">
<script scr="大于等于 1.7.0 版本的 jQuery 地址"></script>
JS 部分
$(function () {
// 如果有缓存,直接触发
if ($('#preload')[0].complete) {
// do something
alert("图片加载完成");
}
$("#preload").load(function () {
// do something
alert("图片加载完成");
})
});
三、使用JS框架
像 React、Vue 之类的现代 JS 框架,都为我们封装了很多方便开发的特性。
尤其是 Virtual Dom ,由于不用直接操作 DOM ,节省了很多的性能。而且他们都不约而同地选择了通过状态(state)决定组件是否加载。
合理选用 JS 框架,可以很大幅度地提升我们的开发效率。