Pré-carregando imagens com JavaScript

263

A função que escrevi abaixo é suficiente para pré-carregar imagens na maioria, senão em todos os navegadores mais usados ​​atualmente?

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

Eu tenho uma matriz de URLs de imagem que faço loop e chamo a preloadImagefunção para cada URL.

Francisc
fonte
19
Observe que alguns navegadores (todos?) Liberarão a imagem após alguns segundos, se você não a tiver usado. Para evitar isso, mantenha uma referência ao imgobjeto, por exemplo, em uma matriz no escopo pai.
Tamlyn
3
O que você quer dizer com "liberar a imagem"? Se foi armazenado em cache pelo navegador, ele permanecerá lá, certo?
Francisc
56
Um pouco mais curto:(new Image()).src = url;
mrzmyr 23/10
20
note que este não irá funcionar quando o Chrome DevTool está aberto e 'chache disable' está habilitado dentro do painel de rede
Wayou
6
Ainda mais curto:new Image().src = url;
Daniel X Moore

Respostas:

163

Sim. Isso deve funcionar em todos os principais navegadores.

Huzi --- Javiator
fonte
48
Nota do moderador: por favor, pare de sinalizar isso como "não uma resposta". É, de fato, uma resposta direta à pergunta que foi feita. Se você acha que a resposta está errada, ou insuficientemente apoiada por evidências, diminua -a.
Cody Gray
43

Tente isso, acho que isso é melhor.

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

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

clintgh
fonte
3
não há nenhum onloadmanipulador para qualquer uma das imagens
Benny
12
Você poderia explicar por que isso é melhor?
JJJ
2
@ BeNice Eu acho que você está entendendo mal, carregar imagens é assíncrono; portanto, você precisa lidar com o onloadestado ou apenas instanciar imagens na memória.
31516 Benny
3
Se você usar esta solução, não esqueça a instrução var para a variável i. Caso contrário, será definido globalmente, que pode causar erros que são realmente difíceis de resolver (a menos que você sabe que você esqueceu a declaração var.for (var i = 0.....
Mohammer
2
@ Mohammer obrigado por isso, eu apenas copiei o código do link que forneci. Vou editar o código agora para adicionar ovar
clintgh
26

No meu caso, foi útil adicionar um retorno de chamada à sua função para o onloadevento:

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

Em seguida, envolva-o para o caso de uma matriz de URLs em imagens a serem pré-carregadas com retorno de chamada em tudo: 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');
});
Alexander
fonte
19

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

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

NOTA: As imagens em um contêiner com display:nonepodem não ser pré-carregadas. Talvez visibilidade: oculto funcione melhor, mas eu não testei isso. Obrigado Marco Del Valle por apontar isso

mplungjan
fonte
Obrigado mplungjan. Embora não me ajude nesse caso em particular, é bom saber.
Francisc
5
Estou certo ao dizer que isso atrasará o carregamento da página porque essas imagens precisam ser baixadas antes que a página possa iniciar o evento load?
precisa saber é o seguinte
3
Não acredito que eles funcionem em todos os navegadores. Eu sei que no Chrome as imagens não são carregadas até ficarem visíveis. Será necessário remover a tela: nenhuma; e tente colocá-los para que não possam ser vistos. É possível ocultar com JS depois que tudo foi carregado, se necessário.
Bullyen
3
As imagens de plano de fundo em um elemento com display: nonenão serão pré-carregadas.
Marquizzo
1
Esse método funcionou para mim apenas que eu (1) não usei display: none, (2) não usei width/height: 0, (3) coloquei no-repeate coloquei uma posição em que a imagem ficaria do lado de fora ( -<width>px -<height>pxvocê chegará lá, por isso, se suas imagens tiverem 100px em altura ou menos, você pode usar 0 -100px, em outras palavras url(...) no-repeat 0 -100px.
Alexis Wilke
11

Eu recomendo que você use um try / catch para evitar alguns possíveis problemas:

OOP:

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

Padrão:

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

Além disso, enquanto eu amo o DOM, navegadores estúpidos antigos podem ter problemas com o uso do DOM, portanto evite-o completamente, ao contrário da contribuição do freedev. Image () tem melhor suporte em navegadores de lixo antigos.

Dave
fonte
7
Eu não acho que é assim que você captura erros ao carregar a imagem em javascript - existe algo como _img.onerror que pode (deve?) Ser usado.
precisa saber é o seguinte
3
O que são "possíveis problemas"?
Kissaki
Problemas como suporte ao comando. Confira o site "posso usar" para ver se um navegador para o qual você precisa oferecer suporte a um comando javascript.
Dave
10

Essa abordagem é um pouco mais elaborada. Aqui você armazena todas as imagens pré-carregadas em um contêiner, pode ser uma div. E depois você pode mostrar as imagens ou movê-las no DOM para a posição correta.

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

Experimente aqui , também adicionei alguns comentários

freedev
fonte
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)))
david_adler
fonte
5

Sim, isso funcionará, no entanto , os navegadores limitarão (entre 4-8) as chamadas reais e, portanto, não armazenarão em cache / pré-carregar todas as imagens desejadas.

Uma maneira melhor de fazer isso é chamar onload antes de usar a imagem da seguinte maneira:

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;
}
Robin
fonte
Você pode vincular alguma referência sobre esse limite de navegadores?
Nulll 10/07/16
É difícil encontrar uma referência (pelo menos nunca encontrei uma), mas usei meu próprio projeto para testá-la enquanto observava atentamente o que foi carregado das chamadas de cache e servidor usando a guia de rede do Chrome nas ferramentas de desenvolvedor do Chrome (f12). Encontrei o mesmo comportamento no Firefox e usando dispositivos móveis.
Robin
1
-1 porque testei cuidadosamente a afirmação na primeira frase aqui e achei que era falsa, pelo menos nos navegadores modernos. Consulte stackoverflow.com/a/56012084/1709587. Sim, há um limite para o número máximo de solicitações HTTP paralelas que o navegador está disposto a enviar, mas acaba solicitando cada URL para o qual você chama preloadImage, mesmo que você faça da maneira mostrada na pergunta.
Mark Amery
5

Aqui está a minha abordagem:

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);
    }
};
naeluh
fonte
4

Solução para navegadores compatíveis com ECMAScript 2017

Nota: isso também funcionará se você estiver usando um transpiler como o 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();

Você também pode ter esperado pelo carregamento de todas as imagens.

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

Ou use Promise.allpara carregá-los em paralelo.

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

Mais sobre promessas .

Mais sobre as funções "Async" .

Mais sobre a tarefa de desestruturação .

Mais sobre o ECMAScript 2015 .

Mais sobre o ECMAScript 2017 .

Amin NAIRI
fonte
2

Posso confirmar que a abordagem da pergunta é suficiente para acionar as imagens a serem baixadas e armazenadas em cache (a menos que você tenha proibido o navegador de fazê-lo por meio dos cabeçalhos de resposta) em, pelo menos:

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

Para testar isso, fiz um pequeno aplicativo da web com vários pontos de extremidade que dormem por 10 segundos antes de servir a foto de um gatinho. Em seguida, adicionei duas páginas da web, uma das quais continha uma <script>tag na qual cada um dos gatinhos é pré-carregado usando a preloadImagefunção da pergunta e a outra inclui todos os gatinhos da página usando <img>tags.

Em todos os navegadores acima, descobri que, se eu visitei a página do pré-carregador primeiro, esperei um pouco e depois fui para a página com as <img>tags, meus gatinhos renderizados instantaneamente. Isso demonstra que o pré-carregador carregou com êxito os gatinhos no cache em todos os navegadores testados.

Você pode ver ou experimentar o aplicativo que usei para testar isso em https://github.com/ExplodingCabbage/preloadImage-test .

Observe, em particular, que essa técnica funciona nos navegadores acima, mesmo que o número de imagens em loop exceda o número de solicitações paralelas que o navegador deseja fazer por vez, ao contrário do que a resposta de Robin sugere. A taxa na qual as imagens de pré-carregamento será, naturalmente, limitado por quantos paralelo solicita o navegador está disposta a enviar, mas irá , eventualmente, solicitar a cada URL da imagem que você chama preloadImage()diante.

Mark Amery
fonte
Importa se as imagens são armazenadas em cache na memória ou no disco? parece que o método OP é armazenado em cache no disco, em oposição ao método @clintgh, que é armazenado em cache na memória.
Chris L
1

Você pode mover esse código para index.html para pré-carregar imagens de qualquer URL

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

O navegador funcionará melhor usando a tag de link na cabeça.

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);
    });
}
Brendan Farquharson
fonte