JavaScript图片预加载的三种核心技术与兼容性处理

作者:竹影时间:2026-10-29 22:19:23浏览:431

本文深入解析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以内)浏览级别的图片,则能达到“秒杀”效果。

作者最新文章