{
"$type": "site.standard.document",
"content": {
"$type": "site.standard.content.markdown",
"text": "import InlineCalloutCluster from '../../components/InlineCalloutCluster.astro';\nimport InlineCallout from '../../components/InlineCallout.astro';\nimport ObjectFitDemo1 from '../../components/demos/ObjectFitDemo1.astro';\nimport ObjectFitDemo2 from '../../components/demos/ObjectFitDemo2.astro';\nimport { Picture } from \"astro:assets\";\n\n<InlineCalloutCluster \n\ttagline=\"From Object-Fit to Zero CLS ยท 2-part series\"\n\tarticles={[\n\t\t{\n\t\t\ttitle: 'CSS Object-Fit Property',\n\t\t\tlink: '/css-object-fit',\n\t\t\tdescription: \"The property, the five values, and which one you'll actually use\",\n\t\t},\n\t\t{\n\t\t\ttitle: 'Mastering image ratios with object-fit',\n\t\t\tlink: '/mastering-image-ratios-object-fit',\n\t\t\tdescription: \"Aspect ratios, CLS prevention, and the full performance strategy\",\n\t\t}\n\t]}\n/>\n\n## The Silent Killer of Web Performance\n\nImages are patient. They'll wait until the last possible moment to load, and when they do, they'll take exactly as much space as their natural dimensions require - regardless of what's already on the page. For users, that means content jumping. For Core Web Vitals, it means a CLS hit. For you, it means a client bug ticket.\n\nTwo lines of CSS fix it. The demo below shows exactly what's happening and why.\n\n## The Problem\n\nThis is the problem we're trying to solve\twith Object-Fit. It's quite prevalent on older sites, or ones that haven't yet adopted effective responsive design practices. By refreshing the page, if it wasn't noticeable on page load, the example below - our 'bad behavior' demo - will highlight exactly the issue that Object-Fit solves. These images have none of these practices, nor size attributes which means that not only do you have a decreased Cumulative Layout Shift (CLS) score, but a poor user experience. The card content will appear first at the top of the row, until the image loads, where it will then shift down to its expected position. Pretty nasty, right? Couple this with a slow internet connection, and it can become a real nightmare, and fast.\n\n<InlineCallout\n\ttagline=\"New to Object-Fit?\"\n\ttitle=\"CSS Object-Fit Property\"\n\tdescription=\"This post picks up from the CSS Object-Fit Property starter guide - worth a read if you want the foundations before diving into CLS and aspect ratios.\"\n\tslug=\"/writing/css-object-fit\"\n/>\n\nImages without fixed dimensions cause content to jump as they load - a Cumulative Layout Shift (CLS) failure. Hit reload on each panel to watch it happen, then see how two lines of CSS prevent it entirely.\n\n<ObjectFitDemo1 />\n\n## How object-fit works\n\n`object-fit` controls the relationship between an image's natural dimensions and the space its container has reserved for it. Without it, the browser makes that decision for you, and almost always it will choose the wrong choice for cards, headers, and profile images. Overall, there's five values you can use, but the most typically used one would be `cover`.\n\n`cover` fills the container completely, cropping the image if the aspect ratios don't match. The image is never distorted or forced to fit...it just fits cleanly, whatever the container dimensions are.\n\nIt's even more beneficial when you use this alongsite its companion property, `object-position`. This property controls which part of the image stays visible when cover crops it. The default is `center`, which works for most images, but for a portrait photo where the subject's face is at the top, `object-position: top` prevents the crop from cutting them out. Otherwise...\n\n<div class=\"h-[300px] w-[768px] overflow-hidden relative\">\n <Picture\n class=\"h-full w-full object-cover object-center absolute\"\n src=\"/images/profile-image.png\"\n formats={[\"avif\", \"webp\"]}\n alt=\"Profile photo cropped at center - the top of the head is cut off\"\n loading=\"eager\"\n height={894}\n width={906}\n />\n</div>\n\nFantastic.\n\n## Every object-fit value, on the same image\n\nTo really drill down into it, here's a small demo that shows a few different image sources with different dimensions. Clicking any value will make it available to test how it behaves with portrait, landscape, and square content.\n\n<ObjectFitDemo2 />\n\n## Two properties, not one\n\n`object-fit` controls how an image fills its container. It doesn't control how big that container is before the image loads - and that's the gap that causes layout shift.\n\nWithout a fixed container size, the browser doesn't know how much vertical space to reserve for an image slot. It allocates nothing, renders the surrounding content, and then reshuffles everything when the image arrives. That's CLS - and object-fit alone can't prevent it.\nThe fix is aspect-ratio on the container:\n\n```css\n.card-img-wrap {\n aspect-ratio: 16 / 9;\n overflow: hidden;\n}\n\n.card-img-wrap img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center;\n}\n```\n\n`aspect-ratio` tells the browser exactly how much space you should reserve for the image for when it arrives, so when it does load it has a container waiting for it and nothing on the page moves.\n\nYou might have seen an old classic at use for this: the padding hack, where a percentage value for the padding-top property is set on a container with position: relative to fake a fixed ratio. It worked, but it was nasty, and a nightmare to explain to the next developer. `aspect-ratio` replaced it entirely and has been supported in all browsers since September 2021.\n\n## The performance upside\n\nOnce the container has a fixed ratio, you know its exact display dimensions at every breakpoint. That's not just useful for layout stability - it means you can serve precisely sized images. Using an aspect ratio of 16/9 within a container that's 600px wide, you know that the display size is 600x338px, so you can then use `srcset` to serve an image that works with those dimensions, rather than downloading a 1200px original and letting `object-fit: cover` deal with the crop in the browser.\n\nWithout using a fixed ratio, there's guesswork. Serving a large image and then hoping/praying that the browser crops it correctly, or at least to a somewhat passable degree.\n\n```html\n<div class=\"card-img-wrap\">\n <picture>\n <source\n srcset=\"image-600x338.jpg 600w, image-1200x675.jpg 1200w\"\n sizes=\"(max-width: 768px) 100vw, 600px\"\n />\n <img\n src=\"image-600x338.jpg\"\n alt=\"Descriptive alt text\"\n width=\"600\"\n height=\"338\"\n loading=\"lazy\"\n />\n </picture>\n</div>\n```\n\nThe code aboves has width and height attributes on the `<img>` element to help establish the aspect ratio as a hint to the browser before the CSS loads. The result is smaller file transfers, no layout shift, and consistent cropping across every card in the grid. That's what the CLS demo at the top of this post is showing. It's not just a visual fix, but a performance strategy as well.\n\n## Go fix your images\n\nIf you've got a card grid in production, open DevTools and run a Lighthouse audit. A CLS score above 0.1 on image-heavy pages is almost always this - unsized images loading into unsized containers. Two properties, five minutes, and you can wrap it up.\n\n`aspect-ratio` reserves the space. `object-fit: cover` fills it cleanly. Add `width`, `height`, and `loading=\"lazy\"` while you're there. That's the whole pattern. No need for a library, or build step, or a polyfill.",
"version": "1.0"
},
"description": "Stop the jumps: the two-line CSS fix that kills CLS for good",
"path": "/writing/mastering-image-ratios-object-fit",
"publishedAt": "2025-11-26T00:00:00.000Z",
"site": "https://dominickjay.com",
"tags": [
"css",
"performance"
],
"textContent": "import InlineCalloutCluster from '../../components/InlineCalloutCluster.astro';\nimport InlineCallout from '../../components/InlineCallout.astro';\nimport ObjectFitDemo1 from '../../components/demos/ObjectFitDemo1.astro';\nimport ObjectFitDemo2 from '../../components/demos/ObjectFitDemo2.astro';\nimport { Picture } from \"astro:assets\";\n\nThe Silent Killer of Web Performance\n\nImages are patient. They'll wait until the last possible moment to load, and when they do, they'll take exactly as much space as their natural dimensions require - regardless of what's already on the page. For users, that means content jumping. For Core Web Vitals, it means a CLS hit. For you, it means a client bug ticket.\n\nTwo lines of CSS fix it. The demo below shows exactly what's happening and why.\n\nThe Problem\n\nThis is the problem we're trying to solve\twith Object-Fit. It's quite prevalent on older sites, or ones that haven't yet adopted effective responsive design practices. By refreshing the page, if it wasn't noticeable on page load, the example below - our 'bad behavior' demo - will highlight exactly the issue that Object-Fit solves. These images have none of these practices, nor size attributes which means that not only do you have a decreased Cumulative Layout Shift (CLS) score, but a poor user experience. The card content will appear first at the top of the row, until the image loads, where it will then shift down to its expected position. Pretty nasty, right? Couple this with a slow internet connection, and it can become a real nightmare, and fast.\n\nImages without fixed dimensions cause content to jump as they load - a Cumulative Layout Shift (CLS) failure. Hit reload on each panel to watch it happen, then see how two lines of CSS prevent it entirely.\n\nHow object-fit works\n\n controls the relationship between an image's natural dimensions and the space its container has reserved for it. Without it, the browser makes that decision for you, and almost always it will choose the wrong choice for cards, headers, and profile images. Overall, there's five values you can use, but the most typically used one would be .\n\n fills the container completely, cropping the image if the aspect ratios don't match. The image is never distorted or forced to fit...it just fits cleanly, whatever the container dimensions are.\n\nIt's even more beneficial when you use this alongsite its companion property, . This property controls which part of the image stays visible when cover crops it. The default is , which works for most images, but for a portrait photo where the subject's face is at the top, prevents the crop from cutting them out. Otherwise...\n\n \n\nFantastic.\n\nEvery object-fit value, on the same image\n\nTo really drill down into it, here's a small demo that shows a few different image sources with different dimensions. Clicking any value will make it available to test how it behaves with portrait, landscape, and square content.\n\nTwo properties, not one\n\n controls how an image fills its container. It doesn't control how big that container is before the image loads - and that's the gap that causes layout shift.\n\nWithout a fixed container size, the browser doesn't know how much vertical space to reserve for an image slot. It allocates nothing, renders the surrounding content, and then reshuffles everything when the image arrives. That's CLS - and object-fit alone can't prevent it.\nThe fix is aspect-ratio on the container:\n\n tells the browser exactly how much space you should reserve for the image for when it arrives, so when it does load it has a container waiting for it and nothing on the page moves.\n\nYou might have seen an old classic at use for this: the padding hack, where a percentage value for the padding-top property is set on a container with position: relative to fake a fixed ratio. It worked, but it was nasty, and a nightmare to explain to the next developer. replaced it entirely and has been supported in all browsers since September 2021.\n\nThe performance upside\n\nOnce the container has a fixed ratio, you know its exact display dimensions at every breakpoint. That's not just useful for layout stability - it means you can serve precisely sized images. Using an aspect ratio of 16/9 within a container that's 600px wide, you know that the display size is 600x338px, so you can then use to serve an image that works with those dimensions, rather than downloading a 1200px original and letting deal with the crop in the browser.\n\nWithout using a fixed ratio, there's guesswork. Serving a large image and then hoping/praying that the browser crops it correctly, or at least to a somewhat passable degree.\n\nThe code aboves has width and height attributes on the element to help establish the aspect ratio as a hint to the browser before the CSS loads. The result is smaller file transfers, no layout shift, and consistent cropping across every card in the grid. That's what the CLS demo at the top of this post is showing. It's not just a visual fix, but a performance strategy as well.\n\nGo fix your images\n\nIf you've got a card grid in production, open DevTools and run a Lighthouse audit. A CLS score above 0.1 on image-heavy pages is almost always this - unsized images loading into unsized containers. Two properties, five minutes, and you can wrap it up.\n\n reserves the space. fills it cleanly. Add , , and while you're there. That's the whole pattern. No need for a library, or build step, or a polyfill.",
"title": "Mastering Image Ratios With object-fit"
}