Messung von Bildbreiten in JavaScript (Vorsicht!)

Avatar of Chris Coyier
Chris Coyier am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Nehmen wir an, Sie möchten ein <img>-Element auf der Seite finden und herausfinden, wie breit es in JavaScript ist. Vielleicht müssen Sie basierend auf dieser Breite (oder Höhe oder beidem) Entscheidungen treffen. Das können Sie definitiv tun. Das DOM bietet sogar eine Vielzahl von Abmessungen zur Auswahl, je nachdem, was Sie benötigen. Es gibt jedoch einen Haken.

Hier ist das Bild

<img src="image.jpg" alt="an image">

Und hier wählen wir es aus, indem wir das erste <img>-Element finden, das JavaScript im DOM finden kann

var img = document.querySelector("img");

Lassen Sie uns einfach die Breite protokollieren

// How wide the image is rendered at (which could be affected by styles)
console.log(img.width);

// How wide the image itself actually is
console.log(img.naturalWidth);

// There is also `offsetWidth` (width with border and padding) and `clientWidth` (width with just padding)
// Those aren't usually too useful with images

Problemwarnung! Sie werden damit wahrscheinlich seltsam inkonsistente Ergebnisse erzielen.

Das Problem liegt darin, *wann* das JavaScript ausgeführt wird. Wenn das JavaScript ausgeführt wird, *bevor* das Bild vollständig heruntergeladen und gerendert wurde, ist das Ergebnis 0. Wenn das JavaScript ausgeführt wird, nachdem das Bild heruntergeladen und gerendert wurde, ist das Ergebnis korrekt. In unserem Fall ist das Bild, mit dem wir arbeiten, 640 (Pixel breit).

Es ist zusätzlich verwirrend, weil Sie manchmal trotzdem das richtige Ergebnis erhalten, auch wenn Ihr Code dies nicht berücksichtigt. Das liegt wahrscheinlich daran, dass das Bild im Cache ist und so schnell auf die Seite geladen wird, dass zu dem Zeitpunkt, an dem Ihr JavaScript-Code ausgeführt wird, die richtige offsetWidth bekannt ist.

Das ist eine Race Condition, und das ist schlecht.

Der gute Weg

Das Mindeste, was Sie tun können, ist sicherzustellen, dass das Bild geladen wurde, bevor Sie es messen. Bilder lösen ein load-Ereignis aus, wenn sie fertig sind, und Sie könnten den Callback verwenden, um die Messungen dann durchzuführen.

img.addEventListener("load", function() {
  console.log("image has loaded");
});

Wahrscheinlich möchten Sie dort auch eine Fehlerbehandlung durchführen, da Bilder auch ein error-Ereignis auslösen können.

Es besteht eine gute Chance, dass Sie dieses Konzept abstrahieren, sodass Sie auf eine beliebige Anzahl von Bildern achten müssten, und dann müssten Sie über sie schleifen und all das. Außerdem können Bilder auch Hintergrundbilder sein...

Es gibt hier genug zu tun, dass ich vorschlagen würde, einfach die imagesLoaded-Bibliothek von David DeSandro zu verwenden. Sie hat keine Abhängigkeiten, ist ziemlich klein und funktioniert mit jQuery, wenn Sie das verwenden.

Hier warten wir einfach darauf, dass alle Bilder auf der gesamten Seite geladen sind, bevor wir die Breiten testen

imagesLoaded(document.body, function() {
  var img = document.querySelector("img");
  console.log(img.width);
  console.log(img.naturalWidth);
});

Warum?

Ein Anwendungsfall dafür, den ich kürzlich hatte, war etwas Ähnliches wie ein Lightbox. Ich wollte sicherstellen, dass, wenn ich ein Bild rendere, das kleiner ist als es tatsächlich war, der Benutzer die Möglichkeit hatte, darauf zu klicken, um es größer zu öffnen.

// Only do this on large screens
if (window.outerWidth > 800) {

  // Wait for all images to load inside the article
  $("article").imagesLoaded(function() {

    $("figure > img").each(function(el) {

        // Only do this is shown image is smaller than actual image
        if (this.naturalWidth > this.width) {

          $(this)
            .closest("figure")
            .addClass("can-be-enlarged")
            .end()
            // When the image is clicked, toggle a class to enlarge it
            .on("click", function(e) {
              e.stopPropagation();
              $(this)
                .closest("figure")
                .toggleClass("enlarge");
            })
        }

      });

    });

    // When the enlarged image is clicked again, remove the class, shrinking it back down
    $("body").on("click", "figure.can-be-enlarged.enlarge", function() {
      $(this).removeClass("enlarge");
    })

  }
}

Die Klasse enlarge macht die gesamte <figure> zu einem Vollbild-Overlay mit dem zentrierten Bild und der zentrierten figcaption darin. Aber all diese Dinge tat sie nur, wenn es Sinn machte, was das Bild mit korrekter Logik benötigte!