Предварительная загрузка изображений с помощью JavaScript


263

Достаточно ли написанной ниже функции для предварительной загрузки изображений в большинстве, если не во всех браузерах, обычно используемых сегодня?

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

У меня есть массив URL-адресов изображений, которые я зацикливаю и вызываю preloadImageфункцию для каждого URL-адреса.


19
Обратите внимание, что некоторые (все?) Браузеры выпустят изображение через несколько секунд, если вы его не использовали. Чтобы избежать этого, сохраняйте ссылку на imgобъект, например, в массиве в родительской области видимости.
— Тамлин

3
Что вы подразумеваете под «выпустить изображение»? Если он был кэширован браузером, он останется там, верно?
— Франциск

56
Немного короче:(new Image()).src = url;
— mrzmyr

20
обратите внимание, что это не будет работать, когда Chrome Devtool открыт и «отключить Chache» включен на панели сети
— Wayou

6
Еще короче:new Image().src = url;
— Даниэль Х Мур

Ответы:


163

Да. Это должно работать во всех основных браузерах.


48
Примечание модератора: пожалуйста, прекратите помечать это как "не ответ". Это, по сути, прямой ответ на вопрос, который был задан. Если вы считаете, что ответ неправильный или недостаточно подтвержден доказательствами, тогда опустите его.
— Коди Грей

43

Попробуйте это, я думаю, что это лучше.

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обработчика для любого из изображений
— Бенни

12
Не могли бы вы объяснить, почему это лучше?
— JJJ

2
@BeNice Я думаю, что вы не понимаете, загрузка изображений асинхронна, поэтому вы должны обрабатывать onloadсостояние, или вы просто создаете изображения в памяти.
— Бенни

3
Если вы используете это решение, не забудьте оператор var для i-переменной. В противном случае он будет установлен глобально, что может вызвать ошибку, которую действительно трудно решить (если только вы не знаете, что забыли оператор var.for (var i = 0.....
— Мохаммер

2
@Mohammer спасибо за это, я просто скопировал код из ссылки, которую я предоставил. Я сейчас отредактирую код, чтобы добавитьvar
— clintgh

26

В моем случае было полезно добавить функцию обратного вызова для 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');
});

19

Альтернатива 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/ (H / T Linh Dam)

.preload-images {
  display: none; 
  width: 0;
  height: 0;
  background: url(img01.jpg),
              url(img02.jpg),
              url(img03.jpg);
}

ПРИМЕЧАНИЕ. Изображения в контейнере display:noneмогут не загружаться предварительно. Возможно видимость: скрытый будет работать лучше, но я не проверял это. Спасибо Marco Del Valle за указание на это


Спасибо, mplungjan. Хотя это не помогает мне в этом конкретном случае, это полезно знать.
— Франциск

5
Правильно ли я говорю, что это замедлит загрузку страницы, потому что эти изображения должны быть загружены до того, как страница сможет запустить событие загрузки?
— Якубисон

3
Я не верю, что они будут работать на всех браузерах. Я знаю, что на Chrome изображения не загружаются, пока они не видны. Нужно будет снять дисплей: нет; и вместо этого попытайтесь расположить их так, чтобы их не было видно. Затем можно спрятать с JS после того, как все загрузится, если это необходимо.
— Bullyen

3
Фоновые изображения в элементе с display: noneне будут предварительно загружаться.
— Маркиззо

1
Этот метод работал у меня только я (1) не использовал display: none , (2) не использовал width/height: 0, (3) пут no-repeatи положение, такое, что изображение будет снаружи ( -<width>px -<height>pxприведет вас туда, так что если ваши изображения все 100px в высота или меньше, вы можете использовать 0 -100px, другими словами url(...) no-repeat 0 -100px.
— Alexis Вильке

11

Я рекомендую вам использовать try / catch, чтобы предотвратить некоторые возможные проблемы:

ООП:

    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 () имеет лучшую поддержку в старых мусорных браузерах.


7
Я не думаю, что именно так вы ловите ошибки при загрузке Image в javascript - есть что-то вроде _img.onerror, которое можно (нужно?) Использовать.
— Грегори

3
Каковы "возможные проблемы"?
— Киссаки

Такие вопросы, как поддержка команды. Зайдите на сайт «Могу ли я использовать», чтобы узнать, поддерживает ли браузер, который вы должны поддерживать, команду javascript.
— Дэйв

10

Этот подход немного сложнее. Здесь вы храните все предварительно загруженные изображения в контейнере, может быть 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;
}

Попробуйте здесь , я также добавил несколько комментариев


7
const preloadImage = src => 
  new Promise(r => {
    const image = new Image()
    image.onload = r
    image.onerror = r
    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)))

5

Да, это будет работать, однако браузеры будут ограничивать (между 4-8) фактические вызовы и, таким образом, не будут кэшировать / предварительно загружать все нужные изображения.

Лучший способ сделать это - вызвать onload перед использованием изображения следующим образом:

function (imageUrls, index) {  
    var img = new Image();

    img.onload = function () {
        console.log('isCached: ' + isCached(imageUrls[index]));
        *DoSomething..*

    img.src = imageUrls[index]
}

function isCached(imgUrl) {
    var img = new Image();
    img.src = imgUrl;
    return img.complete || (img .width + img .height) > 0;
}

Можете ли вы дать ссылку на некоторые ограничения этого браузера?
— nulll

Трудно найти ссылку (по крайней мере, я так и не нашел), но я использовал свой собственный проект, чтобы проверить это, внимательно изучая то, что было загружено из вызовов кеша и сервера, используя вкладку сети Chrome в инструментах разработчика Chrome (f12). Я обнаружил такое же поведение в Firefox и с помощью мобильного.
— Робин

1
-1 потому что я тщательно проверил утверждение в первом предложении и обнаружил, что оно неверно, по крайней мере, в современных браузерах. См. Stackoverflow.com/a/56012084/1709587. Да, существует ограничение на максимальное количество параллельных HTTP-запросов, которые браузер готов отправлять, но в конечном итоге он все же переходит к запросу каждого URL-адреса, на который вы вызываете preloadImage, даже если вы делаете это так, как показано в вопросе.
— Марк Амери

5

Вот мой подход:

var preloadImages = function (srcs, imgs, callback) {
    var img;
    var remaining = srcs.length;
    for (var i = 0; i < srcs.length; i++) {
        img = new Image;
        img.onload = function () {
            --remaining;
            if (remaining <= 0) {
                callback();
            }
        };
        img.src = srcs[i];
        imgs.push(img);
    }
};

4

Решение для браузеров, совместимых с 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);
}

Подробнее об обещаниях .

Подробнее о функциях "Async" .

Подробнее о деструктурирующем задании .

Подробнее о ECMAScript 2015 .

Подробнее о ECMAScript 2017 .


2

Я могу подтвердить, что подход в вопросе достаточен для запуска загрузки и кэширования изображений (если только вы не запретили браузеру делать это через заголовки ответа), по крайней мере, в:

  • Chrome 74
  • Safari 12
  • Firefox 66
  • Край 17

Чтобы проверить это, я сделал небольшое веб-приложение с несколькими конечными точками, каждое из которых спит в течение 10 секунд, а затем подает фотографию котенка. Затем я добавил две веб-страницы, одна из которых содержала <script>тег, в котором каждый из котят предварительно загружен с использованием preloadImageфункции из вопроса, а другая - всех котят на странице, используя<img> тегов.

Во всех браузерах, указанных выше, я обнаружил, что если я сначала зашел на страницу предварительного загрузчика, немного подождал, а затем зашел на страницу с <img>тегами, мои котята рендерились мгновенно. Это показывает, что предварительный загрузчик успешно загрузил котят в кеш во всех протестированных браузерах.

Вы можете посмотреть или опробовать приложение, которое я использовал для проверки этого, на https://github.com/ExplodingCabbage/preloadImage-test .

В частности, обратите внимание, что этот метод работает в указанных выше браузерах, даже если количество зацикленных изображений превышает количество параллельных запросов, которые браузер хочет сделать за раз, вопреки тому, что предлагает ответ Робина . Скорость предварительной загрузки ваших изображений, конечно, будет ограничена количеством параллельных запросов, которые браузер желает отправить, но это конечном итоге будет запрашивать каждый URL-адрес изображения, на который вы звоните preloadImage().


Имеет ли значение, кэшированы ли изображения в памяти или на диске? кажется, что метод OP кэшируется на диске, в отличие от метода @clintgh, который кэшируется в памяти.
— Крис Л

1

Вы можете переместить этот код в index.html для предварительной загрузки изображений с любого URL

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

0

Браузер будет работать лучше всего, используя тег ссылки в заголовке.

export function preloadImages (imageSources: string[]): void {
  imageSources
    .forEach(i => {
      const linkEl = document.createElement('link');
      linkEl.setAttribute('rel', 'preload');
      linkEl.setAttribute('href', i);
      linkEl.setAttribute('as', 'image');
      document.head.appendChild(linkEl);
    });
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.