jQuery / JavaScript, aby zastąpić uszkodzone obrazy


546

Mam stronę internetową zawierającą kilka zdjęć. Czasami obraz nie jest dostępny, więc uszkodzony obraz jest wyświetlany w przeglądarce klienta.

Jak użyć jQuery, aby uzyskać zestaw obrazów, przefiltrować go do uszkodzonych obrazów, a następnie zastąpić src?


- Pomyślałem, że łatwiej będzie to zrobić za pomocą jQuery, ale znacznie łatwiej było po prostu użyć czystego rozwiązania JavaScript, czyli tego dostarczonego przez Prestaul.

Odpowiedzi:


760

Obsługuj onErrorzdarzenie, aby obraz ponownie przypisał swoje źródło za pomocą JavaScript:

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

Lub bez funkcji JavaScript:

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

W poniższej tabeli zgodności wymieniono przeglądarki obsługujące funkcję błędu:

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


132
Prawdopodobnie chcesz wyczyścić onerror przed ustawieniem src. W przeciwnym razie, jeśli brakuje również noimage.gif, może dojść do „przepełnienia stosu”.
pcorcoran

45
Myślałem i miałem nadzieję, że odchodzimy od wbudowanych atrybutów dla zdarzeń javascript ...
redsquare

24
redsquare ... są doskonale zrozumiałe, gdy rozważnie kontrolujesz je, a nawet mogą być przydatne, gdy chcesz, aby znaczniki odzwierciedlały to, co się naprawdę wydarzy.
Nicole

2
Dobra uwaga, NickC, o znacznikach pokazujących, co się dzieje, po prostu kulę się, kiedy na to patrzę: P
SSH To

38
@redsquare, całkowicie się zgadzam, ale jest to wyjątkowy przypadek. Inline to jedyne miejsce, w którym można podłączyć słuchacza z absolutną pewnością, że zdarzenie nie zostanie uruchomione przed dołączeniem go. Jeśli zrobisz to na końcu dokumentu lub zaczekasz na załadowanie DOM, możesz przegapić zdarzenie błędu na niektórych obrazach. Nie ma sensu dołączać procedury obsługi błędów po uruchomieniu zdarzenia.
Prestaul

201

Korzystam z wbudowanego modułu errorobsługi:

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

Edit:error() metoda jest przestarzałe w jQuery 1.8 i wyższej. Zamiast tego należy użyć .on("error"):

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

112
Jeśli użyjesz tej techniki, możesz użyć metody „one”, aby uniknąć konieczności cofnięcia powiązania zdarzenia: $ ('img'). One ('error', function () {this.src = 'broken.gif';}) ;
Prestaul

7
+1 Ale czy musisz to rozwiązać? Działa pięknie na dynamicznych obrazach (np. Takich, które mogą się zmieniać po najechaniu myszą), jeśli nie cofniesz wiązania.
Aximili

15
Myślę, że jeśli go nie usuniesz, a referencja broken.gif src z jakiegoś powodu nigdy się nie załaduje, wtedy w przeglądarce mogą się zdarzyć złe rzeczy.
travis,

4
@travis, w którym momencie miałbyś zadzwonić? Czy jest jakiś sposób na użycie tej metody i upewnienie się, że twój program obsługi dołącza się PRZED uruchomieniem zdarzenia błędu?
Prestaul

5
Oto przykład przypadku, w którym błąd uruchamia się przed dołączeniem modułu obsługi zdarzeń: jsfiddle.net/zmpGz/1 Jestem naprawdę ciekawy, czy istnieje bezpieczny sposób na dołączenie tego
modułu

120

W przypadku, gdy ktoś taki jak ja próbuje dołączyć errorwydarzenie do dynamicznego HTMLimg znacznika , chciałbym zauważyć, że istnieje pewien haczyk:

Najwyraźniej imgzdarzenia błędów nie powodują bąbelkowania w większości przeglądarek, w przeciwieństwie do standardu mówi .

Tak więc coś takiego nie będzie działać :

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

Mam nadzieję, że będzie to pomocne komuś innemu. Chciałbym widzieć to tutaj w tym wątku. Ale nie zrobiłem tego. Dodam to


nie działało Dynamicznie ładuję martwy obraz i dołączam go do domeny i nie jest wywoływane żadne zdarzenie „błąd”.
vsync

59

Oto samodzielne rozwiązanie:

$(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';
    }
  });
});

5
Prawie poprawne ... poprawny obraz w IE nadal zwróci (typeof (this.naturalWidth) == "undefined") == true; - Zmieniłem instrukcję if na (! This.complete || (! $. Browser.msie && (typeof this.naturalWidth == "undefined" || this.naturalWidth == 0)))
Sugendran

3
ta reguła, ponieważ może wykryć uszkodzony obraz po załadowaniu strony. potrzebowałem tego.
północnoamerykański

@Sugendran $.browserjest już nieaktualny i usunięty, zamiast tego użyj wykrywania funkcji (w tym przypadku staje się bardziej skomplikowane).
Lee Penkman,

3
Samodzielny, ale wymaga jQuery?
Charlie,

Działa to świetnie, ale gdy src obrazu zwraca a 204 No Content, byłby to uszkodzony obraz.
Carson Reinke

35

Wierzę, że o to ci chodzi: jQuery.Preload

Oto przykładowy kod z wersji demo, określasz ładowanie i nie znaleziono obrazów i wszystko jest ustawione:

$('#images img').preload({
    placeholder:'placeholder.jpg',
    notFound:'notfound.jpg'
});

1
jQuery.Preload to wtyczka, która jest wymagana do użycia przykładowego kodu.
Dan Lord,

17
Tak ... jest to połączone w odpowiedzi w pierwszym wierszu.
Nick Craver

Dziękuję Nick, wydaje się, że jest to dobre rozwiązanie dla ponad 8-letniego problemu z Firefoksem, który nie pokazuje zastępowania uszkodzonych obrazów. Dodatkowo możesz oznakować to małe ... dzięki jeszcze raz.
klewis


19

Podczas gdy OP chciał zastąpić SRC, jestem pewien, że wiele osób, które zadają to pytanie, może chcieć tylko ukryć uszkodzony obraz, w którym to przypadku to proste rozwiązanie zadziałało dla mnie świetnie.

Za pomocą wbudowanego JavaScript:

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

Korzystanie z zewnętrznego JavaScript:

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' />

Korzystanie z nowoczesnego zewnętrznego JavaScript:

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

Zobacz obsługę przeglądarki NoteList.forEach i funkcje strzałek .


1
Uważam to za najbardziej niezawodne rozwiązanie dla bardzo podobnego problemu. Po prostu wolę nie pokazywać żadnego obrazu niż pokazać alternatywę dla uszkodzonego obrazu. Dzięki.
pc_

To oczywiście nie działa, gdy Polityka bezpieczeństwa treści nie zezwala na wbudowane skrypty.
isherwood,

@isherwood Dobry punkt. Dodałem rozwiązanie, które działa poprzez zewnętrzny plik JS.
Nathan Arthur

11

Oto szybki i brudny sposób na zastąpienie wszystkich uszkodzonych obrazów i nie ma potrzeby zmiany kodu HTML;)

przykład 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"),
            });
        }
  });

9

Nie mogłem znaleźć skryptu odpowiadającego moim potrzebom, dlatego uruchomiłem funkcję rekurencyjną, aby sprawdzić uszkodzone obrazy i próbować je ładować co cztery sekundy, aż zostaną naprawione.

Ograniczyłem go do 10 prób, jak gdyby do tego czasu nie został załadowany, obraz może nie być obecny na serwerze, a funkcja wejdzie w nieskończoną pętlę. Nadal jednak testuję. Zaprojektuj to :)

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

Niezły pomysł z próbą przeładowania zdjęć. Jest to przydatne w przypadku obrazów przesyłanych / generowanych automatycznie, a serwer mógł nie zostać ukończony / ukończony podczas ładowania strony. To trochę oryginalne formatowanie. Trochę zbyt niespójne i nie w moim guście jednak ...
Joakim

8

To kiepska technika, ale jest prawie w pełni gwarantowana:

<img ...  onerror="this.parentNode.removeChild(this);">

6

Możesz użyć własnego pobierania GitHub do tego:

Frontend: https://github.com/github/fetch
lub Backend, wersja Node.js: https://github.com/bitinn/node-fetch

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

Edycja: Ta metoda zastąpi XHR i podobno już była w Chrome. Każdy, kto to przeczyta w przyszłości, może nie potrzebować dołączonej wyżej biblioteki.


6

To jest JavaScript, powinien być kompatybilny z wieloma przeglądarkami i zapewnia bez brzydkich znaczników 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:


głosowanie zawhile (i--)
s3c

4

Lepsze połączenia przy użyciu

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

Ponieważ używanie document.readynie musi oznaczać, że obrazy są ładowane, tylko HTML. Dlatego nie ma potrzeby opóźnionego połączenia.


4

CoffeeScriptWariant :

Zrobiłem to, aby naprawić problem z Turbolinks, który powoduje, że metoda .error () czasami pojawia się w Firefoksie, nawet jeśli obraz naprawdę tam jest.

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

4

Korzystając z odpowiedzi Prestaul , dodałem kilka czeków i wolę używać 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;
}

4

Jeśli wstawiłeś swój za imgpomocą innerHTML, np .: $("div").innerHTML = <img src="wrong-uri">możesz załadować inny obraz, jeśli się nie powiedzie, np .:

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

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

Dlaczego jest javascript: _potrzebny Ponieważ skrypty wstrzykiwane do DOM za pomocą znaczników skryptu w innerHTMLnie są uruchamiane w momencie ich wstrzykiwania, więc musisz być jawny.


4

Znalazłem ten post, patrząc na ten inny post SO . Poniżej znajduje się kopia odpowiedzi, którą tam podałem.

Wiem, że to stary wątek, ale React stał się popularny i być może ktoś korzystający z React przyjdzie tutaj, szukając odpowiedzi na ten sam problem.

Tak więc, jeśli używasz React, możesz zrobić coś takiego jak poniżej, co było oryginalną odpowiedzią udzieloną przez Bena Alperta z zespołu React tutaj

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

4

Stworzyłem skrzypce, aby zastąpić uszkodzony obraz za pomocą zdarzenia „onerror”. To może ci pomóc.

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

4

Oto przykład użycia obiektu HTML5 Image zawiniętego przez JQuery. Wywołaj funkcję ładowania dla podstawowego adresu URL obrazu, a jeśli to obciążenie spowoduje błąd, zamień atrybut src obrazu na zapasowy adres URL.

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>

4

Pure JS. Moje zadanie brzmiało: jeśli obraz „bl-once.png” jest pusty -> wstaw pierwszy (który nie ma statusu 404) obraz z listy tablic (w bieżącym katalogu):

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

Może trzeba to poprawić, ale:

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

                }
            }
        }

    };

Tak więc wstawi poprawne „potrzebne.png” do mojego src lub „no-image.png” z bieżącego katalogu.


Użyłem na tej samej stronie. To tylko pomysł. Mój prawdziwy kod został ulepszony ...
Дмитрий Дорогонов

3

Nie jestem pewien, czy istnieje lepszy sposób, ale mogę wymyślić sposób na hack, aby go zdobyć - możesz Ajax zamieścić na adresie URL img i przeanalizować odpowiedź, aby zobaczyć, czy obraz rzeczywiście wrócił. Jeśli wrócił jako 404 lub coś, to zamień img. Chociaż oczekuję, że będzie to dość powolne.


3

Rozwiązałem problem z tymi dwiema prostymi funkcjami:

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

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

odniesienie


3

Myślę, że mają bardziej elegancki sposób, ze delegacja wydarzenie i wydarzenie na uchwycenie window„s error, nawet jeśli kopia zapasowa nie obciążenia.

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

Chodzi o to :

  1. Wprowadź kod headi jako pierwszy skrypt wbudowany. Będzie nasłuchiwać, że błędy występują po skrypcie.

  2. Użyj funkcji przechwytywania zdarzeń, aby wychwycić błędy, szczególnie w przypadku zdarzeń, które nie powodują bąbelkowania.

  3. Użyj delegowania zdarzeń, aby uniknąć wiązania zdarzeń na każdym obrazie.

  4. Nadaj imgelementowi błędu atrybut po nadaniu mu znaku, backup.pngaby uniknąć zniknięcia backup.pngnastępnej nieskończonej pętli, jak poniżej:

img error-> backup.png-> error-> backup.png-> error -> ,,,,,


To zdecydowanie najlepsza odpowiedź. Jedyne, co zasugerowałbym to użyć, let isImgErrorHandled = target.src === 'backup.png';ponieważ to trochę upraszcza.
Sabo

@ Sabo, istnieje mały problem, ponieważ ścieżka src może nie być równa ścieżce, którą przypisałem. Na przykład, target.src='backup.png'ale następnym razem console.log(target.src)może nie byćbackup.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] || ''; });
    });
};

Możesz przekazać ścieżkę zastępczą i uzyskać do niej dostęp do wszystkich właściwości z uszkodzonego obiektu obrazu poprzez $*:

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

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


2

Frustruje mnie to od lat. Moja poprawka CSS ustawia obraz tła na img. Gdy obraz dynamiczny srcnie ładuje się na pierwszym planie, symbol zastępczy jest widoczny na imgbg. To działa, jeśli obrazy mają domyślny rozmiar (na przykład height, min-height, widthi / lub min-width).

Zobaczysz ikonę zepsutego obrazu, ale jest to ulepszenie. Pomyślnie przetestowano do wersji IE9. iOS Safari i Chrome nawet nie wyświetlają uszkodzonej ikony.

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

Dodaj małą animację, aby dać srcczas na załadowanie bez migotania tła. Chrome płynnie gaśnie w tle, ale Safari na komputerze nie.

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

2

Jeśli obrazu nie można załadować (na przykład dlatego, że nie ma go pod dostarczonym adresem URL), adres URL obrazu zostanie zmieniony na domyślny,

Więcej informacji o .error ()

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


1

Mam ten sam problem. Ten kod działa dobrze w moim przypadku.

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

1

Czasami użycie errorzdarzenia jest niewykonalne, np. Ponieważ próbujesz zrobić coś na stronie, która jest już załadowana, na przykład gdy uruchamiasz kod za pomocą konsoli, skryptozakładki lub skryptu ładowanego asynchronicznie. W takim przypadku sprawdzenie tego img.naturalWidthiimg.naturalHeight są 0, wydaje się załatwić .

Na przykład, oto fragment, aby ponownie załadować wszystkie uszkodzone obrazy z konsoli:

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

0

Uważam, że działa to najlepiej, jeśli którykolwiek obraz nie ładuje się po raz pierwszy, jest on całkowicie usuwany z DOM. Wykonanie console.clear()utrzymuje okno konsoli w czystości, ponieważ błędów 404 nie można pominąć w blokach try / catch.

$('img').one('error', function(err) {
    // console.log(JSON.stringify(err, null, 4))
    $(this).remove()
    console.clear()
})
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.