跳至正文
来自: 首页 » Coder Life » Front End » JS监听元素的加载完成/失败事件

JS监听元素的加载完成/失败事件

〇、适用场景

在前端开发中,为了优化用户体验,我们经常有等背景图完成之后显示内容、加个遮罩等待页面加载完成之后消失等需求。

本文以下述的特殊情况(监听 background-image 的加载)为主,当然也通用于各种需要监听加载完成、失败事件的场景。

特例—— background-image 的特殊处理

由于 css 中的 background-image 会在 DOM 加载完成之后,再进行加载,所以我们无法直接使用 document 的加载完成事件进行监听。

目前看来,主流的解决方案是在文档流中添加一个隐藏的图片,这个图片和背景图片的路径是相同的。然后利用以下两个特性来实现:

  1. 文档流中的元素会先于 css 中的图片加载
  2. 图片加载完成一次之后,再次使用会读取缓存

一、原生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 .on( "load", handler ) instead of .load( handler ) and .trigger( "load" ) instead of .load().

https://api.jquery.com/on/

如上所述,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 框架,可以很大幅度地提升我们的开发效率。

发表回复

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据