jQuery / JavaScript para substituir imagens quebradas

547

Eu tenho uma página da web que inclui um monte de imagens. Às vezes, a imagem não está disponível, portanto, uma imagem quebrada é exibida no navegador do cliente.

Como uso o jQuery para obter o conjunto de imagens, filtrá-lo para imagens quebradas e substituir o src?


--Eu pensei que seria mais fácil fazer isso com o jQuery, mas ficou muito mais fácil usar apenas uma solução JavaScript pura, ou seja, a fornecida pela Prestaul.

Dan Lord
fonte

Respostas:

760

Manipule o onErrorevento para a imagem reatribuir sua origem usando JavaScript:

function imgError(image) {
    image.onerror = "";
    image.src = "/images/noimage.gif";
    return true;
}
<img src="image.png" onerror="imgError(this);"/>

Ou sem uma função JavaScript:

<img src="image.png" onError="this.onerror=null;this.src='/images/noimage.gif';" />

A tabela de compatibilidade a seguir lista os navegadores que suportam o recurso de erro:

http://www.quirksmode.org/dom/events/error.html

Prestaul
fonte
132
Você provavelmente deseja limpar o erro antes de definir o src. Caso contrário, se noimage.gif também estiver ausente, você poderá acabar com um "estouro de pilha".
pcorcoran 18/09/08
45
Pensei & esperava que estavam se afastando da linha atributos para eventos javascript ...
redsquare
24
redsquare ... eles são perfeitamente compreensíveis quando você pensa em mantê-los sob controle e pode até ser útil quando você deseja que sua marcação reflita o que realmente vai acontecer.
Nicole
2
Bom ponto NickC, sobre a marcação mostrando o que está acontecendo, eu só tremo quando eu olhar para ele: P
SSH Esta
38
@redsquare, concordo plenamente, mas este é um caso único. Inline é o único lugar em que você pode anexar o ouvinte com certeza absoluta de que o evento não será acionado antes do seu ouvinte ser anexado. Se você fizer isso no final do documento ou esperar pelo DOM carregado, poderá perder o evento de erro em algumas imagens. Não adianta anexar um manipulador de erros depois que o evento foi disparado.
Prestaul
201

Eu uso o errormanipulador incorporado :

$("img").error(function () {
    $(this).unbind("error").attr("src", "broken.gif");
});

Edit: O error()método está obsoleto no jquery 1.8 e superior. Em vez disso, você deve usar .on("error"):

$("img").on("error", function () {
    $(this).attr("src", "broken.gif");
});
travis
fonte
112
Se você usar essa técnica, poderá usar o método "one" para evitar a necessidade de desvincular o evento: $ ('img'). One ('error', function () {this.src = 'broken.gif';}) ;
Prestaul
7
+1 Mas você precisa desatá-lo? Funciona lindamente em imagens dinâmicas (por exemplo, aquelas que podem ser alteradas ao passar o mouse) se você não desenrolar.
Axelili
15
Eu acho que, se você não desencaixá-lo, e a referência broken.gif src falhar ao carregar por qualquer motivo, poderão ocorrer coisas ruins no navegador.
Travis
4
@travis, em que momento você faria essa ligação? Existe alguma maneira de usar esse método e ter certeza de que seu manipulador está sendo anexado ANTES do evento de erro ser acionado?
Prestaul
5
Aqui está um exemplo de caso em que o erro é disparado antes que o manipulador de eventos seja anexado: jsfiddle.net/zmpGz/1 Estou realmente curioso para
saber
120

Caso alguém como eu tente anexar o errorevento a uma imgtag HTML dinâmica , gostaria de salientar que existe um problema:

Aparentemente, os imgeventos de erro não surgem na maioria dos navegadores, ao contrário do que o padrão diz.

Portanto, algo como o seguinte não funcionará :

$(document).on('error', 'img', function () { ... })

Espero que isso seja útil para outra pessoa. Eu gostaria de ter visto isso aqui neste tópico. Mas eu não fiz. Então, eu estou adicionando

mouad
fonte
não funcionou. Carrego uma imagem morta dinamicamente e a anexo ao dom, e nenhum evento de 'erro' é acionado.
vsync 14/06
59

Aqui está uma solução autônoma:

$(window).load(function() {
  $('img').each(function() {
    if (!this.complete || typeof this.naturalWidth == "undefined" || this.naturalWidth == 0) {
      // image was broken, replace with your new image
      this.src = 'http://www.tranism.com/weblog/images/broken_ipod.gif';
    }
  });
});
Devon
fonte
5
Quase correto ... uma imagem correta no IE ainda retornará (typeof (this.naturalWidth) == "undefined") == true; - Alterei a instrução if para (! This.complete || (! $. Browser.msie && (tipoof this.naturalWidth == "undefined" || this.naturalWidth == 0)))
Sugendran
3
isso regras porque ele pode detectar uma imagem quebrada após o carregamento da página. eu precisava disso.
Northamerican
O @Sugendran $.browserfoi descontinuado e removido agora, use a detecção de recursos (neste caso, fica mais complicado).
Lee penkman 03/12/2014
3
Autônomo, mas requer jQuery?
6776 Charlie
Isso funciona muito bem, mas quando o src da imagem retorna a 204 No Content, seria uma imagem quebrada.
Carson Reinke
35

Acredito que é isso que você procura : jQuery.Preload

Aqui está o código de exemplo da demonstração, você especifica o carregamento e as imagens não encontradas e está pronto:

$('#images img').preload({
    placeholder:'placeholder.jpg',
    notFound:'notfound.jpg'
});
Nick Craver
fonte
1
jQuery.Preload é um plug-in necessário para usar o código de exemplo.
Dan Lord
17
Sim ... isso está vinculado na resposta na primeira linha.
Nick Craver
Obrigado Nick, esta parece ser uma boa solução para o problema de mais de 8 anos do Firefox que não mostra uma substituição de imagem por imagens quebradas. Além disso, você pode marcar a pequena coisa ... obrigado novamente.
klewis
25
$(window).bind('load', function() {
$('img').each(function() {
    if((typeof this.naturalWidth != "undefined" &&
        this.naturalWidth == 0 ) 
        || this.readyState == 'uninitialized' ) {
        $(this).attr('src', 'missing.jpg');
    }
}); })

Fonte: http://www.developria.com/2009/03/jquery-quickie---broken-images.html

Mohamad
fonte
1
Corrigido o problema do onerror não disparando no firefox :) Obrigado.
ram
19

Enquanto o OP procurava substituir o SRC, tenho certeza de que muitas pessoas que responderam a essa pergunta podem apenas querer ocultar a imagem corrompida. Nesse caso, essa solução simples funcionou muito bem para mim.

Usando JavaScript embutido:

<img src="img.jpg" onerror="this.style.display='none';" />

Usando JavaScript Externo:

var images = document.querySelectorAll('img');

for (var i = 0; i < images.length; i++) {
  images[i].onerror = function() {
    this.style.display='none';
  }
}
<img src='img.jpg' />

Usando JavaScript externo moderno:

document.querySelectorAll('img').forEach((img) => {
  img.onerror = function() {
    this.style.display = 'none';
  }
});
<img src='img.jpg' />

Consulte o suporte do navegador para as funções NoteList.forEach e arrow .

Nathan Arthur
fonte
1
Eu encontrei isso como uma solução mais confiável para mim para um problema muito semelhante. Eu prefiro não mostrar nenhuma imagem do que mostrar uma alternativa de imagem quebrada. Obrigado.
PC_
Obviamente, isso não funciona onde a Política de Segurança de Conteúdo não permite scripts embutidos.
Isherwood #
@isherwood Bom ponto. Eu adicionei uma solução que funciona através de um arquivo JS externo.
Nathan Arthur
11

Aqui está uma maneira rápida e suja de substituir todas as imagens quebradas, e não há necessidade de alterar o código HTML;)

exemplo codepen

    $("img").each(function(){
        var img = $(this);
        var image = new Image();
        image.src = $(img).attr("src");
        var no_image = "https://dummyimage.com/100x100/7080b5/000000&text=No+image";
        if (image.naturalWidth == 0 || image.readyState == 'uninitialized'){
            $(img).unbind("error").attr("src", no_image).css({
                height: $(img).css("height"),
                width: $(img).css("width"),
            });
        }
  });
Ashfaq Ahmed
fonte
9

Como não consegui encontrar um script para atender às minhas necessidades, criei uma função recursiva para verificar imagens quebradas e tentar recarregá-las a cada quatro segundos até que elas sejam corrigidas.

Limitei-o a 10 tentativas, como se não estivesse carregado até então, a imagem pode não estar presente no servidor e a função entraria em um loop infinito. Ainda estou testando. Sinta-se livre para ajustá-lo :)

var retries = 0;
$.imgReload = function() {
    var loaded = 1;

    $("img").each(function() {
        if (!this.complete || typeof this.naturalWidth == "undefined" || this.naturalWidth == 0) {

            var src = $(this).attr("src");
            var date = new Date();
            $(this).attr("src", src + "?v=" + date.getTime()); //slightly change url to prevent loading from cache
            loaded =0;
        }
    });

    retries +=1;
    if (retries < 10) { // If after 10 retries error images are not fixed maybe because they
                        // are not present on server, the recursion will break the loop
        if (loaded == 0) {
            setTimeout('$.imgReload()',4000); // I think 4 seconds is enough to load a small image (<50k) from a slow server
        }
        // All images have been loaded
        else {
            // alert("images loaded");
        }
    }
    // If error images cannot be loaded  after 10 retries
    else {
        // alert("recursion exceeded");
    }
}

jQuery(document).ready(function() {
    setTimeout('$.imgReload()',5000);
});
Sombra
fonte
Boa ideia ao tentar recarregar imagens. Isso é útil para imagens carregadas / geradas automaticamente e o servidor pode não ter sido obtido / concluído enquanto a página é carregada. Isso é alguma formatação original. Um pouco demasiado inconsistente e não em meu gosto no entanto ...
Joakim
8

Esta é uma técnica de baixa qualidade, mas é praticamente garantida:

<img ...  onerror="this.parentNode.removeChild(this);">
Phil LaNasa
fonte
6

Você pode usar a própria busca do GitHub para isso:

Frontend: https://github.com/github/fetch
ou para Backend, uma versão do Node.js.: https://github.com/bitinn/node-fetch

fetch(url)
  .then(function(res) {
    if (res.status == '200') {
      return image;
    } else {
      return placeholder;
    }
  }

Edit: Este método vai substituir o XHR e supostamente já está no Chrome. Para quem ler isso no futuro, talvez você não precise da biblioteca mencionada acima.

BarryMode
fonte
6

Este é JavaScript, deve ser compatível com vários navegadores e é entregue sem a marcação feia onerror="":

var sPathToDefaultImg = 'http://cdn.sstatic.net/stackexchange/img/logos/so/so-icon.png',
    validateImage = function( domImg ) {
        oImg = new Image();
        oImg.onerror = function() {
            domImg.src = sPathToDefaultImg;
        };
        oImg.src = domImg.src;
    },
    aImg = document.getElementsByTagName( 'IMG' ),
    i = aImg.length;

while ( i-- ) {
    validateImage( aImg[i] );
}

CODEPEN:

Axel
fonte
while (i--)
voto positivo
4

Melhor chamada usando

jQuery(window).load(function(){
    $.imgReload();
});

Como o uso document.readynão é necessário implica que as imagens sejam carregadas, apenas o HTML. Portanto, não há necessidade de uma chamada atrasada.

Sombra
fonte
4

Variante CoffeeScript :

Fiz isso para corrigir um problema com o Turbolinks que faz com que o método .error () seja gerado no Firefox algumas vezes, mesmo que a imagem esteja realmente lá.

$("img").error ->
  e = $(@).get 0
  $(@).hide() if !$.browser.msie && (typeof this.naturalWidth == "undefined" || this.naturalWidth == 0)
Kevin
fonte
4

Usando a resposta do Prestaul , adicionei algumas verificações e prefiro usar a maneira jQuery.

<img src="image1.png" onerror="imgError(this,1);"/>
<img src="image2.png" onerror="imgError(this,2);"/>

function imgError(image, type) {
    if (typeof jQuery !== 'undefined') {
       var imgWidth=$(image).attr("width");
       var imgHeight=$(image).attr("height");

        // Type 1 puts a placeholder image
        // Type 2 hides img tag
        if (type == 1) {
            if (typeof imgWidth !== 'undefined' && typeof imgHeight !== 'undefined') {
                $(image).attr("src", "http://lorempixel.com/" + imgWidth + "/" + imgHeight + "/");
            } else {
               $(image).attr("src", "http://lorempixel.com/200/200/");
            }
        } else if (type == 2) {
            $(image).hide();
        }
    }
    return true;
}
trante
fonte
4

Se você inseriu o seu imgcom innerHTML, como:, $("div").innerHTML = <img src="wrong-uri">você pode carregar outra imagem se ela falhar, por exemplo:

<script>
    function imgError(img) {
        img.error="";
        img.src="valid-uri";
    }
</script>

<img src="wrong-uri" onerror="javascript:imgError(this)">

Por que é javascript: _necessário? Como os scripts injetados no DOM por meio de tags de script innerHTMLnão são executados no momento em que são injetados, você deve ser explícito.

horro
fonte
4

Eu encontrei este post enquanto olha para este outro post SO . Abaixo está uma cópia da resposta que dei lá.

Sei que esse é um tópico antigo, mas o React se tornou popular e, talvez, alguém usando o React venha aqui procurando uma resposta para o mesmo problema.

Portanto, se você estiver usando o React, poderá fazer algo como o abaixo, que foi uma resposta original fornecida por Ben Alpert da equipe do React aqui

getInitialState: function(event) {
    return {image: "http://example.com/primary_image.jpg"};
},
handleError: function(event) {
    this.setState({image: "http://example.com/failover_image.jpg"});
},
render: function() {
    return (
        <img onError={this.handleError} src={src} />;
    );
}
Jordan Bonitatis
fonte
4

Eu criei um violino para substituir a imagem quebrada usando o evento "onerror". Isso pode ajudá-lo.

    //the placeholder image url
    var defaultUrl = "url('https://sadasd/image02.png')";

    $('div').each(function(index, item) {
      var currentUrl = $(item).css("background-image").replace(/^url\(['"](.+)['"]\)/, '$1');
      $('<img>', {
        src: currentUrl
      }).on("error", function(e) {
        $this = $(this);
        $this.css({
          "background-image": defaultUrl
        })
        e.target.remove()
      }.bind(this))
    })
Ninjaneer
fonte
4

Aqui está um exemplo usando o objeto Imagem HTML5 agrupado pelo JQuery. Chame a função load para o URL da imagem principal e, se esse carregamento causar um erro, substitua o atributo src da imagem por um URL de backup.

function loadImageUseBackupUrlOnError(imgId, primaryUrl, backupUrl) {
    var $img = $('#' + imgId);
    $(new Image()).load().error(function() {
        $img.attr('src', backupUrl);
    }).attr('src', primaryUrl)
}

<img id="myImage" src="primary-image-url"/>
<script>
    loadImageUseBackupUrlOnError('myImage','primary-image-url','backup-image-url');
</script>
Kurt Hartmann
fonte
4

JS puro. Minha tarefa era: se a imagem 'bl-once.png' estiver vazia -> insira a primeira imagem (que não possui status 404) da lista de matriz (no diretório atual):

<img src="http://localhost:63342/GetImage/bl-once.png" width="200" onerror="replaceEmptyImage.insertImg(this)">

Talvez seja necessário melhorar, mas:

var srcToInsertArr = ['empty1.png', 'empty2.png', 'needed.png', 'notActual.png']; // try to insert one by one img from this array
    var path;
    var imgNotFounded = true; // to mark when success

    var replaceEmptyImage = {
        insertImg: function (elem) {

            if (srcToInsertArr.length == 0) { // if there are no more src to try return
                return "no-image.png";
            }
            if(!/undefined/.test(elem.src)) { // remember path
                path = elem.src.split("/").slice(0, -1).join("/"); // "http://localhost:63342/GetImage"
            }
            var url = path + "/" + srcToInsertArr[0];

            srcToInsertArr.splice(0, 1); // tried 1 src

            
                if(imgNotFounded){ // while not success
                    replaceEmptyImage.getImg(url, path, elem); // CALL GET IMAGE
                }
            

        },
        getImg: function (src, path, elem) { // GET IMAGE

            if (src && path && elem) { // src = "http://localhost:63342/GetImage/needed.png"
                
                var pathArr = src.split("/"); // ["http:", "", "localhost:63342", "GetImage", "needed.png"]
                var name = pathArr[pathArr.length - 1]; // "needed.png"

                xhr = new XMLHttpRequest();
                xhr.open('GET', src, true);
                xhr.send();

                xhr.onreadystatechange = function () {

                    if (xhr.status == 200) {
                        elem.src = src; // insert correct src
                        imgNotFounded = false; // mark success
                    }
                    else {
                        console.log(name + " doesn't exist!");
                        elem.onerror();
                    }

                }
            }
        }

    };

Portanto, ele inserirá o arquivo 'needed.png' correto no meu src ou 'no-image.png' do diretório atual.

Дмитрий Дорогонов
fonte
Eu usei no mesmo site. Isso é apenas ideia. Meu verdadeiro código foi melhorado ...
Дмитрий Дорогонов
3

Não tenho certeza se existe uma maneira melhor, mas posso pensar em um hack para obtê-lo - você pode colocar o Ajax no URL img e analisar a resposta para ver se a imagem realmente voltou. Se ele voltou como um 404 ou algo assim, troque o img. Embora eu espere que isso seja bastante lento.

Chii
fonte
3

Eu resolvi meu problema com essas duas funções simples:

function imgExists(imgPath) {
    var http = jQuery.ajax({
                   type:"HEAD",
                   url: imgPath,
                   async: false
               });
    return http.status != 404;
}

function handleImageError() {
    var imgPath;

    $('img').each(function() {
        imgPath = $(this).attr('src');
        if (!imgExists(imgPath)) {
            $(this).attr('src', 'images/noimage.jpg');
        }
    });
}
Luis Gustavo Beligante
fonte
3

jQuery 1.8

// If missing.png is missing, it is replaced by replacement.png
$( "img" )
  .error(function() {
    $( this ).attr( "src", "replacement.png" );
  })
  .attr( "src", "missing.png" );

jQuery 3

// If missing.png is missing, it is replaced by replacement.png
$( "img" )
  .on("error", function() {
    $( this ).attr( "src", "replacement.png" );
  })
  .attr( "src", "missing.png" );

referência

Nabi KAZ
fonte
3

Eu acho que tenho uma maneira mais elegante com a delegação de eventos e a captura de eventos window, errormesmo quando a imagem de backup falha ao carregar.

img {
  width: 100px;
  height: 100px;
}
<script>
  window.addEventListener('error', windowErrorCb, {
    capture: true
  }, true)

  function windowErrorCb(event) {
    let target = event.target
    let isImg = target.tagName.toLowerCase() === 'img'
    if (isImg) {
      imgErrorCb()
      return
    }

    function imgErrorCb() {
      let isImgErrorHandled = target.hasAttribute('data-src-error')
      if (!isImgErrorHandled) {
        target.setAttribute('data-src-error', 'handled')
        target.src = 'backup.png'
      } else {
        //anything you want to do
        console.log(target.alt, 'both origin and backup image fail to load!');
      }
    }
  }
</script>
<img id="img" src="error1.png" alt="error1">
<img id="img" src="error2.png" alt="error2">
<img id="img" src="https://i.stack.imgur.com/ZXCE2.jpg" alt="avatar">

O ponto é:

  1. Coloque o código no heade executado como o primeiro script embutido. Portanto, ele escutará os erros após o script.

  2. Use a captura de eventos para capturar os erros, especialmente para os eventos que não borbulham.

  3. Use a delegação de eventos que evita eventos vinculativos em cada imagem.

  4. Atribua imgum atributo ao elemento error após atribuí-lo a backup.pngpara evitar o desaparecimento do backup.pngloop infinito e subsequente, como abaixo:

img erro-> backup.png-> erro-> backup.png-> erro -> ,,,,,

xianshenglu
fonte
Esta é de longe a melhor resposta. A única coisa que eu sugeriria é usar let isImgErrorHandled = target.src === 'backup.png';, pois simplifica um pouco.
Sabo
@ Sabo, Há um pequeno problema porque o caminho src pode não ser igual ao caminho que eu atribuí. Por exemplo, target.src='backup.png', mas da próxima vez console.log(target.src)pode não serbackup.png
xianshenglu
2
;(window.jQuery || window.Zepto).fn.fallback = function (fallback) {
    return this.one('error', function () {
        var self = this;
        this.src = (fallback || 'http://lorempixel.com/$width/$height')
        .replace(/\$(\w+)/g, function (m, t) { return self[t] || ''; });
    });
};

Você pode passar um caminho de espaço reservado e acessar todas as propriedades do objeto de imagem com falha via $*:

$('img').fallback('http://dummyimage.com/$widthx$height&text=$src');

http://jsfiddle.net/ARTsinn/Cu4Zn/

yckart
fonte
2

Isso me frustra há anos. Minha correção CSS define uma imagem de plano de fundo no img. Quando uma imagem dinâmica srcnão é carregada em primeiro plano, um espaço reservado é visível na imgBG. Isso funciona se as imagens têm um tamanho padrão (por exemplo height, min-height, widthe / ou min-width).

Você verá o ícone de imagem quebrada, mas é uma melhoria. Testado no IE9 com sucesso. O iOS Safari e o Chrome nem mostram um ícone quebrado.

.dynamicContainer img {
  background: url('/images/placeholder.png');
  background-size: contain;
}

Adicione um pouco de animação para dar srctempo para carregar sem um piscar de fundo. O Chrome desvanece-se suavemente no fundo, mas o Safari da área de trabalho não.

.dynamicContainer img {
  background: url('/images/placeholder.png');
  background-size: contain;
  -webkit-animation: fadein 1s;
  animation: fadein 1s;                     
}

@-webkit-keyframes fadein {
  0%   { opacity: 0.0; }
  50%  { opacity: 0.5; }
  100% { opacity: 1.0; }
}

@keyframes fadein {
  0%   { opacity: 0.0; }
  50%  { opacity: 0.5; }
  100% { opacity: 1.0; }
}
Dylan Valade
fonte
2

Se a imagem não puder ser carregada (por exemplo, porque não está presente no URL fornecido), o URL da imagem será alterado para o padrão,

Para mais informações sobre .error ()

$('img').on('error', function (e) {
  $(this).attr('src', 'broken.png');
});

Casper
fonte
1

Eu tenho o mesmo problema. Este código funciona bem no meu caso.

// Replace broken images by a default img
$('img').each(function(){
    if($(this).attr('src') === ''){
      this.src = '/default_feature_image.png';
    }
});
Dale Nguyen
fonte
1

Às vezes, o uso do errorevento não é viável, por exemplo, porque você está tentando fazer algo em uma página que já está carregada, como quando você está executando um código pelo console, um bookmarklet ou um script carregado de forma assíncrona. Nesse caso, verificar que img.naturalWidthe img.naturalHeightsão 0 parece fazer o truque.

Por exemplo, aqui está um trecho para recarregar todas as imagens quebradas do console:

$$("img").forEach(img => {
  if (!img.naturalWidth && !img.naturalHeight) {
    img.src = img.src;
  }
}
Lea Verou
fonte
0

Eu achei que funcionava melhor, se alguma imagem falha ao carregar na primeira vez, ela foi completamente removida do DOM. A execução console.clear()mantém a janela do console limpa, pois os erros 404 não podem ser omitidos nos blocos try / catch.

$('img').one('error', function(err) {
    // console.log(JSON.stringify(err, null, 4))
    $(this).remove()
    console.clear()
})
decoder7283
fonte