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:

PropertyReturnsWatch out for
naturalWidth / naturalHeightIntrinsic size of the image file, in CSS pixelsReturns 0 until the image loads
width / heightLayout size of the elementMatches natural size only when nothing constrains it
clientWidth / clientHeightContent box, including paddingInteger — rounds off subpixel layout
offsetWidth / offsetHeightContent box plus padding and borderBorder inflates the number
getBoundingClientRect()Actual painted box, subpixel precisionIncludes CSS transforms (a scaled image reports the scaled size)
currentSrcThe URL actually chosen from srcsetEmpty 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 width and height attributes 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: cover crop 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 Now

Working 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.

Bulk & API →