Intrinsic vs Rendered Image Size
Every image on a webpage has two sets of dimensions: the real pixel size of the file (intrinsic, also called natural) and the size it is actually drawn at on screen (rendered). If you landed here from Chrome DevTools, those are the two numbers in the tooltip — and the gap between them is usually wasted bandwidth.
Intrinsic size vs rendered size: the short answer
Intrinsic size is how big the image file really is. Rendered size is how big the browser drew it. A photo whose intrinsic size is 3000 × 2000 but whose rendered size is 300 × 200 downloaded 100 times more pixel data than the page displayed.
Chrome DevTools shows both when you hover an image in the Elements panel, which is where most people first meet the terms. "Intrinsic" and "natural" mean exactly the same thing — the HTML spec and the JavaScript property (naturalWidth) say natural, while DevTools and CSS say intrinsic.
Ideally rendered size × your device pixel ratio should roughly equal intrinsic size. Much larger and you are shipping pixels nobody sees; much smaller and the image looks soft.
Definitions
Intrinsic (Natural) Dimensions
DevTools and CSS say intrinsic; the HTML spec and JavaScript say natural. Same thing: the actual pixel width and height of the image file as stored on the server.
In JavaScript: img.naturalWidth and img.naturalHeight
Rendered Dimensions
Also called displayed or layout dimensions. The actual space the image occupies on the page, determined by CSS and HTML.
In JavaScript: getBoundingClientRect().width and .height
The JavaScript Properties, Compared
There are more ways to ask an image how big it is than most people expect, and they don't all answer the same question. This is the reference worth bookmarking:
| Property | Returns | Watch out for |
|---|---|---|
| naturalWidth / naturalHeight | Intrinsic size of the image file, in CSS pixels | Returns 0 until the image loads |
| width / height | Layout size of the element | Matches natural size only when nothing constrains it |
| clientWidth / clientHeight | Content box, including padding | Integer — rounds off subpixel layout |
| offsetWidth / offsetHeight | Content box plus padding and border | Border inflates the number |
| getBoundingClientRect() | Actual painted box, subpixel precision | Includes CSS transforms (a scaled image reports the scaled size) |
| currentSrc | The URL actually chosen from srcset | Empty until selection happens |
The pairing that answers "am I shipping too many pixels?" is naturalWidth versus getBoundingClientRect().width. The first is what you downloaded; the second is what the user actually sees.
Why naturalWidth Is 0 (the timing gotcha)
This trips up nearly everyone once. naturalWidth is only populated after the browser has read enough of the file to know its dimensions. Read it immediately after setting src and you get 0:
const img = new Image();
img.src = '/photo.jpg';
console.log(img.naturalWidth); // 0 — not loaded yet
// Correct: wait for load
img.onload = () => console.log(img.naturalWidth); // 3000
// Or with promises
await img.decode();
console.log(img.naturalWidth); // 3000
// Already in the DOM? Guard on .complete
if (img.complete && img.naturalWidth > 0) { /* safe to read */ }Note the naturalWidth > 0 check alongside complete: complete is also true when the image failed to load, so checking it alone will happily hand you a broken image's zeroes.
How to Get Image Dimensions From a URL
If all you have is a URL and you want its dimensions, you don't need to insert anything into the page — construct an Image in memory and let it load:
function getImageDimensions(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
img.onerror = () => reject(new Error('Could not load ' + url));
img.src = url;
});
}
const { width, height } = await getImageDimensions('https://example.com/photo.jpg');This works for cross-origin images: reading dimensions is only reading metadata, so no CORS headers are needed. You would only hit CORS restrictions if you tried to read the image's pixels — for example by drawing it to a canvas. If you just want the answer without writing code, paste the URL into our image dimension finder.
Why They Differ
Several factors cause natural and rendered dimensions to diverge:
- CSS sizing: Rules like
max-width: 100%,width: 50vw, or container constraints scale images down (or up). - HTML attributes: Explicit
widthandheightattributes override natural size. - Responsive layouts: Flexbox and grid containers resize images to fit available space.
- Device pixel ratio: On retina (2x) displays, a 200px CSS pixel container actually uses 400 device pixels.
- object-fit: Properties like
object-fit: covercrop the image, changing effective dimensions.
Why It Matters
Natural >> Rendered = Wasted bandwidth
A 3000px wide image rendered at 300px downloads ~100x more pixel data than needed. The browser still downloads the full file.
Natural < Rendered = Blurry images
When an image is stretched beyond its natural size, the browser interpolates pixels, creating visible blur and artifacts.
Natural = 2x Rendered = Ideal for retina
Serving images at 2x the rendered CSS pixels ensures crisp display on high-DPI screens without excessive file sizes.
How to Check
You can check both dimension sets several ways:
- Browser DevTools: Hover over an image element to see both intrinsic and rendered sizes in the tooltip.
- JavaScript console:
document.querySelectorAll('img').forEach(img => {
console.log(img.src);
console.log(` Natural: ${img.naturalWidth}×${img.naturalHeight}`);
const rect = img.getBoundingClientRect();
console.log(` Rendered: ${Math.round(rect.width)}×${Math.round(rect.height)}`);
});Or use our Image Dimension Finder to scan an entire page and see both dimensions for every image at once.
Compare natural vs rendered dimensions for every image on a page.
Scan a Page NowWorking Out the Right Size
The target natural width is the rendered CSS width multiplied by the device pixel ratio — that is the number of real device pixels the slot has to fill:
const rect = img.getBoundingClientRect();
const target = Math.round(rect.width * window.devicePixelRatio);
console.log('rendered:', Math.round(rect.width), 'css px');
console.log('target natural width:', target, 'px');
console.log('actual natural width:', img.naturalWidth, 'px');
console.log('wasted pixels:', img.naturalWidth - target);A practical rule: aim for a natural width within about 1–2× the rendered width. Well below that and the image looks soft; far above it and you are paying for pixels nobody will ever see. Because file size scales with area, an image twice as wide as it needs to be is roughly four times the bytes.
Frequently Asked Questions
What is the difference between intrinsic size and rendered size?
Intrinsic size is the real pixel width and height of the image file itself. Rendered size is how large the browser actually drew it on the page after CSS and layout. They match only when nothing constrains the image; the moment a stylesheet, a width attribute, or a flex or grid container resizes it, the two diverge. That gap is where wasted bandwidth hides.
Why does Chrome DevTools show a different intrinsic size and rendered size?
Because it is telling you the image was downloaded at one size and displayed at another. Hovering an image in the Elements panel shows both numbers, and a large difference means the file is bigger than the layout needs. For example a tooltip reading "Rendered size 258 × 43, Intrinsic size 300 × 50" is fine, but "Rendered 300 × 200, Intrinsic 3000 × 2000" means you shipped roughly 100 times the pixel data required.
Is intrinsic size the same as natural size?
Yes, they are two names for the same measurement. The HTML specification and the JavaScript properties call it natural, which is why the property is naturalWidth. Chrome DevTools and the CSS sizing specification call it intrinsic. If you have seen both terms and wondered whether you were missing a distinction, you were not.
What does naturalWidth return?
img.naturalWidth returns the intrinsic width of the image in CSS pixels — the width of the image file itself, independent of any CSS or HTML sizing applied to it. img.naturalHeight does the same for height. If the image has not loaded yet, both return 0, which is the single most common bug when reading them.
What is the difference between naturalWidth and width?
naturalWidth is the intrinsic size of the image file. width reflects the layout size the image occupies on the page. If nothing constrains the image, the two match. As soon as CSS, a width attribute, or a flex/grid container resizes the image, they diverge — and that gap is exactly what wastes bandwidth or causes blurring.
Why is naturalWidth 0?
Because the image has not finished loading. naturalWidth is only populated once the browser has parsed enough of the file to know its dimensions. Read it inside an onload handler, after awaiting img.decode(), or guard on img.complete before reading. Reading it synchronously right after setting img.src will almost always give you 0.
How do I get image dimensions from a URL in JavaScript?
Create an Image object, set its src to the URL, and read naturalWidth and naturalHeight once it loads: const img = new Image(); img.onload = () => console.log(img.naturalWidth, img.naturalHeight); img.src = url. This works cross-origin for dimensions because you are only reading metadata, not pixel data — no CORS headers are required unless you need to read the pixels via canvas.
What size should an image be for a retina display?
Roughly twice the rendered CSS width for a 2x display — multiply the rendered width by window.devicePixelRatio. An image displayed in a 400 CSS-pixel slot wants an 800-pixel-wide source on a 2x screen. Going beyond about 2x is rarely worth the extra bytes, since the visual difference at 3x is very hard to perceive on typical content.
Checking images at scale?
Bulk-audit hundreds of URLs at once or hit a dimensions API from your own code.