{"id":252794,"date":"2017-03-15T06:56:37","date_gmt":"2017-03-15T13:56:37","guid":{"rendered":"http:\/\/css-tricks.com\/?p=252794"},"modified":"2017-03-15T12:33:10","modified_gmt":"2017-03-15T19:33:10","slug":"measuring-image-widths-javascript-carefully","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/measuring-image-widths-javascript-carefully\/","title":{"rendered":"Measuring Image Widths in JavaScript (Carefully!)"},"content":{"rendered":"

Let’s say you want to find an <img><\/code> on the page and find out how wide it is in JavaScript. Maybe you need to make some choices based on that width (or height, or both) You can definitely do that. The DOM will even give you a variety of dimensions to choose from depending on what you need. There is definitely a catch though.<\/p>\n

<\/p>\n

Here’s the image:<\/p>\n

<img src=\"image.jpg\" alt=\"an image\"><\/code><\/pre>\n

And here’s us selecting it, by finding the first <img><\/code> JavaScript can find in the DOM:<\/p>\n

var img = document.querySelector(\"img\");<\/code><\/pre>\n

Let’s just log the width:<\/p>\n

\/\/ How wide the image is rendered at (which could be affected by styles)\r\nconsole.log(img.width);\r\n\r\n\/\/ How wide the image itself actually is\r\nconsole.log(img.naturalWidth);\r\n\r\n\/\/ There is also `offsetWidth` (width with border and padding) and `clientWidth` (width with just padding)\r\n\/\/ Those aren't usually too useful with images<\/code><\/pre>\n

Problem alert!<\/strong> You’re probably going to get weirdly inconsistent results with that. <\/p>\n

The issue is rooted in when<\/em> the JavaScript runs. If the JavaScript runs before<\/em> that image has fully downloaded and rendered, the result will be 0<\/code>. If the JavaScript runs after the image has downloaded and rendered, the result will be correct. In our case, the image we’re working with is 640<\/code> (pixels wide).<\/p>\n

It’s extra confusing, because sometimes even if your code doesn’t account for this, you’ll still sometimes get the correct result. That’s because, probably, the image is in cache and it’s put onto the page so quickly that by the time your bit of JavaScript runs, it knows the proper offsetWidth<\/code>. <\/p>\n

This is a race condition, and that’s a bad thing.<\/p>\n

The Good Way<\/h3>\n

The minimum you can do is ensure the image has loaded before you measure it. Images emit a load<\/code> event when they are finished, and you could use the callback to do the measurements then.<\/p>\n

img.addEventListener(\"load\", function() {\r\n  console.log(\"image has loaded\");\r\n});<\/code><\/pre>\n

You’d probably want to do some error handling there too, as images can also emit an error<\/code> event.<\/p>\n

There is a good chance you’re abstracting this concept, so you’d be watching for any number of images, in which you’d have to loop over them and all that. Plus images can be background-images too… <\/p>\n

There is enough going on here that I’d suggest just using David DeSandro’s imagesLoaded library<\/a>. It has no dependencies, is pretty small, and works with jQuery if you’re using that.<\/p>\n

Here we’re just watching for all images on the entire page to be loaded before testing widths:<\/p>\n

imagesLoaded(document.body, function() {\r\n  var img = document.querySelector(\"img\");\r\n  console.log(img.width);\r\n  console.log(img.naturalWidth);\r\n});<\/code><\/pre>\n

Why?<\/h3>\n

One use case for this I had recently was a lightbox-like thing. I wanted to make sure that if I was rendering an image smaller than it actually was, the user had the ability to click on it to open it up bigger.<\/p>\n

\/\/ Only do this on large screens\r\nif (window.outerWidth > 800) {\r\n\r\n  \/\/ Wait for all images to load inside the article\r\n  $(\"article\").imagesLoaded(function() {\r\n\r\n    $(\"figure > img\").each(function(el) {\r\n\r\n        \/\/ Only do this is shown image is smaller than actual image\r\n        if (this.naturalWidth > this.width) {\r\n\r\n          $(this)\r\n            .closest(\"figure\")\r\n            .addClass(\"can-be-enlarged\")\r\n            .end()\r\n            \/\/ When the image is clicked, toggle a class to enlarge it\r\n            .on(\"click\", function(e) {\r\n              e.stopPropagation();\r\n              $(this)\r\n                .closest(\"figure\")\r\n                .toggleClass(\"enlarge\");\r\n            })\r\n        }\r\n\r\n      });\r\n\r\n    });\r\n\r\n    \/\/ When the enlarged image is clicked again, remove the class, shrinking it back down\r\n    $(\"body\").on(\"click\", \"figure.can-be-enlarged.enlarge\", function() {\r\n      $(this).removeClass(\"enlarge\");\r\n    })\r\n\r\n  }\r\n}<\/code><\/pre>\n

The enlarge<\/code> class make the entire <figure><\/code> into a full-screen overlay with the image and figcaption centered within it. But it only did all this stuff if it made sense to do it, which needed the image with logic to be correct!<\/p>\n","protected":false},"excerpt":{"rendered":"

Let’s say you want to find an <img \/> on the page and find out how wide it is in JavaScript. Maybe you need to make some choices based on that width (or height, or both) You can definitely do that. The DOM will even give you a variety of dimensions to choose from depending […]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4],"tags":[585,1184],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":305235,"url":"https:\/\/css-tricks.com\/using-intersectionobserver-to-check-if-page-scrolled-past-certain-point\/","url_meta":{"origin":252794,"position":0},"title":"Using IntersectionObserver to Check if Page Scrolled Past Certain Point","date":"February 21, 2019","format":false,"excerpt":"When a web page scrolls, that\u2019s a DOM event. I can find out how far a window has scrolled at any time with\u00a0window.scrollY. I can\u00a0listen\u00a0for that event and get that number: window.addEventListener(\"scroll\", () => { console.log(window.scrollY) }); Let\u2019s say I want to know if the user has scrolled down 100px\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":365510,"url":"https:\/\/css-tricks.com\/syntax-highlighting-prism-on-a-next-js-site\/","url_meta":{"origin":252794,"position":1},"title":"Syntax Highlighting (and More!) With Prism on a Static Site","date":"May 4, 2022","format":false,"excerpt":"So, you've decided to build a blog with Next.js. Like any dev blogger, you'd like to have code snippets in your posts that are formatted nicely with syntax highlighting. Perhaps you also want to display line numbers in the snippets, and maybe even have the ability to call out certain\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2022\/04\/prism-next-syntax-highlighting.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":317422,"url":"https:\/\/css-tricks.com\/a-lightweight-masonry-solution\/","url_meta":{"origin":252794,"position":2},"title":"A Lightweight Masonry Solution","date":"August 3, 2020","format":false,"excerpt":"Back in May, I learned about Firefox adding masonry to CSS grid. Masonry layouts are something I've been wanting to do on my own from scratch for a very long time, but have never known where to start. So, naturally, I checked the demo and then I had a lightbulb\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/05\/bricks-with-gaps.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":169239,"url":"https:\/\/css-tricks.com\/links-inline-svg-staying-target-events\/","url_meta":{"origin":252794,"position":3},"title":"Links with Inline SVG, Staying on Target with Events","date":"May 5, 2014","format":false,"excerpt":"It's pretty common to use SVG within an anchor link or otherwise \"click\/tappable thing\" on a web page. It's also increasingly common that the SVG is inline , because it's often nice having the SVG in the DOM since you can style it with CSS and script it with JS\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":169949,"url":"https:\/\/css-tricks.com\/jquery-coffeescript\/","url_meta":{"origin":252794,"position":4},"title":"jQuery with CoffeeScript","date":"May 13, 2014","format":false,"excerpt":"I don't always write CoffeeScript, but when I do, I'm probably using jQuery too; I always forget the syntax for stuff. So I'm going to write it all down here so I can reference it until I memorize it. Note that the compiled example doesn't include the automatic closure around\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":306022,"url":"https:\/\/css-tricks.com\/a-grid-of-logos-in-squares\/","url_meta":{"origin":252794,"position":5},"title":"A Grid of Logos in Squares","date":"April 6, 2020","format":false,"excerpt":"Let's build a literal grid of squares, and we'll put the logos of some magazines centered inside each square. I imagine plenty of you have had to build a logo grid before. You can probably already picture it: an area of a site that lists the donors, sponsors, or that\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/04\/grid-of-logos.png?fit=1200%2C902&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/252794"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=252794"}],"version-history":[{"count":4,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/252794\/revisions"}],"predecessor-version":[{"id":252823,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/252794\/revisions\/252823"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=252794"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=252794"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=252794"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}