使用JavaScript预加载图片

386

我写的以下函数足以在今天常用的大多数浏览器中预加载图片吗?

function preloadImage(url)
{
    var img=new Image();
    img.src=url;
}

我有一个图片URL数组,会对每个URL调用preloadImage函数。


29
请注意,一些(或全部)浏览器会在几秒钟后释放图像资源,如果你没有使用它。为避免这种情况,请保留对img对象的引用,例如在父级范围的数组中。 - Tamlyn
7
“释放图片”是什么意思?如果图片被浏览器缓存了,那它会一直留在那里,对吗? - Francisc
68
稍微简洁一些:(new Image()).src = url; - e382df99a7950919789725ceeec126
40
请注意,当Chrome开发者工具处于打开状态且网络面板中启用了“禁用缓存”选项时,这种方法将无效。 - 牛さん
20
更简洁的写法:new Image().src = url; - Daniel X Moore
显示剩余12条评论
20个回答

293

是的。这适用于所有主流浏览器。


132
请停止将此标记为“不是答案”。事实上,它是对所提出问题的直接回答。如果您认为答案是错误的或缺乏足够证据支持,请投反对票 - Cody Gray

64

尝试这个,我认为这更好。

var images = [];
function preload() {
    for (var i = 0; i < arguments.length; i++) {
        images[i] = new Image();
        images[i].src = preload.arguments[i];
    }
}

//-- usage --//
preload(
    "http://domain.tld/gallery/image-001.jpg",
    "http://domain.tld/gallery/image-002.jpg",
    "http://domain.tld/gallery/image-003.jpg"
)

来源: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/


3
没有任何图像的onload处理程序。 - Benny Schmidt
24
你能解释一下为什么这样更好吗? - JJJ
3
@BeNice,我认为你误解了,加载图片是异步的,因此你必须处理“onload”状态,否则你只是在内存中实例化图片。 - Benny Schmidt
3
如果您使用此解决方案,请不要忘记为 i 变量添加 var 语句。否则它将被全局设置,可能会导致非常难以解决的错误(除非您知道自己忘记了 var 语句)。for (var i = 0..... - Mohammer
2
@Mohammer 谢谢你,我刚刚从我提供的链接中复制了代码。现在我会编辑代码以添加 var - clintgh
显示剩余4条评论

60

您可以将此代码移动到 index.html 中,以从任何 URL 预加载图像。


<link rel="preload" href="https://via.placeholder.com/160" as="image">

1
这个答案并没有得到足够的关注,实际上它是最不繁琐的方法之一。 - Michael Flores
1
同意。在预加载旋转木马中的图片方面,这非常有效。感谢您的发布! - Omar
3
好的,楼主问了如何在JS中预加载图片。但这不是JS。 - gabriel-kaam
1
这难道不是唯一保证可行的解决方案吗? - Leo
2
@KhomNazid 从该标记加载图像不会阻止呈现。您可以通过打开网络选项卡,设置限流并查看瀑布图来查看此情况。预加载的图像开始下载,但页面的其余部分在其下载过程中继续呈现。 - Benjamin Carlsson
显示剩余2条评论

43

在我的情况下,为您的函数添加一个 onload 事件回调函数是有用的:

function preloadImage(url, callback)
{
    var img=new Image();
    img.src=url;
    img.onload = callback;
}

如果要预加载图像URL的数组并在所有操作完成后执行回调,则可以使用以下代码进行封装: https://jsfiddle.net/4r0Luoy7/

function preloadImages(urls, allImagesLoadedCallback){
    var loadedCounter = 0;
  var toBeLoadedNumber = urls.length;
  urls.forEach(function(url){
    preloadImage(url, function(){
        loadedCounter++;
            console.log('Number of loaded images: ' + loadedCounter);
      if(loadedCounter == toBeLoadedNumber){
        allImagesLoadedCallback();
      }
    });
  });
  function preloadImage(url, anImageLoadedCallback){
      var img = new Image();
      img.onload = anImageLoadedCallback;
      img.src = url;
  }
}

// Let's call it:
preloadImages([
    '//upload.wikimedia.org/wikipedia/commons/d/da/Internet2.jpg',
  '//www.csee.umbc.edu/wp-content/uploads/2011/08/www.jpg'
], function(){
    console.log('All images were loaded');
});

39
const preloadImage = src => 
  new Promise((resolve, reject) => {
    const image = new Image()
    image.onload = resolve
    image.onerror = reject
    image.src = src
  })


// Preload an image
await preloadImage('https://picsum.photos/100/100')

// Preload a bunch of images in parallel 
await Promise.all(images.map(x => preloadImage(x.src)))

对于一个包含相当大的图像的轮播图,这在我的案例中完美地运作了,+1。 - Matthew Vandenberg
我也这样做了,但是 Google Page Insights 仍然告诉我需要预加载图片。 - Michael

23
CSS2替代方案:http://www.thecssninja.com/css/even-better-image-preloading-with-css2,可实现更好的图像预加载。
body:after {
  content: url(img01.jpg) url(img02.jpg) url(img03.jpg);
  display: none; 
}

CSS3的替代方法:https://perishablepress.com/preload-images-css3/(感谢Linh Dam)。
.preload-images {
  display: none; 
  width: 0;
  height: 0;
  background: url(img01.jpg),
              url(img02.jpg),
              url(img03.jpg);
}

注意:在带有display:none样式的容器中的图像可能无法预加载。 也许使用visibility:hidden样式效果更好,但我没有测试过。感谢Marco Del Valle指出这一点。


5
我翻译的内容:我说这样做会减慢页面加载速度,因为这些图片需要在页面启动加载事件之前先被下载。 - jakubiszon
可能。为什么不尝试一下呢? - mplungjan
3
我不相信这些方法在所有浏览器上都有效。我知道在Chrome浏览器中,图片只有在可见时才会加载。需要移除"display: none;"并尝试将它们定位到看不见的位置。如果需要,可以在所有内容加载完毕后使用JS来隐藏它们。 - Bullyen
3
具有display: none属性的元素中的背景图像将不会预加载。 - M -
1
这种方法对我有效,但是(1)我没有使用display: none,(2)也没有使用width/height: 0,(3)我使用了no-repeat和一个使图像位于外部的位置(-<width>px -<height>px将把它带到那里,所以如果您的图像高度都小于100px,您可以使用0 -100px,换句话说,url(...) no-repeat 0 -100px)。 - Alexis Wilke
显示剩余3条评论

14

2020年以来的有效解决方案

这篇帖子中的大多数答案已经不再起作用了(至少在Firefox上)。

以下是我的解决方案:

var cache = document.createElement("CACHE");
cache.style = "position:absolute;z-index:-1000;opacity:0;";
document.body.appendChild(cache);
function preloadImage(url) {
    var img = new Image();
    img.src = url;
    img.style = "position:absolute";
    cache.appendChild(img);
}

使用方法:

preloadImage("example.com/yourimage.png");

显然,<cache>不是一个“定义”元素,如果需要,可以使用<div>

在CSS中使用以下内容,而不是应用style属性:

cache {
    position: absolute;
    z-index: -1000;
    opacity: 0;
}

cache image {
    position: absolute;
}

如果你已经测试过,请留下评论。

注:

  • 不要将display: none;应用于缓存 - 这样不会加载图像。
  • 不要调整图像元素的大小,因为这也会影响到您在使用它时加载图像的质量。
  • position: absolute设置为图像是必要的,因为图像元素最终会超出视口范围 - 导致它们无法加载,并影响性能。

更新

虽然上面的解决方案可以工作,但我做了一些小更新以使其结构更好:

(现在也接受一个函数中的多个图像)

var cache = document.createElement("CACHE");
document.body.appendChild(cache);
function preloadImage() {
    for (var i=0; i<arguments.length; i++) {
        var img = new Image();
        img.src = arguments[i];
        var parent = arguments[i].split("/")[1]; // Set to index of folder name
        if ($(`cache #${parent}`).length == 0) {
            var ele = document.createElement("DIV");
            ele.id = parent;
            cache.appendChild(ele);
        }
        $(`cache #${parent}`)[0].appendChild(img);
        console.log(parent);
    }
}

preloadImage(
    "assets/office/58.png",
    "assets/leftbutton/124.png",
    "assets/leftbutton/125.png",
    "assets/leftbutton/130.png",
    "assets/leftbutton/122.png",
    "assets/leftbutton/124.png"
);

预览:

输入图片描述

注意事项:

  • 尽量不要同时预加载过多的图片(这可能会导致性能问题) - 我通过隐藏某些事件期间不可见的图片来解决这个问题。当需要时,再把它们显示出来。

你的简化版本解决了我的问题,谢谢。我确实不得不将appendChild调用移动到函数体中,以使其在页面头部区域工作,否则它会抱怨document.body为空。 - Stovey

12

这种方法稍微复杂一些。在这里,您将所有预加载的图像存储在一个容器中,可能是一个div。之后,您可以显示这些图片或将其移动到DOM中的正确位置。

function preloadImg(containerId, imgUrl, imageId) {
    var i = document.createElement('img'); // or new Image()
    i.id = imageId;
    i.onload = function() {
         var container = document.getElementById(containerId);
         container.appendChild(this);
    };
    i.src = imgUrl;
}

在这里试一下,我也添加了几个注释


11

我建议你使用try/catch来防止一些可能出现的问题:

面向对象编程(OOP):

    var preloadImage = function (url) {
        try {
            var _img = new Image();
            _img.src = url;
        } catch (e) { }
    }

标准:

    function preloadImage (url) {
        try {
            var _img = new Image();
            _img.src = url;
        } catch (e) { }
    }

此外,虽然我喜欢DOM,但古老而愚蠢的浏览器可能会出现使用DOM的问题,因此我认为最好完全避免使用它,与freedev的贡献相反。在旧垃圾浏览器中,Image()有更好的支持。


8
我认为这不是在JavaScript中加载图像时捕获错误的正确方式——应该使用类似于_img.onerror的东西(可能更好)。 - Greg0ry
3
“Possible issues”指的是可能存在的问题。 - Kissaki
问题如支持命令。请查看网站“can i use”以查看您需要支持的浏览器是否支持JavaScript命令。 - Dave
@Dave 我知道这很老了,但请将这些信息添加到您的答案中 ^^ - Toni Michel Caubet

9

ECMAScript 2017兼容浏览器的解决方案

注意:如果您使用类似Babel的转译器,此方法同样适用。

'use strict';

function imageLoaded(src, alt = '') {
    return new Promise(function(resolve) {
        const image = document.createElement('img');

        image.setAttribute('alt', alt);
        image.setAttribute('src', src);

        image.addEventListener('load', function() {
            resolve(image);
        });
    });
}

async function runExample() {
    console.log("Fetching my cat's image...");

    const myCat = await imageLoaded('https://placekitten.com/500');

    console.log("My cat's image is ready! Now is the time to load my dog's image...");

    const myDog = await imageLoaded('https://placedog.net/500');

    console.log('Whoa! This is now the time to enable my galery.');

    document.body.appendChild(myCat);
    document.body.appendChild(myDog);
}

runExample();

你也可以等待所有图片加载完毕。
async function runExample() {
    const [myCat, myDog] = [
        await imageLoaded('https://placekitten.com/500'),
        await imageLoaded('https://placedog.net/500')
    ];

    document.body.appendChild(myCat);
    document.body.appendChild(myDog);
}

或者使用 Promise.all 并行加载它们。

async function runExample() {
    const [myCat, myDog] = await Promise.all([
        imageLoaded('https://placekitten.com/500'),
        imageLoaded('https://placedog.net/500')
    ]);

    document.body.appendChild(myCat);
    document.body.appendChild(myDog);
}

更多关于Promise的信息.

更多关于“Async”函数的信息.

更多关于解构赋值的信息.

更多关于ECMAScript 2015的信息.

更多关于ECMAScript 2017的信息.


优秀的解决方案 - Adam Fowler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接