Detectar quando uma imagem falha ao carregar em Javascript

104

Existe uma maneira de determinar se um caminho de imagem leva a uma imagem real, ou seja, detectar quando uma imagem falha ao carregar em Javascript.

Para um aplicativo da web, estou analisando um arquivo xml e criando imagens HTML dinamicamente a partir de uma lista de caminhos de imagem. Alguns caminhos de imagem podem não existir mais no servidor, portanto, quero falhar normalmente detectando quais imagens falham ao carregar e excluindo esse elemento HTML img.

Nota As soluções JQuery não poderão ser usadas (o chefe não quer usar JQuery, sim, eu sei, não me faça começar). Eu conheço uma maneira no JQuery de detectar quando uma imagem é carregada, mas não se ela falhou.

Meu código para criar elementos img, mas como posso detectar se o caminho img leva a uma falha ao carregar a imagem?

var imgObj = new Image();  // document.createElement("img");
imgObj.src = src;
Sazr
fonte
Isso pode ajudá-lo: stackoverflow.com/questions/1977871/… (é jQuery, mas ainda pode levar você no caminho certo)
Ben Lee
tente um destes google.com/…
ajax333221
2
Engraçado @ ajax333221 essa mesma pergunta é a primeira nos resultados do seu link :)
SSH Em
escreva um seletor JQuery para encontrar um novo Boss ... a internet é um lugar grande.
JJS de

Respostas:

115

Você pode tentar o seguinte código. Não posso garantir a compatibilidade do navegador, então você terá que testar isso.

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

E minhas condolências para o chefe resistente a jQuery!

Nikhil
fonte
2
Alguma ideia se há uma maneira de saber se ele encontra um redirecionamento?
quickshift em
4
Isso não funciona no webKit (Epiphany, Safari, ...) dependendo do servidor do qual você está obtendo a imagem. Por exemplo, às vezes o imgur não envia uma imagem que existe nem um status 404 e o evento de erro não é acionado neste caso.
O problema com isso é sua mensagem, "Essa imagem não foi encontrada." O código de resposta pode ser 500 ou 403 ou outra coisa. Você não sabe que era um 404. Na verdade, não é muito provável que seja um 404, pois provavelmente você não tentaria carregar uma imagem que não estivesse lá.
PHP Guru
1
deve adicionar o manipulador de eventos em vez de atribuição direta
cdalxndr
50

A resposta é boa, mas apresenta um problema. Sempre que você atribui onloadou onerrordiretamente, ele pode substituir o retorno de chamada atribuído anteriormente. É por isso que existe um bom método que "registra o ouvinte especificado no EventTarget em que é chamado", como dizem no MDN . Você pode registrar quantos ouvintes desejar no mesmo evento.

Deixe-me reescrever a resposta um pouco.

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

Como o processo de carregamento de recursos externos é assíncrono, seria ainda melhor usar o JavaScript moderno com promessas, como as seguintes.

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
fonte
2
talvez eu esteja faltando alguma coisa, mas como a atribuição (tester.onload = ..) pode substituir um retorno de chamada, se o "testador" acabou de ser criado? mesmo que alguma parte obscura do código adicione um evento a cada imagem criada, não é óbvio que queremos manter esses eventos com o propósito de detectar se uma imagem existe.
jvilhena de
1
Você está absolutamente correto. Neste caso específico, pode não ser um problema porque é altamente provável que não seja substituído. No entanto, em geral, adicionar um ouvinte de evento é uma prática melhor do que atribuir um método diretamente.
emil.c
1
As funções de @JohnWeisz Arrow são anônimas, portanto, são mais difíceis de depurar, use-as com cuidado.
emil.c
10
@GifCo Não me sinto confortável em continuar essa discussão com você. Me sinto ofendido com sua linguagem. Se você acredita que minha resposta está incorreta ou incompleta, sugira mudanças e explique seu raciocínio. Se você acha que algo é uma prática RUIM, sugira uma boa prática com sua própria resposta.
emil.c
2
Por que há uma discussão sobre as funções das setas aqui? Está fora do assunto. A resposta é boa, especialmente porque navegadores antigos não suportam funções de seta (Internet Explorer, por exemplo).
PHP Guru
29

Este:

<img onerror="this.src='/images/image.png'" src="...">
Rafael Martins
fonte
1
Embora esse seja um atributo útil que pode ser usado como uma técnica de failover de imagem, uma resposta mais detalhada seria útil para os leitores entenderem.
sorak de
1
Tão curto e tão útil! A fim de apenas escondê-lo:<img src="your/path/that/might/fail.png" onerror="$(this).hide();"/>
J0ANMM
2
O comentário de @sorak me fez rir de nojo. Essa é a resposta correta.
user3751385
@ J0ANMM Outra maneira de ocultar a imagem é adicionar um atributo alt vazio: <img alt = "" src = "yourpicture.png">
Rick Glimmer
Excelente! Em vez de fazer failover para uma imagem, imprimo este texto onerror = "this.alt = 'Imagem não válida, use o link ^'"
suado
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
fonte
4

jQuery + CSS para img

Com jQuery, isso está funcionando para mim:

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

E posso usar essa imagem em qualquer lugar do meu site, independentemente do tamanho dela, com a seguinte propriedade CSS3:

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

DICA 1: use uma imagem quadrada de pelo menos 800 x 800 pixels.

DICA 2: para uso com retrato de pessoas , useobject-position: 20% 50%;

CSS apenas para background-img

Para imagens de fundo ausentes, também adicionei o seguinte em cada background-imagedeclaração:

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

NOTA: não funciona para imagens transparentes.

Lado do servidor Apache

Outra solução é detectar a imagem ausente com o Apache antes de enviar ao navegador e substituí-la pelo conteúdo padrão no-img.png.

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

Aqui está uma função que escrevi para outra resposta: Javascript Image Url Verify . Eu não sei se é exatamente o que você precisa, mas ele usa as várias técnicas que você usaria que incluem manipuladores para onload, onerror, onaborte um tempo limite geral.

Como o carregamento da imagem é assíncrono, você chama essa função com sua imagem e, em seguida, ela chama seu retorno de chamada algum tempo depois com o resultado.

jfriend00
fonte
-19

assim como abaixo:

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

if (!img.complete) {

//has picture
}
else //not{ 

}
JamesChen
fonte
3
Não. Isso pode indicar se a imagem está ou não armazenada em cache, em alguns navegadores - mas sem um retorno de chamada de carga, você não está esperando um momento sequer para dar ao navegador a chance de iniciar uma solicitação HTTP para essa imagem.
ChaseMoskal
isso é apenas para dizer algo!
Hitmands
4
O carregamento da imagem é assíncrono.
emil.c
@ emil.c não é sempre, por algum motivo. ChaseMoskal (a quem o SO não me deixa notificar ...) está certo - isso funciona apenas se a imagem terminar de carregar antes que a execução do JS continue, o que parece acontecer sempre que é armazenado em cache.
tristan