Detectar cuando una imagen no se carga en Javascript

104

¿Hay alguna manera de determinar si la ruta de una imagen conduce a una imagen real, es decir, detectar cuando una imagen no se carga en Javascript?

Para una aplicación web, estoy analizando un archivo xml y creando dinámicamente imágenes HTML a partir de una lista de rutas de imágenes. Es posible que algunas rutas de imagen ya no existan en el servidor, por lo que quiero fallar con elegancia al detectar qué imágenes no se cargan y eliminar ese elemento img HTML.

Tenga en cuenta que las soluciones de JQuery no podrán usarse (el jefe no quiere usar JQuery, sí, sé que no me ayude a comenzar). Conozco una forma en JQuery de detectar cuándo se carga una imagen, pero no si falló.

Mi código para crear elementos img, pero ¿cómo puedo detectar si la ruta img conduce a una imagen que no se pudo cargar?

var imgObj = new Image();  // document.createElement("img");
imgObj.src = src;
sazr
fuente
Esto podría ayudarlo: stackoverflow.com/questions/1977871/… (es jQuery, pero aún puede llevarlo por el camino correcto)
Ben Lee
prueba uno de estos google.com/…
ajax333221
2
Divertido @ ajax333221 esta misma pregunta es la primera en los resultados de su enlace :)
SSH Este
escriba un selector de JQuery para encontrar un nuevo jefe ... Internet es un gran lugar.
JJS

Respuestas:

115

Puedes probar el siguiente código. Sin embargo, no puedo garantizar la compatibilidad del navegador, así que tendrás que probarlo.

function testImage(URL) {
    var tester=new Image();
    tester.onload=imageFound;
    tester.onerror=imageNotFound;
    tester.src=URL;
}

function imageFound() {
    alert('That image is found and loaded');
}

function imageNotFound() {
    alert('That image was not found.');
}

testImage("http://foo.com/bar.jpg");

¡Y mis condolencias por el jefe resistente a jQuery!

Nikhil
fuente
2
¿Alguna idea de si hay una forma de saber si encuentra un redireccionamiento?
quickshiftin
4
Esto no funciona en webKit (Epiphany, Safari, ...) dependiendo del servidor del que está obteniendo una imagen. Por ejemplo, a veces imgur no envía una imagen que existe ni un estado 404 y el evento de error no se activa en este caso.
El problema con esto es su mensaje, "Esa imagen no se encontró". El código de respuesta podría ser 500 o 403 o algo más. No sabe que era un 404. De hecho, no es muy probable que sea un 404 ya que probablemente no intentaría cargar una imagen que no está allí.
PHP Guru
1
debería agregar un controlador de eventos en lugar de una asignación directa
cdalxndr
50

La respuesta es buena, pero presenta un problema. Siempre que asigne onloado onerrordirectamente, puede reemplazar la devolución de llamada que se asignó anteriormente. Es por eso que hay un buen método que "registra el oyente especificado en el EventTarget al que se llama", como dicen en MDN . Puede registrar tantos oyentes como desee en el mismo evento.

Déjame reescribir un poco la respuesta.

function testImage(url) {
    var tester = new Image();
    tester.addEventListener('load', imageFound);
    tester.addEventListener('error', imageNotFound);
    tester.src = url;
}

function imageFound() {
    alert('That image is found and loaded');
}

function imageNotFound() {
    alert('That image was not found.');
}

testImage("http://foo.com/bar.jpg");

Debido a que el proceso de carga de recursos externos es asincrónico, sería aún mejor usar JavaScript moderno con promesas, como las siguientes.

function testImage(url) {

    // Define the promise
    const imgPromise = new Promise(function imgPromise(resolve, reject) {

        // Create the image
        const imgElement = new Image();

        // When image is loaded, resolve the promise
        imgElement.addEventListener('load', function imgOnLoad() {
            resolve(this);
        });

        // When there's an error during load, reject the promise
        imgElement.addEventListener('error', function imgOnError() {
            reject();
        })

        // Assign URL
        imgElement.src = url;

    });

    return imgPromise;
}

testImage("http://foo.com/bar.jpg").then(

    function fulfilled(img) {
        console.log('That image is found and loaded', img);
    },

    function rejected() {
        console.log('That image was not found');
    }

);
emil.c
fuente
2
tal vez me falta algo, pero ¿cómo puede la asignación (tester.onload = ..) reemplazar una devolución de llamada, si el "tester" se acaba de crear? incluso si alguna parte oscura del código agrega un evento a cada imagen creada, no es obvio que queramos mantener esos eventos con el propósito de detectar si existe una imagen.
jvilhena
1
Estás absolutamente en lo correcto. En este caso particular, puede que no sea un problema porque es muy probable que no sea reemplazado. Sin embargo, en general, agregar un detector de eventos es una mejor práctica que asignar un método directamente.
emil.c
1
Las funciones de @JohnWeisz Arrow son anónimas, por lo tanto, es más difícil de depurar, úselas con cuidado.
emil.c
10
@GifCo No me siento cómodo para continuar esa conversación contigo. Me siento ofendido por tu lenguaje. Si cree que mi respuesta es incorrecta o incompleta, sugiera cambios y explique su razonamiento. Si cree que algo es una MALA práctica, sugiera una buena práctica con su propia respuesta.
emil.c
2
¿Por qué hay una discusión sobre las funciones de flecha aquí? Está fuera de tema. La respuesta es correcta, especialmente porque los navegadores antiguos no admiten las funciones de flecha (Internet Explorer, por ejemplo).
PHP Guru
29

Esta:

<img onerror="this.src='/images/image.png'" src="...">
Rafael Martins
fuente
1
Si bien este es un atributo útil que se puede utilizar como una técnica de conmutación por error de imagen, una respuesta más detallada sería útil para que los lectores la comprendan.
sorak
1
¡Tan breve y tan útil! Para ocultarlo:<img src="your/path/that/might/fail.png" onerror="$(this).hide();"/>
J0ANMM
2
El comentario de @sorak me hizo reír con disgusto. Esta es la respuesta correcta.
user3751385
@ J0ANMM Otra forma de ocultar la imagen es agregar un atributo alt vacío: <img alt = "" src = "yourpicture.png">
Rick Glimmer
¡Excelente! En lugar de cambiar a una imagen, imprimo este texto onerror = "this.alt = 'Imagen no válida, use el enlace ^'"
sudoroso
6
/**
 * Tests image load.
 * @param {String} url
 * @returns {Promise}
 */
function testImageUrl(url) {
  return new Promise(function(resolve, reject) {
    var image = new Image();
    image.addEventListener('load', resolve);
    image.addEventListener('error', reject);
    image.src = url;
  });
}

return testImageUrl(imageUrl).then(function imageLoaded(e) {
  return imageUrl;
})
.catch(function imageFailed(e) {
  return defaultImageUrl;
});
holmberd
fuente
4

jQuery + CSS para img

Con jQuery esto me funciona:

$('img').error(function() {
    $(this).attr('src', '/no-img.png').addClass('no-img');
});

Y puedo usar esta imagen en cualquier lugar de mi sitio web sin importar su tamaño con la siguiente propiedad CSS3:

img.no-img {
    object-fit: cover;
    object-position: 50% 50%;
}

SUGERENCIA 1: use una imagen cuadrada de al menos 800 x 800 píxeles.

SUGERENCIA 2: para usar con retratos de personas , useobject-position: 20% 50%;

CSS solo para background-img

Para las imágenes de fondo que faltan, también agregué lo siguiente en cada background-imagedeclaración:

background-image: url('path-to-image.png'), url('no-img.png');

NOTA: no funciona para imágenes transparentes.

Lado del servidor Apache

Otra solución es detectar la imagen que falta con Apache antes de enviarla al navegador y reemplazarla por el contenido predeterminado no-img.png.

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} /images/.*\.(gif|jpg|jpeg|png)$
RewriteRule .* /images/no-img.png [L,R=307]
Meloman
fuente
3

Aquí hay una función que escribí para otra respuesta: Javascript Image Url Verify . No sé si es exactamente lo que necesita, pero utiliza las diversas técnicas que utilizaría que incluyen controladores para onload, onerror, onaborty un tiempo de espera general.

Debido a que la carga de imágenes es asincrónica, llama a esta función con su imagen y luego llama a su devolución de llamada en algún momento posterior con el resultado.

jfriend00
fuente
-19

como a continuación:

var img = new Image(); 
img.src = imgUrl; 

if (!img.complete) {

//has picture
}
else //not{ 

}
JamesChen
fuente
3
No Esto podría indicarle si la imagen está almacenada en caché o no, en algunos navegadores, pero sin una devolución de llamada de carga, no está esperando ni un momento para darle al navegador la oportunidad de iniciar una solicitud HTTP para esa imagen.
ChaseMoskal
¡esto es solo por decir algo!
Hitmands
4
La carga de imágenes es asincrónica.
emil.c
@ emil.c no siempre, por alguna razón. ChaseMoskal (a quien SO no me deja notificar ...) tiene razón: esto solo funciona si la imagen termina de cargarse antes de que continúe la ejecución de JS, lo que parece suceder siempre que se almacena en caché.
Tristan