Wywołanie zwrotne jQuery przy ładowaniu obrazu (nawet gdy obraz jest buforowany)

291

Chcę zrobić:

$("img").bind('load', function() {
  // do stuff
});

Ale zdarzenie ładowania nie jest uruchamiane, gdy obraz jest ładowany z pamięci podręcznej. Dokumenty jQuery sugerują wtyczkę, aby to naprawić, ale to nie działa

Tom Lehman
źródło
12
Od czasu twojego pytania sytuacja się zmieniła. Zepsuta wtyczka została przeniesiona do gist, a później do repozytorium z małą działającą wtyczką jQuery.imagesLoaded . Naprawiają wszystkie małe dziwactwa przeglądarki .
Lode
Wyżej wspomniana biblioteka JQuery działała dla mnie dobrze. Dzięki!
plang
Dziękujemy za wtyczkę, działała dobrze.
onimojo,

Odpowiedzi:

572

Jeśli parametr srcjest już ustawiony, zdarzenie jest uruchamiane w zbuforowanej skrzynce, zanim jeszcze zostanie powiązana procedura obsługi zdarzenia. Aby to naprawić, możesz przejść przez sprawdzanie i wyzwalanie zdarzenia na podstawie .complete:

$("img").one("load", function() {
  // do stuff
}).each(function() {
  if(this.complete) {
      $(this).load(); // For jQuery < 3.0 
      // $(this).trigger('load'); // For jQuery >= 3.0 
  }
});

Zwróć uwagę na zmianę z .bind()na, .one()aby moduł obsługi zdarzeń nie uruchamiał się dwukrotnie.

Nick Craver
źródło
8
Twoje rozwiązanie działało dla mnie idealnie, ale chcę coś zrozumieć, kiedy ten kod „if (this.complete)” zostanie uruchomiony, po załadowaniu zawartości obrazu, czy wcześniej? ponieważ jak rozumiem z tego .each, że zapętlasz wszystkie $ ("img") i być może zawartość obrazu jest pusta i ładowanie się nie wydarzy. hmmmm, myślę, że czegoś mi brakuje, byłoby miło, gdybyś mógł opisać, że to lepiej zrozumie. dzięki.
Amr Elgarhy
33
Od twojej odpowiedzi wszystko się zmieniło. Teraz jest mała działająca wtyczka jQuery.imagesLoaded . Naprawiają wszystkie małe dziwactwa przeglądarki .
Lode
3
Dodałbym w setTimeout(function(){//do stuff},0)ramach funkcji „load” ... 0 daje systemowi przeglądarki (DOM) jeden dodatkowy haczyk, aby upewnić się, że wszystko jest poprawnie aktualizowane.
dsdsdsdsd,
14
@Lode - to OGROMNA wtyczka, wcale nie mała w żadnej skali !!
vsync
5
ponieważ metoda JQuery 3 .load()nie wyzwala zdarzenia i ma 1 wymagany argument, użyj .trigger("load")zamiast tego
ForceUser
48

Czy mogę zasugerować ponowne załadowanie go do obiektu obrazu innego niż DOM? Jeśli jest buforowany, nie zajmie to wcale czasu, a obciążenie nadal będzie działać. Jeśli nie jest przechowywany w pamięci podręcznej, wyzwoli obciążenie po załadowaniu obrazu, co powinno być w tym samym czasie, gdy zakończy się ładowanie wersji DOM obrazu.

JavaScript:

$(document).ready(function() {
    var tmpImg = new Image() ;
    tmpImg.src = $('#img').attr('src') ;
    tmpImg.onload = function() {
        // Run onload code.
    } ;
}) ;

Zaktualizowano (do obsługi wielu obrazów i z poprawnie uporządkowanym załącznikiem onload):

$(document).ready(function() {
    var imageLoaded = function() {
        // Run onload code.
    }
    $('#img').each(function() {
        var tmpImg = new Image() ;
        tmpImg.onload = imageLoaded ;
        tmpImg.src = $(this).attr('src') ;
    }) ;
}) ;
Gus
źródło
1
Powinieneś spróbować dołączyć loadmoduł obsługi przed dodaniem, również to nie zadziała, ale dla jednego obrazu kod OP działa dla wielu :)
Nick Craver
Dzięki, dodałem zaktualizowaną wersję, która, mam nadzieję, rozwiązuje te dwa problemy.
Gus
#imgjest identyfikatorem, a nie selektorem elementów :) this.srcDziała również , nie trzeba używać jQuery tam, gdzie nie jest to potrzebne :) Ale tworzenie kolejnego obrazu wydaje się przesadą w obu przypadkach IMO.
Nick Craver
powinno to być $ („img”). ale rozwiązaniem jest także gr8 w 2k15
Dimag Kharab
Również w przypadku wielu obrazów, jeśli chcesz operować na elemencie DOM dla każdego obrazu imageLoaded, użyjtmp.onload = imageLoaded.bind(this)
blisstdev
38

Moje proste rozwiązanie, nie wymaga żadnej zewnętrznej wtyczki, a w typowych przypadkach powinno wystarczyć:

/**
 * Trigger a callback when the selected images are loaded:
 * @param {String} selector
 * @param {Function} callback
  */
var onImgLoad = function(selector, callback){
    $(selector).each(function(){
        if (this.complete || /*for IE 10-*/ $(this).height() > 0) {
            callback.apply(this);
        }
        else {
            $(this).on('load', function(){
                callback.apply(this);
            });
        }
    });
};

użyj tego w ten sposób:

onImgLoad('img', function(){
    // do stuff
});

na przykład, aby zanikać pod obciążeniem, możesz wykonać:

$('img').hide();
onImgLoad('img', function(){
    $(this).fadeIn(700);
});

Lub alternatywnie, jeśli wolisz podejście podobne do wtyczek jquery:

/**
 * Trigger a callback when 'this' image is loaded:
 * @param {Function} callback
 */
(function($){
    $.fn.imgLoad = function(callback) {
        return this.each(function() {
            if (callback) {
                if (this.complete || /*for IE 10-*/ $(this).height() > 0) {
                    callback.apply(this);
                }
                else {
                    $(this).on('load', function(){
                        callback.apply(this);
                    });
                }
            }
        });
    };
})(jQuery);

i użyj go w ten sposób:

$('img').imgLoad(function(){
    // do stuff
});

na przykład:

$('img').hide().imgLoad(function(){
    $(this).fadeIn(700);
});
guari
źródło
urm .. a co jeśli moje obrazy mają ustawiony rozmiar z css?
Simon_Weaver
Zestaw width, max-heighti odchodzenie height:auto, często jest to konieczne, aby ustawić wysokość i szerokość tylko wtedy, gdy trzeba rozciągnąć obraz; bardziej niezawodne rozwiązanie byłoby użyć wtyczki jak ImagesLoaded
Guari
1
tak, to był błąd w pisaniu, jsdoc był w porządku, poprawiłem przykład linii
guari
1
To rozwiązanie z $ .fn.imgLoad działa w różnych przeglądarkach. Naprawienie tego będzie jeszcze lepsze: && / * dla IE 10 - * / this.naturalHeight> 0 Ta linia nie bierze pod uwagę stylu css, ponieważ img może być blokowany, ale ma wysokość. Praca w IE10
Patryk Padus
1
Ma to (mały) stan wyścigu. Obraz może być ładowany do skryptu w innym wątku, a zatem może się ładować między sprawdzeniem zakończenia a dołączeniem zdarzenia onload, w którym to przypadku nic się nie stanie. Zwykle JS jest zsynchronizowany z renderowaniem, więc nie musisz się martwić warunkami wyścigu, ale jest to wyjątek. html.spec.whatwg.org/multipage/…
Mog0
23

Czy naprawdę musisz to zrobić z jQuery? Możesz również dołączyć onloadwydarzenie bezpośrednio do swojego obrazu;

<img src="/path/to/image.jpg" onload="doStuff(this);" />

Będzie uruchamiany za każdym razem, gdy obraz zostanie załadowany, z pamięci podręcznej lub nie.

Björn
źródło
5
Jest czystszy bez wbudowanego javascript
orzech laskowy
1
Cześć, Bjorn, czy program onloadobsługi uruchomi się, gdy obraz zostanie załadowany po raz drugi z pamięci podręcznej?
SexyBeast
1
@Cupidvogel nie, nie będzie.
Reklamy
3
Koleś, uratowałeś mi życie, próbowałem co najmniej tuzin innych rozwiązań, a twoje jest jedyne, które faktycznie działało. Poważnie myślę o utworzeniu 10 kolejnych kont, aby głosować na twoją odpowiedź jeszcze 10 razy. Poważnie dzięki!
Carlo,
1
Nigdy nie rozumiałem niechęci ludzi do wbudowanego JS. Ładowanie obrazu odbywa się osobno i poza kolejnością w stosunku do reszty procesu ładowania strony. Dlatego jest to jedyne prawidłowe rozwiązanie, aby uzyskać natychmiastową informację zwrotną na temat zakończenia ładowania obrazu. Jeśli jednak ktoś będzie musiał czekać na załadowanie jQuery, co nastąpi później, wówczas nie będzie musiał użyć prostego rozwiązania tutaj i zastosować jednego z pozostałych rozwiązań (Piotr jest prawdopodobnie najlepszą opcją, ponieważ nie jest zależny od pseudo- hacki, ale kontrola guari () może być również ważna). Przydałaby się również kolejka przetwarzania.
CubicleSoft,
16

Możesz także użyć tego kodu z obsługą błędu ładowania:

$("img").on('load', function() {
  // do stuff on success
})
.on('error', function() {
  // do stuff on smth wrong (error 404, etc.)
})
.each(function() {
    if(this.complete) {
      $(this).load();
    } else if(this.error) {
      $(this).error();
    }
});
Piotr Stępniewski
źródło
1
Ten działał dla mnie najlepiej. Myślę, że kluczem jest ustawienie modułu loadobsługi, a następnie wywołanie go później w eachfunkcji.
GreenRaccoon23
ustawienie obrazu za pomocą poniższego kodu 'var img = new Image (); img.src = sosImgURL; $ (img) .on ("load", function () {''
imdadhusen
13

Właśnie miałem ten problem, szukałem wszędzie rozwiązania, które nie wymagałoby zabicia mojej pamięci podręcznej ani pobrania wtyczki.

Nie zobaczyłem tego wątku od razu, więc zamiast tego znalazłem coś innego, co jest interesującą poprawką i (myślę) warte opublikowania tutaj:

$('.image').load(function(){
    // stuff
}).attr('src', 'new_src');

Właściwie ten pomysł pochodzi z komentarzy tutaj: http://www.witheringtree.com/2009/05/image-load-event-binding-with-ie-using-jquery/

Nie mam pojęcia, dlaczego to działa, ale przetestowałem to na IE7 i gdzie się zepsuł, zanim teraz działa.

Mam nadzieję, że to pomoże,

Edytować

Przyjęta odpowiedź faktycznie wyjaśnia, dlaczego:

Jeśli src jest już ustawiony, zdarzenie jest uruchamiane w pamięci podręcznej, zanim zostanie powiązana procedura obsługi zdarzenia.

Sammaye
źródło
4
Testowałem to w wielu przeglądarkach i nigdzie nie znalazłem. Kiedyś $image.load(function(){ something(); }).attr('src', $image.attr('src'));resetowałem oryginalne źródło.
Maurice
Przekonałem się, że to podejście nie działa na iOS, przynajmniej w Safari / 601.1 i Safari / 602.1
cronfy
Byłoby wspaniale dowiedzieć się, dlaczego, w jakiej przeglądarce jest?
Sammaye
5

Dzięki użyciu jQuery do wygenerowania nowego obrazu za pomocą src obrazu i przypisaniu bezpośrednio do niego metody load, metoda load jest z powodzeniem wywoływana, gdy jQuery zakończy generowanie nowego obrazu. Działa to dla mnie w IE 8, 9 i 10

$('<img />', {
    "src": $("#img").attr("src")
}).load(function(){
    // Do something
});
nacięcie
źródło
Dla tych, którzy używają Ruby on Rails, ze zdalnym żądaniem, używając szablonów __rjs.erb, jest to jedyne rozwiązanie, jakie znalazłem. Ponieważ użycie części w js.erb powoduje utratę wiązania ($ („# img” .on („load”) ...), a dla obrazów NIE jest możliwe delegowanie metody „on”: $ („body”). on („load”, „# img”, function () ...
Albert Català
5

Rozwiązanie znalazłem https://bugs.chromium.org/p/chromium/issues/detail?id=7731#c12 (ten kod pochodzi bezpośrednio z komentarza)

var photo = document.getElementById('image_id');
var img = new Image();
img.addEventListener('load', myFunction, false);
img.src = 'http://newimgsource.jpg';
photo.src = img.src;
AllisonC
źródło
Właśnie tego szukałem. Pojedynczy obraz przychodzi do mnie z serwera. Chciałem go wstępnie załadować, a następnie podać. Nie zaznaczanie wszystkich zdjęć, jak w przypadku wielu odpowiedzi. Dobry.
Kai Qing
4

Modyfikacja przykładu GUS:

$(document).ready(function() {
    var tmpImg = new Image() ;
    tmpImg.onload = function() {
        // Run onload code.
    } ;

tmpImg.src = $('#img').attr('src');
})

Ustaw źródło przed i po obciążeniu.

Chuck Conway
źródło
Podobnie jak odpowiedź @ Gusa, nie zadziała to w przypadku wielu obrazów ... i nie ma potrzeby ustawiania srcprzed dołączeniem programu onloadobsługi, raz wystarczy.
Nick Craver
3

Wystarczy ponownie dodać argument src w osobnym wierszu po zdefiniowaniu obiektu img. Spowoduje to, że IE uruchomi zdarzenie lad. To brzydkie, ale jest to najprostsze obejście, jakie do tej pory znalazłem.

jQuery('<img/>', {
    src: url,
    id: 'whatever'
})
.load(function() {
})
.appendTo('#someelement');
$('#whatever').attr('src', url); // trigger .load on IE
Bjørn T. Dahl
źródło
1

Mogę dać ci małą wskazówkę, jeśli chcesz to zrobić:

<div style="position:relative;width:100px;height:100px">
     <img src="loading.jpg" style='position:absolute;width:100px;height:100px;z-index:0'/>
     <img onLoad="$(this).fadeIn('normal').siblings('img').fadeOut('normal')" src="picture.jpg" style="display:none;position:absolute;width:100px;height:100px;z-index:1"/>
</div>

Jeśli zrobisz to, gdy przeglądarka buforuje zdjęcia, nie ma problemu, że zawsze wyświetla się img, ale ładuje obraz pod prawdziwym obrazem.

Josephy Besim
źródło
1

Miałem ten problem z IE, w którym e.target.width byłby niezdefiniowany. Zdarzenie load zadziałałoby, ale nie mogłem uzyskać wymiarów obrazu w IE (działało chrome + FF).

Okazuje się, że musisz poszukać e.currentTarget.naturalWidth i e.currentTarget.naturalHeight .

Po raz kolejny IE robi rzeczy po swojemu (bardziej skomplikowane).

Ali
źródło
0

Możesz rozwiązać problem za pomocą wtyczki JAIL, która pozwala również na leniwe ładowanie obrazów (poprawa wydajności strony) i przekazywanie wywołania zwrotnego jako parametru

$('img').asynchImageLoader({callback : function(){...}});

HTML powinien wyglądać

<img name="/global/images/sample1.jpg" src="/global/images/blank.gif" width="width" height="height" />
sebarmeli
źródło
0

Jeśli potrzebujesz czystego rozwiązania CSS, ta sztuczka działa bardzo dobrze - użyj obiektu transformacji. Działa to również z obrazami, gdy są one buforowane lub nie:

CSS:

.main_container{
    position: relative;
    width: 500px;
    height: 300px;
    background-color: #cccccc;
}

.center_horizontally{
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: green;
  left: 50%;
  top: 0;
  transform: translate(-50%,0);
}

.center_vertically{
  position: absolute;
  top: 50%;
  left: 0;
  width: 100px;
  height: 100px;
  background-color: blue;
  transform: translate(0,-50%);
}

.center{
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100px;
  height: 100px;
  background-color: red;
  transform: translate(-50%,-50%);
}

HTML:

<div class="main_container">
  <div class="center_horizontally"></div>
  <div class="center_vertically"></div>
  <div class="center"></div>
  </div>
</div

Przykład Codepen

Przykład Codepen MNIEJ

neoRiley
źródło
Czy możesz podać przykład, jak to działa z ładowaniem obrazu?
Hastig Zusammenstellen,