Precarga de imágenes con JavaScript

263

¿La función que escribí a continuación es suficiente para precargar imágenes en la mayoría, si no en todos, los navegadores de uso común hoy en día?

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

Tengo una matriz de URL de imágenes que recorro y llamo a la preloadImagefunción para cada URL.

Francisc
fuente
19
Tenga en cuenta que algunos navegadores (¿todos?) Liberarán la imagen después de algunos segundos si no la ha usado. Para evitar esto, mantenga una referencia al imgobjeto, por ejemplo, en una matriz en el ámbito primario.
Tamlyn
3
¿Qué quieres decir con "liberar la imagen"? Si fue guardado en caché por el navegador, permanecerá allí, ¿verdad?
Francisc
56
Un poco más corto:(new Image()).src = url;
mrzmyr
20
tenga en cuenta que esto no funcionará cuando Chrome Devtool esté abierto y 'Deshabilitar chache' esté habilitado dentro del panel de red
Wayou
66
Aún más corto:new Image().src = url;
Daniel X Moore

Respuestas:

163

Si. Esto debería funcionar en todos los principales navegadores.

Huzi --- Javiator
fuente
48
Nota para el moderador: deje de marcar esto como "no es una respuesta". Es, de hecho, una respuesta directa a la pregunta que se hizo. Si cree que la respuesta es incorrecta o no está suficientemente respaldada con evidencia, entonces la rechaza .
Cody Gray
43

Prueba esto, creo que esto es mejor.

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

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

clintgh
fuente
3
no hay onloadcontrolador para ninguna de las imágenes
Benny
12
¿Podría explicar por qué esto es mejor?
JJJ
2
@BeNice Creo que está malentendido, cargar imágenes es asíncrono, por lo tanto, debe manejar el onloadestado o simplemente está creando instancias de imágenes en la memoria.
Benny
3
Si usa esta solución, no olvide la declaración var para la variable i. De lo contrario, se establecerá globalmente, lo que puede causar errores que son realmente difíciles de resolver (a menos que sepa que olvidó la declaración var.)for (var i = 0.....
Mohammer
2
@ Mohammer, gracias por esto, acabo de copiar el código del enlace que proporcioné. var
Editaré
26

En mi caso, fue útil agregar una devolución de llamada a su función para el onloadevento:

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

Y luego envolverlo para el caso de una matriz de URL a imágenes para ser precargadas con devolución de llamada en todo: 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');
});
Alejandro
fuente
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 CSS3: https://perishablepress.com/preload-images-css3/ (Presa H / T Linh)

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

NOTA: Las imágenes en un contenedor con display:nonepodrían no precargarse. Quizás visibilidad: oculto funcionará mejor, pero no lo he probado. Gracias Marco Del Valle por señalar esto

mplungjan
fuente
Gracias mplungjan. Aunque no me ayuda con este caso en particular, es bueno saberlo.
Francisc
55
¿Estoy en lo cierto al decir que esto ralentizará la carga de la página porque estas imágenes deben descargarse antes de que la página pueda iniciar el evento de carga?
jakubiszon
3
No creo que estos funcionen en todos los navegadores. Sé que en Chrome las imágenes no se cargan hasta que son visibles. Deberá eliminar la pantalla: ninguna; e intente colocarlos para que no se puedan ver. Luego puede esconderse con JS después de que todo se haya cargado si es necesario.
Bullyen
3
Las imágenes de fondo en un elemento con display: noneno se precargarán.
Marquizzo
1
Este método funcionó solo para mí. (1) no usé display: none, (2) no usé width/height: 0, (3) put no-repeaty una posición tal que la imagen estaría afuera (lo -<width>px -<height>pxllevará allí, por lo que si sus imágenes son 100px en altura o menos, puede usar 0 -100px, en otras palabras url(...) no-repeat 0 -100px.
Alexis Wilke
11

Le recomiendo que use un try / catch para evitar algunos posibles problemas:

OOP:

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

Estándar:

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

Además, aunque me encanta DOM, los navegadores viejos y estúpidos pueden tener problemas con el uso de DOM, así que evítelo por completo en mi humilde opinión contraria a la contribución de freedev. Image () tiene mejor soporte en navegadores de basura viejos.

Dave
fuente
77
No creo que así sea como se detectan los errores al cargar la imagen en JavaScript: hay algo como _img.onerror que se puede (¿debería?) Usar.
Greg0ry
3
¿Cuáles son los "posibles problemas"?
Kissaki
Problemas como el soporte para el comando. Consulte el sitio "¿Puedo usarlo?" Para ver si un navegador que debe admitir admite un comando de JavaScript.
Dave
10

Este enfoque es un poco más elaborado. Aquí almacena todas las imágenes precargadas en un contenedor, puede ser un div. Y después, podría mostrar las imágenes o moverlas dentro del DOM a la posición correcta.

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

Pruébalo aquí , también he agregado algunos comentarios

freedev
fuente
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
fuente
5

Sí, esto funcionará, sin embargo , los navegadores limitarán (entre 4 y 8) las llamadas reales y, por lo tanto, no almacenarán en caché / precargarán todas las imágenes deseadas.

Una mejor manera de hacer esto es llamar a onload antes de usar la imagen de esta manera:

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
fuente
¿Puede vincular alguna referencia sobre este límite de navegadores?
nulll
Es difícil encontrar una referencia (al menos nunca encontré una), pero utilicé mi propio proyecto para probar esto mientras miraba de cerca lo que se cargaba desde la memoria caché y las llamadas al servidor utilizando la pestaña de red de Chrome en las herramientas de desarrollador de Chrome (f12). Encontré el mismo comportamiento en Firefox y usando dispositivos móviles.
Robin
1
-1 porque probé cuidadosamente el reclamo en la primera oración aquí y descubrí que era falso, al menos en los navegadores modernos. Ver stackoverflow.com/a/56012084/1709587. Sí, hay un límite en el número máximo de solicitudes HTTP paralelas que el navegador está dispuesto a enviar, pero eventualmente puede solicitar cada URL que llame a preloadImage, incluso si lo hace de la manera que se muestra en la pregunta.
Mark Amery
5

Aquí está mi enfoque:

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
fuente
4

Solución para navegadores compatibles con ECMAScript 2017

Nota: esto también funcionará si está utilizando un transpilador como 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();

También podría haber esperado a que se cargaran todas las imágenes.

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

O use Promise.allpara cargarlos en 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);
}

Más sobre promesas .

Más acerca de las funciones "Async" .

Más sobre la tarea de desestructuración .

Más sobre ECMAScript 2015 .

Más sobre ECMAScript 2017 .

Amin NAIRI
fuente
2

Puedo confirmar que el enfoque en la pregunta es suficiente para activar las imágenes que se descargarán y almacenarán en caché (a menos que haya prohibido que el navegador lo haga a través de sus encabezados de respuesta) en, al menos:

  • Cromo 74
  • Safari 12
  • Firefox 66
  • Borde 17

Para probar esto, hice una pequeña aplicación web con varios puntos finales que cada uno duerme durante 10 segundos antes de servir una foto de un gatito. Luego agregué dos páginas web, una de las cuales contenía una <script>etiqueta en la que cada uno de los gatitos está precargado usando la preloadImagefunción de la pregunta, y la otra incluye todos los gatitos en la página usando<img> etiquetas.

En todos los navegadores anteriores, descubrí que si primero visité la página del precargador, esperé un rato y luego fui a la página con el <img> etiquetas, mis gatitos se mostraban al instante. Esto demuestra que el precargador cargó con éxito los gatitos en el caché en todos los navegadores probados.

Puede ver o probar la aplicación que usé para probar esto en https://github.com/ExplodingCabbage/preloadImage-test .

Tenga en cuenta en particular que esta técnica funciona en los navegadores anteriores, incluso si el número de imágenes que se superponen excede el número de solicitudes paralelas que el navegador está dispuesto a hacer a la vez, al contrario de lo que sugiere la respuesta de Robin . La velocidad a la que las imágenes de precarga, por supuesto, estará limitada por la cantidad paralelo pide al navegador está dispuesto a enviar, pero será finalmente solicitar a cada URL de la imagen se llama preloadImage()sucesivamente.

Mark Amery
fuente
¿Importa si las imágenes se almacenan en la memoria caché o en el disco? parece que el método OP se almacena en caché en el disco en lugar del método @clintgh que se almacena en memoria caché.
Chris L
1

Puede mover este código a index.html para precargar imágenes desde cualquier url

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

El navegador funcionará mejor usando la etiqueta de enlace en la cabeza.

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
fuente