假设您想在页面上找到一个 <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 居中。但它只会在有意义的情况下执行这些操作,而这需要图片宽度逻辑正确!
当我看到这个时,我想到了 JS 中的
getComputedStyle( )
和getBoundingClientRect( )
。资源
1. http://stackoverflow.com/questions/20412248/javascript-getting-the-size-of-an-image-using-getcomputedstyle
http://stackoverflow.com/questions/5834624/retrieve-width-height-of-a-css3-scaled-element
这些方法非常笨拙,因为它们在访问时会重新流式布局页面。
@Udi Talias
您的答案在两个方面都错了。
img.naturalWidth
返回图片的物理宽度(请注意:如果使用srcset
,它还会考虑srcset
描述符)。这与img.width
相比,img.width
返回图片的布局宽度,其工作方式类似于getComputedStyle
或getBoundingClientRect
或offsetWidth
。因此,它没有使用更不笨拙的代码,因为它已经使用了可能强制同步样式/布局的代码。它的作用只是将图片的内在尺寸与图片的布局尺寸进行比较,以确定是否可以在不损失质量的情况下放大图片。这些方法不会导致重新流式布局。更改 DOM 或 CSSOM 会导致重新流式布局。(
classList.add
、appendChild
等)。而这些方法实际上是性能瓶颈。getComputedStyle
、getBoundingClientRect
、offsetWidth
可以做的就是强制同步布局,如果在同一帧之前更改了 DOM/CSSOM。但通常情况下,这些方法速度非常快。您不希望做的是在一帧中强制执行多次同步布局,或者在强制执行布局后在同一帧中更改 DOM/CSSOM。因此,这不是关于不使用这些方法,而是更多地关于在正确的时间使用这些方法。最简单的方法是,您可以随时读取布局,但应将布局更改限制在
rAF
中。顺便说一下:如果您在页面上有不止一张图片,上面的代码会强制执行布局。
您可以通过使用这些方法而不是普通的 jQuery 方法轻松解决这个问题(
toggleClassRaf
而不是toggleClass
)。