ScreenshotNeo

BlogHow-to

Lazy Load Images With CSS

CSS does not defer image downloads by itself. Use HTML loading="lazy" for noncritical images and CSS to control their layout.

By the ScreenshotNeo team1 October 20267 min read

Direct answer: CSS controls how an image is laid out and rendered, but it does not provide the native control that defers an ordinary <img> download. Put loading="lazy" on noncritical images in HTML, keep initially visible and LCP images eager, and use CSS for responsive sizing.

1. The complete HTML and CSS pattern

Use native lazy loading on images that are initially below the viewport. Always provide dimensions and useful alternative text.

<!-- gallery.html -->
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Lazy-loaded gallery</title>
  <style>
    .gallery {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
      gap: 1rem;
    }

    .gallery img {
      display: block;
      width: 100%;
      height: auto;
      aspect-ratio: 3 / 2;
      object-fit: cover;
    }
  </style>
</head>
<body>
  <main>
    <h1>Travel photos</h1>
    <div class="gallery">
      <img src="alps.jpg" width="1200" height="800"
           alt="Snow-covered Alps at sunrise"
           loading="eager" fetchpriority="high">
      <img src="coast.jpg" width="1200" height="800"
           alt="Rocky coast beside blue water" loading="lazy">
      <img src="forest.jpg" width="1200" height="800"
           alt="Sunlight through a pine forest" loading="lazy">
      <img src="desert.jpg" width="1200" height="800"
           alt="Sand dunes under a clear sky" loading="lazy">
    </div>
  </main>
</body>
</html>

loading="lazy" tells the browser to defer fetching until conditions chosen by that browser are met. The browser decides the distance from the viewport; your code should not promise an exact pixel threshold. eager is the default.

2. Decide which images should be lazy

Image situation Recommended setting Reason
Hero or LCP image visible on first load loading="eager"; consider fetchpriority="high" It is needed for the initial render. Lazy loading can delay the most important image.
Image below the initial viewport loading="lazy" Its request can wait until it is likely to be viewed.
Logo or small header icon Usually eager It is part of the initial interface.
Image that appears after a user action Lazy loading can help, but test the interaction The browser may still need time to fetch it after the action.

loading="eager" and fetchpriority="high" are separate hints. Eager loading does not itself raise network priority. Use high priority sparingly so it does not compete with CSS, fonts, or other critical resources. See web.dev’s browser-level image lazy-loading guidance.

3. Reserve space with width and height

Give every content image intrinsic dimensions, even when CSS makes it responsive:

<img src="article.jpg" loading="lazy" width="1600" height="900" alt="...">

img {
  max-width: 100%;
  height: auto;
}

The attributes establish an aspect ratio so the browser can reserve space before the file arrives. That reduces layout shifts and also avoids a failure mode where a lazy image has no established layout size and never intersects visible content as expected. The dimensions should describe the image’s real ratio, not merely the rendered width.

4. Responsive images and lazy loading

Combine loading="lazy" with srcset and sizes so the browser downloads an appropriate resource:

<img
  src="photo-1200.jpg"
  srcset="photo-480.jpg 480w, photo-800.jpg 800w, photo-1200.jpg 1200w"
  sizes="(max-width: 700px) 100vw, 50vw"
  width="1200"
  height="800"
  loading="lazy"
  alt="A cyclist on a mountain road"
>

Keep the fallback src valid for browsers that do not use the responsive candidates. Lazy loading changes when the request starts; it does not replace image compression, responsive sizing, or an appropriate format.

5. What CSS can and cannot do

CSS can make images fit their container:

img {
  display: block;
  max-width: 100%;
  height: auto;
}

.card-image {
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

CSS cannot add the native fetch-deferral behavior to a normal image. content-visibility: auto can allow the browser to skip layout and painting work for offscreen content, but it is a rendering optimization, not a documented replacement for loading="lazy":

.article-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;
}

Use it only when skipping offscreen rendering is useful and test its effects on layout, accessibility, and measurement.

6. CSS background images

A background-image is not an ordinary <img>, so this article’s native loading="lazy" pattern does not apply to it. The reviewed sources do not establish a guaranteed, equivalent CSS-only fetch-deferral mechanism for backgrounds. Do not claim that content-visibility reliably lazy-loads a background.

When the image conveys content, prefer an <img> with alt text. If it is purely decorative, keep the background and measure its network behavior in the browsers you support before relying on an optimization.

7. Important edge cases

  • Above-the-fold images: Do not lazy-load an image expected in the first viewport, particularly the LCP image.
  • Missing dimensions: Add width and height; responsive CSS does not remove the need for intrinsic dimensions.
  • Very short pages: A lazy image may be close enough to the viewport that the browser fetches it immediately. That is expected.
  • Print and alternate layouts: Test print styles and media-query changes; an image that is below the screen can become visible in another layout.
  • Accessibility: Lazy loading does not replace meaningful alt text. Decorative images should use alt="".
  • JavaScript image loaders: Do not replace native loading with a script unless you need behavior the native attribute cannot provide. Extra script adds failure modes.

8. Verify that it works

  1. Open browser developer tools and select the Network panel.
  2. Enable the Img resource filter and reload with the cache disabled.
  3. Confirm that initial viewport images request early and lower images request as you scroll near them.
  4. Use the Performance panel or Lighthouse to inspect LCP and layout shifts.
  5. Test a slow connection and a narrow viewport; browser thresholds and network timing vary.

Do not test only by looking for a request that starts exactly at the viewport edge. The browser is allowed to fetch before the image is visible.

9. Troubleshooting

Symptom Likely cause Fix
The hero image appears late It was marked lazy or competing with too many high-priority requests Remove loading="lazy" from the LCP image and use fetchpriority="high" only when appropriate.
Content jumps when images arrive No intrinsic dimensions or stable aspect ratio Add accurate width/height attributes and responsive CSS.
All images seem to load immediately They are close to the browser’s calculated preload distance, or the page is short Check a longer page and a throttled connection; do not depend on a fixed threshold.
An image never appears Invalid URL, blocked request, zero-size layout, or custom JavaScript replacing the source Inspect the console and Network panel, verify the URL and dimensions, and remove conflicting loader code.
Background images are still downloaded early CSS backgrounds do not use loading="lazy" Use a semantic <img> where suitable, or verify any background strategy in target browsers.

10. Performance, reliability, and cost notes

Lazy loading reduces unnecessary initial image transfer, which can matter on image-heavy pages. It does not reduce the eventual bytes for images a visitor scrolls to, and it does not replace compression, responsive images, caching, or a content delivery network. The browser chooses the fetch distance, so results differ by browser, connection, device, and page structure.

HTTP Archive data cited by web.dev reports that, at the 90th percentile, sites send more than 5 MB of images on desktop and mobile. In Chrome on Android experiments reported by the same article, 97.5% of lazy-loaded images finished within 10 ms of becoming visible on 4G and 92.6% did so on slow 2G. Those figures are observations from that article, not guarantees for every current site.

Native lazy loading has no API charge. Your costs come from image storage, transfer, processing, and any service that captures or transforms pages. Measure real page loads before setting a performance budget.

11. Or skip the browser setup

If you need rendered screenshots of pages that contain lazy images, ScreenshotNeo handles the browser capture through one request. Its full-page capture loads lazy images, and it can capture a selected element, set a viewport or device preset, apply custom CSS or JavaScript, wait for a selector, delay, or network idle, and return PNG, JPEG, WebP, or PDF.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and whether it was billed.
  • An MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf.
  • The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to try it with 1,000 screenshots a month and no card.

12. FAQ

Can CSS alone lazy-load an image?

No. Use the HTML loading="lazy" attribute for native image fetch deferral; use CSS for sizing and presentation.

Should every image use loading="lazy"?

No. Keep images needed for the initial viewport, especially the LCP image, eager.

Can I choose the exact distance before an image loads?

No. The browser calculates the threshold and may change it by browser, device, connection, and conditions.

Does content-visibility: auto lazy-load images?

It can skip offscreen layout and painting work, but it is not documented as an equivalent image-fetch control.

Do I still need image dimensions when using responsive CSS?

Yes. Accurate dimensions let the browser reserve space and reduce layout shift.