Onkel Schnitzel: jQuery: img.on('load') auf Funktion beschränken

Hallo Leute,

kein Tag ohne Frage :-) Ich habe mir mit JS eine kleine Slidefunktion für meine Bildergalerie gebaut. Per Klick auf den Vor- oder Zurück-Button rufe ich die Funktion slideBilde auf, mit der ich ein neues <img> erzeuge und lade. Nach Fertigladen füge ich das Bild hinter oder vor dem schon vorhandenen Bild ein und schiebe mit ihm das alte Bild weg. Danach entferne ich das alte Bild aus dem DOM. Und so weiter...

Hier mal die stark verkürzte Funktion, ich hoffe ich habe nichts Wichtiges weggelassen:

  
var slideBild = function () {  
  var img = $('<img/>');  
  img.attr('src','bild.jpg');  
  
  img.on('load', function() {  
    img.insertBefore('#bild');  
    $('#fenster > div').animate ( { left: 0} );  
  });  
};  

Nun habe ich noch eine andere Funktion, mir der ich per Index direkt zu einem bestimmten Bild springen kann. In dem Fall tausche ich nur das src-Attribut aus. In dieser Funktion gibt es keine Animation. Dennoch wird das neue Bild animiert. Und zwar wohl weil hier auch wieder das img.on(load) greift. Dasselbe passiert auch wenn ich die Seite einfach bloß riefresche, auch da schwuppt das Bild vor dem Neuaufbau nochmal weg.

Mich verwirrt dieses Verhalten etwas, weil ich dachte, dass img.on(load) gilt nur innerhalb der slideBild-Funktion. Aber offenbar greift das Mistvieh wohl global. Um das load-Event nur auf das eine gerade neu erstellte Bild zu beschränken, hab ich mal zusätzlich eine id="neu" vergeben und diese im on(load) als Selektor mit angegeben und das Event beim übergeordneten div registriert. Aber damit reagiert das Event nun überhaupt nicht mehr.

  
var slideBild = function () {  
  var img = $('<img/>');  
  img.attr('src','bild.jpg');  
  img.attr('id', 'neu');  
  
  $('div').on('load', '#neu', function() {  
    img.insertBefore('#bild');  
    $('#fenster > div').animate ( { left: 0},  
      function() {  
        img.removeAttr('id');  
      }  
    );  
  });  
};  

Was mach ichn schon wieder falsch?

Gruß

Onkel Schnitzel

  1. Hallo,

    Hier mal die stark verkürzte Funktion, ich hoffe ich habe nichts Wichtiges weggelassen:

    Ich vermute schon.

    Nun habe ich noch eine andere Funktion, mir der ich per Index direkt zu einem bestimmten Bild springen kann. In dem Fall tausche ich nur das src-Attribut aus. In dieser Funktion gibt es keine Animation. Dennoch wird das neue Bild animiert. Und zwar wohl weil hier auch wieder das img.on(load) greift.

    Das ist logisch.

    Wenn du ein frisches img-Element erstellst, das noch nicht im DOM hängt, und es beim load-Ereignis ins DOM einhängst, dann ist der Event-Handler immer noch aktiv.

    Wenn du später das img-Element wiederverwendest und die src änderst, passiert dort ein weiteres load-Ereignis und der Handler wird aufgerufen.

    Wenn du das nicht willst, musst du den Handler beim ersten load wieder entfernen. jQuery hat mit one eine Extra-Methode dafür. Diese entfernt den Handler nach dem ersten Ereignis des Types automatisch.

    Mich verwirrt dieses Verhalten etwas, weil ich dachte, dass img.on(load) gilt nur innerhalb der slideBild-Funktion.

    Event-Handling hat mit dem Scope von Funktionen nichts zu tun. img.on('load', function(){}) überwacht sämtliche load-Ereignisse beim angegebenen Element, solange das Element existiert.

    Aber offenbar greift das Mistvieh wohl global.

    Nein, das kann nicht sein.

    Um das load-Event nur auf das eine gerade neu erstellte Bild zu beschränken

    Event-Handler sind immer auf das Element beschränkt, bei dem sie registriert wurden.

    Es gibt natürlich die Bubbling- und Capturing-Phasen beim Event-Dispatching. Mit jQuerys img.on('load', function(){}) registrierst du Event-Handler für die Target- bzw. Bubbling-Phase.

    hab ich mal zusätzlich eine id="neu" vergeben und diese im on(load) als Selektor mit angegeben und das Event beim übergeordneten div registriert. Aber damit reagiert das Event nun überhaupt nicht mehr.

    Das ist klar. Load-Ereignisse steigen nicht auf (es gibt keine Bubbling-Phase).

    http://www.w3.org/TR/html5/embedded-content-0.html#img-load
    http://www.w3.org/TR/html5/webappapis.html#fire-a-simple-event

    var slideBild = function () {

    var img = $('<img/>');
      img.attr('src','bild.jpg');
      img.attr('id', 'neu');

    $('div').on('load', '#neu', function() {

      
    Das ergibt keinen Sinn, selbst wenn img in das div eingehängt wird. Das load-Ereignis wird das div nicht erreichen, da es nicht aufsteigt.  
      
    Event-Delegation für load-Ereignisse ist möglich, aber nur in der Capturing-Phase – also nicht mit diesem jQuery-Idiom.  
      
    Mathias
    
    -- 
    [Chaplin.js - JavaScript application architecture](http://chaplinjs.org/)
    
    1. Hallo molily,

      danke für deine schnelle Hilfe. Unfassbar, was du alles weißt.

      Wenn du das nicht willst, musst du den Handler beim ersten load wieder entfernen. jQuery hat mit one eine Extra-Methode dafür. Diese entfernt den Handler nach dem ersten Ereignis des Types automatisch.

      Was es nicht alles gibt. Das funktioniert tatsächlich.

      Event-Handling hat mit dem Scope von Funktionen nichts zu tun. img.on('load', function(){}) überwacht sämtliche load-Ereignisse beim angegebenen Element, solange das Element existiert.

      Aah, ok. Schwerer Denkfehler.

      Aber offenbar greift das Mistvieh wohl global.

      Nein, das kann nicht sein.

      War blöd ausgedrückt. Ich meinte sinngemäß das, was du auch geschrieben hast: "Wenn du später das img-Element wiederverwendest und die src änderst, passiert dort ein weiteres load-Ereignis und der Handler wird aufgerufen.

      Das ist klar. Load-Ereignisse steigen nicht auf (es gibt keine Bubbling-Phase).

      http://www.w3.org/TR/html5/embedded-content-0.html#img-load
      http://www.w3.org/TR/html5/webappapis.html#fire-a-simple-event

      Puuhh, da platzt mir der Kopf :-/ Ich glaub dir das einfach mal.

      Nochmal vielen Dank.

      Gruß

      Onkel Schnitzel

  2. Lieber Onkel Schnitzel,

    zwei Dinge sind mir aufgefallen:

    1.)

    img.insertBefore('#bild');

    Wenn insertBefore die original-DOM-Funktion eines Elementknotens ist (und keine jQuery-Verbiegung), dann benötigst Du noch einen zweiten Parameter bei diesem Aufruf. Die Methode <http://de.selfhtml.org/javascript/objekte/node.htm#insert_before@title=<node>.insertBefore> will nämlich einen _Kind-_konten einfügen. Mir scheint dass img daher das falsche Objekt ist, dessen insertBefore-Methode man hier benötigt, denn ein <img>-Element kann keine Kindknoten aufnehmen. Daher vielleicht besser so:

    $('#bild').insertBefore(img, $('#bild').firstChild)).

    Noch besser sicherlich so:

    var myParent = $('#bild');  
      
    myParent.insertBefore(img, myParent.firstChild)
    

    Die Reihenfolge der Parameter bestimmt dann die tatsächliche Position: Steht der vorhandene Kindknoten als zweiter Parameter, wird der einzufügende Knoten davor eingefügt, steht der Kindknoten als erster Parameter, wird der einzufügende Knoten dahinger eingefügt.

    2.)

    $('div').on('load', '#neu', function() {...}

    Wenn ich die jQuery-Syntax richtig verstehe (ich nutze jQuery nicht), dann willst Du wohl jedem <div>-Element Deines Dokuments einen onload-Eventhandler verpassen. Der wird niemals gefeuert! Das document-Objekt feuert den onload-Event, wenn alle nachzuladenden Bestandteile der Seite im Browser verfügbar sind. Meines Wissens gilt das nicht für Kindknoten - wobei man dem <body>-Element einen onload-Eventhandler zuweisen kann (per onload-Attribut), der in demselben Moment ausgelöst wird.

    Da Dein Dokument längst fertig geladen hat, wird niemals ein onload-Event irgendwelcher HTML-ELemente ausgelöst werden, es sei denn, da muss externer Inhalt nachgeladen werden (also alles, was ein src-Attribut haben kann). Damit muss Dein onload-Event an ein <img>-Element gebunden werden!

    Was jetzt Dein Problem auslöst, vermag ich nicht zu beurteilen, da ich nicht genau weiß, was jQuery alles mit den Members einer HTML-Node macht.

    Liebe Grüße,

    Felix Riesterer.

    --
    ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
    1. Hallo!

      Wenn insertBefore die original-DOM-Funktion eines Elementknotens ist

      Angesichts von
      var img = $('<img/>');
      ein paar Zeilen vorher trifft das höchstwahrscheinlich nicht zu.

      dann benötigst Du noch einen zweiten Parameter bei diesem Aufruf.

      Nicht bei jQuery, siehe insertBefore.

      Mir scheint dass img daher das falsche Objekt ist, dessen insertBefore-Methode man hier benötigt, denn ein <img>-Element kann keine Kindknoten aufnehmen.

      el1.insertBefore(el2) heißt in jQuery dort, dass el1 vor el2 eingefügt wird. Was auch sonst – das ist viel intuitiver.

      $('div').on('load', '#neu', function() {...}

      Wenn ich die jQuery-Syntax richtig verstehe (ich nutze jQuery nicht), dann willst Du wohl jedem <div>-Element Deines Dokuments einen onload-Eventhandler verpassen.

      Was prinzipiell möglich, wenngleich in diesem Fall nicht sinnvoll ist.

      Das document-Objekt feuert den onload-Event, wenn alle nachzuladenden Bestandteile der Seite im Browser verfügbar sind. (…) Da Dein Dokument längst fertig geladen hat, wird niemals ein onload-Event irgendwelcher HTML-ELemente ausgelöst werden

      Es ging hier meinem Verständnis nach um load-Events bei img-Elementen. Die existieren durchaus.

      Das load-Ereignis gibt es laut HTML5 bei den window- und document-Objekten sowie bei den Elementen link, style, img, iframe, object, track, input[type='image'], script und frame.

      Grüße
      Mathias