用 JavaScript 测量图片宽度(小心!)

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您的每个旅程阶段提供云产品。立即开始使用 价值 200 美元的免费积分!

假设您想在页面上找到一个 <img> 元素,并用 JavaScript 找出它的宽度。也许您需要根据宽度(或高度,或两者)做出一些选择。您绝对可以这样做。DOM 甚至会根据您的需要提供各种尺寸供您选择。不过,确实存在一个问题。

以下是图片

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

以下是我们在 JavaScript 中通过查找 DOM 中的第一个 <img> 元素来选择它。

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

我们只记录宽度。

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

问题警报!您可能会得到奇怪的不一致结果。

问题在于 JavaScript 运行的 时间。如果 JavaScript 在图片完全下载和渲染 之前 运行,结果将为 0。如果 JavaScript 在图片下载和渲染 之后 运行,结果将是正确的。在我们的例子中,我们使用的图片宽度为 640(像素)。

更令人困惑的是,即使您的代码没有考虑这个问题,有时您仍然会得到正确的结果。这是因为,可能是图片位于缓存中,并且非常快地被放入页面中,以至于在您的 JavaScript 代码运行时,它已经知道正确的 offsetWidth

这是一个竞争条件,这是一件坏事。

正确的方法

您至少可以确保图片加载完成后再测量它。图片在完成加载时会发出一个 load 事件,您可以使用回调函数来执行测量操作。

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

您可能还需要进行一些错误处理,因为图片也可能会发出 error 事件。

您很可能正在抽象这个概念,因此您需要监视任意数量的图片,这意味着您必须遍历所有图片,等等。此外,图片还可以是背景图片...

这里涉及的内容很多,我建议使用 David DeSandro 的 imagesLoaded 库。它没有依赖关系,体积很小,并且如果使用 jQuery,它可以与 jQuery 协同工作。

在这里,我们只是监视页面上所有图片的加载情况,并在所有图片加载完成后测试宽度。

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

为什么?

我最近遇到过一个这样的用例,即一个类似于灯箱的东西。我想确保,如果我以小于实际尺寸的方式渲染图片,用户可以单击图片以打开更大尺寸的图片。

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

  }
}

enlarge 类将整个 <figure> 元素变成一个全屏覆盖层,其中图片和 figcaption 居中。但它只会在有意义的情况下执行这些操作,而这需要图片宽度逻辑正确!