Wstępne ładowanie obrazów za pomocą JavaScript


263

Czy funkcja, którą napisałem poniżej, jest wystarczająca, aby wstępnie załadować obrazy w większości, jeśli nie we wszystkich, przeglądarkach powszechnie używanych obecnie?

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

Mam tablicę adresów URL obrazów, które zapętlam i wywołuję preloadImagefunkcję dla każdego adresu URL.


19
Pamiętaj, że niektóre (wszystkie?) Przeglądarki zwolnią obraz po kilku sekundach, jeśli go nie użyłeś. Aby tego uniknąć, przechowuj odniesienie do imgobiektu, np. W tablicy w zakresie nadrzędnym.
Tamlyn

3
Co rozumiesz przez „uwolnienie obrazu”? Jeśli przeglądarka zapisała go w pamięci podręcznej, pozostanie tam, prawda?
Francisc

56
Nieco krótszy:(new Image()).src = url;
mrzmyr

20
pamiętaj, że to nie zadziała, gdy chrome devtool jest otwarty i funkcja „wyłącz pamięć podręczną” jest włączona w panelu sieci
Wayou

6
Jeszcze krótszy:new Image().src = url;
Daniel X Moore,

Odpowiedzi:


163

Tak. Powinno to działać we wszystkich głównych przeglądarkach.


48
Uwaga moderatora: Przestań oznaczać to jako „brak odpowiedzi”. Jest to w rzeczywistości bezpośrednia odpowiedź na zadane pytanie. Jeśli uważasz, że odpowiedź jest nieprawidłowa lub niewystarczająco poparta dowodami, zgłoś ją.
Cody Gray

43

Spróbuj tego, myślę, że to jest lepsze.

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"
)

Źródło: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/


3
onloaddla żadnego ze zdjęć nie ma obsługi
Benny

12
Czy możesz wyjaśnić, dlaczego tak jest lepiej?
JJJ

2
@BeNice Myślę, że nie rozumiesz, ładowanie obrazów jest asynchroniczne, dlatego musisz obsługiwać onloadstan lub po prostu tworzysz obrazy w pamięci.
Benny

3
Jeśli korzystasz z tego rozwiązania, nie zapomnij instrukcji var dla zmiennej i. W przeciwnym razie zostanie ustawiony globalnie, co może powodować błędy, które są naprawdę trudne do rozwiązania (chyba że wiesz, że zapomniałeś instrukcji var.for (var i = 0.....
Mohammer,

2
@Mohammer dzięki za to, właśnie skopiowałem kod z linku, który podałem. var
Zmienię

26

W moim przypadku użyteczne było dodanie wywołania zwrotnego do funkcji onloadzdarzenia:

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

A następnie owiń go w przypadku tablicy adresów URL obrazów, które mają być wstępnie załadowane z wywołaniem zwrotnym na wszystkich, jest zrobione: 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 Alternative: 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 Alternative: 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);
}

UWAGA: Obrazy w kontenerze display:nonemogą nie zostać wstępnie załadowane. Być może widoczność: ukryty będzie działał lepiej, ale tego nie przetestowałem. Dzięki Marco Del Valle za zwrócenie na to uwagi


Dzięki mplungjan. Chociaż nie pomaga mi to w tym konkretnym przypadku, dobrze jest wiedzieć.
Francisc

5
Czy mam rację mówiąc, że spowolni to ładowanie strony, ponieważ obrazy te muszą zostać pobrane, zanim strona będzie mogła uruchomić ładowanie?
jakubiszon

3
Nie sądzę, aby działały one we wszystkich przeglądarkach. Wiem, że w Chrome obrazy nie są ładowane, dopóki nie będą widoczne. Konieczne będzie usunięcie wyświetlacza: brak; i zamiast tego spróbuj ustawić je tak, aby nie były widoczne. Może następnie ukryć się w JS po załadowaniu wszystkiego, jeśli to konieczne.
Bullyen

3
Obrazy tła w elemencie display: nonenie zostaną wstępnie załadowane.
Marquizzo,

1
Metoda ta pracował dla mnie tylko ja (1) nie używać display: none, (2) nie używać width/height: 0, (3) put no-repeata stanowisko takie, że obraz będzie na zewnątrz ( -<width>px -<height>pxbędzie Ci tam, więc jeśli twoje zdjęcia są 100px w wysokość lub mniej, można użyć 0 -100px, innymi słowy url(...) no-repeat 0 -100px.
Alexis Wilke

11

Zalecam użycie try / catch, aby zapobiec niektórym możliwym problemom:

OOP:

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

Standard:

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

Ponadto, chociaż uwielbiam DOM, stare głupie przeglądarki mogą mieć problemy z używaniem DOM, więc unikaj go całkowicie IMHO w przeciwieństwie do wkładu Freedev. Image () ma lepszą obsługę w starych przeglądarkach śmieci.


7
Nie sądzę, aby w ten sposób wychwytywały się błędy podczas ładowania obrazu w javascript - istnieje coś takiego jak _img.onerror, który może (powinien?) Zostać użyty.
Greg0ry,

3
Jakie są „możliwe problemy”?
Kissaki,

Problemy takie jak obsługa polecenia. Sprawdź witrynę „czy mogę używać”, aby sprawdzić, czy przeglądarka, którą musisz obsługiwać, obsługuje komendę javascript.
Dave

10

To podejście jest nieco bardziej rozbudowane. Tutaj przechowujesz wszystkie wstępnie załadowane obrazy w kontenerze, może to być div. A po tym, jak możesz pokazać obrazy lub przenieść je w DOM do właściwej pozycji.

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;
}

Spróbuj tutaj , dodałem także kilka komentarzy


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

Tak, to zadziała, jednak przeglądarki się ograniczą (między 4-8) rzeczywiste połączenia, a tym samym nie będą buforować / ładować wstępnie wszystkich pożądanych obrazów.

Lepszym sposobem na to jest wywołanie onload przed użyciem obrazu w następujący sposób:

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;
}

Czy możesz podać link do tego limitu przeglądarek?
nulll

Trudno znaleźć referencję (przynajmniej nigdy jej nie znalazłem), ale użyłem własnego projektu do przetestowania tego, przyglądając się uważnie temu, co zostało załadowane z pamięci podręcznej i wywołań serwera za pomocą karty sieciowej Chrome w narzędziach programistycznych Chrome (f12). Znalazłem to samo zachowanie w przeglądarce Firefox i korzystaniu z telefonu komórkowego.
Robin,

1
-1, ponieważ dokładnie przetestowałem twierdzenie w pierwszym zdaniu tutaj i stwierdziłem, że jest ono fałszywe, przynajmniej w nowoczesnych przeglądarkach. Zobacz stackoverflow.com/a/56012084/1709587. Tak, istnieje ograniczenie maksymalnej liczby równoległych żądań HTTP, które przeglądarka jest skłonna wysłać, ale w końcu zaczyna żądać każdego adresu URL, na który wywołujesz preloadImage, nawet jeśli robisz to tak, jak pokazano w pytaniu.
Mark Amery

5

Oto moje podejście:

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

Rozwiązanie dla przeglądarek zgodnych z ECMAScript 2017

Uwaga: zadziała to również, jeśli używasz transpilatora takiego jak 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();

Mogłeś także poczekać na załadowanie wszystkich zdjęć.

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);
}

Lub użyj, Promise.allaby załadować je równolegle.

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);
}

Więcej o obietnicach .

Więcej informacji o funkcjach „Async” .

Więcej informacji na temat zadania restrukturyzacji .

Więcej informacji o ECMAScript 2015 .

Więcej informacji o ECMAScript 2017 .


2

Mogę potwierdzić, że podejście zawarte w pytaniu jest wystarczające, aby obrazy zostały pobrane i zapisane w pamięci podręcznej (chyba, że ​​przeglądarka zrobiła to za pomocą nagłówków odpowiedzi), przynajmniej:

  • Chrome 74
  • Safari 12
  • Firefox 66
  • Edge 17

Aby to przetestować, stworzyłem małą aplikację internetową z kilkoma punktami końcowymi, z których każdy śpi przez 10 sekund przed podaniem zdjęcia kota. Następnie dodałem dwie strony internetowe, z których jedna zawierała <script>tag, w którym każda z kociąt jest wstępnie załadowana za pomocą preloadImagefunkcji z pytania, a druga zawiera wszystkie kocięta na stronie za pomocą <img>tagów.

We wszystkich powyższych przeglądarkach stwierdziłem, że jeśli najpierw odwiedzę stronę modułu wstępnego ładowania, zaczekam chwilę, a następnie przejdę na stronę z <img>tagami, moje kocięta wyrenderują się natychmiast. To pokazuje, że moduł wstępnego ładowania pomyślnie załadował kocięta do pamięci podręcznej we wszystkich testowanych przeglądarkach.

Możesz sprawdzić lub wypróbować aplikację, której użyłem do przetestowania tego na https://github.com/ExplodingCabbage/preloadImage-test .

W szczególności zauważ, że ta technika działa w powyższych przeglądarkach, nawet jeśli liczba zapętlonych obrazów przekracza liczbę równoległych żądań, które przeglądarka jest skłonna wysyłać jednocześnie, w przeciwieństwie do sugestii Robina . Tempo, w jakim obrazy preload będzie oczywiście ograniczona jak wiele równoległych żąda przeglądarka jest gotowa do wysłania, ale to będzie ostatecznie wystąpić każdy adres URL obrazu, który nazywamy preloadImage()dalej.


Czy to ważne, czy obrazy są buforowane w pamięci, czy na dysku? wygląda na to, że metoda OP jest buforowana na dysku w przeciwieństwie do metody @clintgh, która jest buforowana w pamięci.
Chris L

1

Możesz przenieść ten kod do index.html, aby wstępnie załadować obrazy z dowolnego adresu URL

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

0

Przeglądarka będzie działać najlepiej za pomocą znacznika link w nagłówku.

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);
    });
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.