Ctrl + K
Images21 min read

How Lazy Loading Images Works

A practical guide to lazy loading images, covering loading=lazy, browser behavior, responsive images, LCP, layout stability, JavaScript lazy loading and common mistakes.

Published: 2026-10-05

Images can account for a large part of the data transferred by a web page. A page containing a long article, product catalog or image gallery may include dozens of images, even though only a few are visible when the page initially opens. Loading all of those images immediately can waste bandwidth and compete with resources that are more important for the first screen.

Lazy loading solves this problem by delaying the loading of images that are not immediately needed. Instead of requesting every image as soon as the document is processed, the browser can postpone offscreen images until they are closer to becoming visible.

Modern browsers provide native lazy loading through the loading attribute. For most ordinary content images, loading="lazy" is considerably simpler and more reliable than implementing a custom JavaScript intersection observer.

What Is Image Lazy Loading?

Image lazy loading is a technique that postpones downloading an image until it is likely to be needed. An image below the initial viewport does not necessarily need to be downloaded immediately because the user cannot see it yet.

For example, an article may contain a dozen illustrations. When the user opens the page, only the first illustration might be visible. Lazy loading allows the browser to prioritize the resources required for the initial view while delaying images further down the page.

<img
  src="/images/article-image.jpg"
  width="1200"
  height="800"
  loading="lazy"
  alt="Example illustration"
>

The loading="lazy" attribute is a browser hint that the image does not need to be fetched immediately. The browser decides when the resource should actually be requested based on factors such as its position relative to the viewport and current loading conditions.

Lazy Loading vs Eager Loading

Images are normally loaded eagerly unless another loading strategy is specified. Eager loading means that the browser considers the image an ordinary resource that can be requested without intentionally delaying it because it is offscreen.

<img
  src="/images/hero.jpg"
  width="1600"
  height="900"
  alt="Main page illustration"
>

Lazy loading changes that behavior for images that are not immediately necessary.

StrategyTypical behaviorBest suited for
EagerImage can be requested without intentional lazy delayImportant above-the-fold images
LazyBrowser can delay offscreen image loadingBelow-the-fold content

How loading="lazy" Works

Native lazy loading is implemented by the browser. When it encounters an image with loading="lazy", the browser can defer fetching the image if it is sufficiently far from the viewport. As the user approaches the image by scrolling, the browser can start the request before the image actually enters the visible area.

This means lazy loading is not simply equivalent to "load the image only when the first pixel becomes visible." Browsers generally need to fetch an image early enough that it has a chance to finish loading before the user reaches it.

The exact distance from the viewport at which a browser begins loading a lazy image is an implementation detail and can vary with browser behavior, connection conditions and other factors. Developers should therefore treat loading="lazy" as a loading hint rather than a precise scheduling API.

The Browser Still Decides When to Load It

One important detail is that loading="lazy" does not provide an exact instruction such as "wait until the image is 300 pixels from the viewport." The browser retains control over resource scheduling.

This is useful because the browser has information that application code may not have, including network conditions, device constraints, current resource priorities and the user's scrolling behavior.

💡 Think of loading="lazy" as a browser-controlled performance hint, not as a JavaScript callback that fires at a precisely defined distance from the viewport.

Why Lazy Loading Improves Performance

The main benefit of lazy loading is that unnecessary image requests are delayed. A page with many offscreen images can therefore reduce the amount of data competing for the network during the initial page load.

This can be particularly useful on slower connections. If the browser does not immediately download twenty large images below the fold, network capacity can be used for HTML, CSS, JavaScript, fonts and important images instead.

Lazy loading does not make an individual image smaller. It changes when the image is requested. Image compression, responsive images and modern formats address the amount of data required when the image is eventually loaded.

Lazy Loading Does Not Replace Image Optimization

A common misconception is that adding loading="lazy" automatically makes images lightweight. It does not.

If a lazy-loaded image is a 4000-pixel JPEG that is several megabytes in size, the browser may still download that entire file when the image approaches the viewport. Lazy loading only postpones the transfer.

A better image pipeline combines lazy loading with appropriate dimensions, compression, responsive image candidates and efficient formats.

Lazy Loading and Responsive Images

Responsive images and lazy loading solve complementary problems. Responsive images determine which image resource should be downloaded. Lazy loading determines when an offscreen image should be requested.

<img
  src="/images/photo-800.jpg"
  srcset="
    /images/photo-400.jpg 400w,
    /images/photo-800.jpg 800w,
    /images/photo-1200.jpg 1200w,
    /images/photo-1600.jpg 1600w
  "
  sizes="(max-width: 800px) 100vw, 800px"
  width="1600"
  height="1067"
  loading="lazy"
  alt="Mountain landscape"
>

Here, srcset and sizes help the browser choose an appropriate image candidate, while loading="lazy" allows the browser to defer the image when it is not immediately needed.

This combination is much more useful than lazy loading alone because the eventual image request can still be optimized for the device and layout.

Lazy Loading and Image Dimensions

Width and height attributes remain important when using lazy loading. The browser needs to know how much space the image is expected to occupy, even if its actual resource has not been downloaded yet.

<img
  src="/images/product.jpg"
  width="1200"
  height="900"
  loading="lazy"
  alt="Product photograph"
>

The dimensions establish the image's intrinsic aspect ratio. CSS can still make the image responsive while the browser uses the known ratio to reserve appropriate layout space.

Lazy Loading and Layout Shifts

Images without known dimensions can cause layout movement when they load. The browser may initially have little information about how much vertical space the image will occupy. Once the image arrives, surrounding content can move.

This behavior can contribute to Cumulative Layout Shift, commonly known as CLS. Providing intrinsic image dimensions or otherwise reserving the correct aspect ratio allows the browser to allocate space before the resource is available.

.article-image {
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  display: block;
}

For responsive layouts, the CSS aspect-ratio property can also help establish a predictable box. The exact implementation depends on whether the intrinsic dimensions are already known and how the image is positioned within its container.

Should Above-the-Fold Images Be Lazy Loaded?

Usually, important above-the-fold images should not be lazy-loaded. If an image is immediately visible and contributes significantly to the initial page presentation, delaying it can make the page appear slower.

This is especially important for a prominent hero image that may become the Largest Contentful Paint element. LCP measures how quickly the largest relevant content element becomes visible, so unnecessarily delaying the main image can hurt the metric.

⚠️ Do not add loading="lazy" to every image automatically. A critical image near the top of the page may need to load immediately, and lazy-loading it can delay the most important visual content.

What Is LCP?

Largest Contentful Paint, or LCP, is a Core Web Vital that measures the time until the largest relevant content element in the viewport becomes rendered. Depending on the page, that element can be a large image, video poster, text block or another prominent element.

When a hero image is the LCP element, its loading path deserves special attention. The image should be discoverable early, use an appropriately sized resource and should not be unnecessarily delayed by lazy loading.

Lazy Loading Below the Fold

Below-the-fold images are the classic use case for lazy loading. Consider an article containing several images separated by paragraphs. Only the first few may be relevant during the initial page view.

<article>
  <img
    src="/images/intro.jpg"
    width="1200"
    height="800"
    alt="Introduction illustration"
  >

  <p>Article content...</p>

  <img
    src="/images/example-1.jpg"
    width="1200"
    height="800"
    loading="lazy"
    alt="Example illustration"
  >

  <p>More article content...</p>

  <img
    src="/images/example-2.jpg"
    width="1200"
    height="800"
    loading="lazy"
    alt="Another example illustration"
  >
</article>

The first image is shown without lazy loading because it may be part of the initial viewport. The later images can be deferred because the user has to scroll before seeing them.

Lazy Loading in Image Galleries

Galleries are particularly good candidates for lazy loading because they can contain many images. A gallery with fifty thumbnails should not necessarily request all fifty images immediately.

Each thumbnail should still have an appropriate intrinsic size, responsive source candidates where useful and meaningful alternative text. Lazy loading should complement those optimizations rather than replace them.

Lazy Loading in Product Grids

E-commerce interfaces often display many product cards. If only the first row is visible initially, loading every product image at once can create unnecessary network activity.

<article class="product-card">
  <img
    src="/images/product-320.webp"
    srcset="
      /images/product-320.webp 320w,
      /images/product-640.webp 640w
    "
    sizes="(max-width: 700px) 50vw, 240px"
    width="640"
    height="640"
    loading="lazy"
    alt="Wireless headphones"
  >
</article>

The browser can defer products that are further down the page while also selecting a resource that better matches the card's actual display size.

Lazy Loading and CSS Background Images

CSS background images do not use the loading="lazy" attribute. Their loading behavior is controlled through CSS and browser resource scheduling rather than the native image loading attribute.

If a background is decorative, CSS is often appropriate. If the image represents meaningful content, an img element is usually preferable because it provides semantics, alt text and native image loading features.

.card {
  background-image: url("/images/card-background.jpg");
  background-size: cover;
  background-position: center;
}

For complex cases involving many background images or viewport-dependent loading, developers may use CSS strategies or JavaScript. However, this should be done only when native img loading behavior cannot satisfy the requirement.

Native Lazy Loading vs JavaScript

Before native lazy loading became broadly available, websites commonly implemented lazy loading with JavaScript and APIs such as IntersectionObserver. The page initially stored the real image URL in a data attribute and JavaScript assigned it when the image approached the viewport.

<img
  src="/images/placeholder.jpg"
  data-src="/images/photo.jpg"
  width="1200"
  height="800"
  alt="Mountain landscape"
>
const images = document.querySelectorAll("img[data-src]");

const observer = new IntersectionObserver((entries, observer) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;

    const image = entry.target;
    image.src = image.dataset.src;

    observer.unobserve(image);
  }
});

for (const image of images) {
  observer.observe(image);
}

This approach provides more control, but it also introduces JavaScript execution, additional application logic and more opportunities for bugs. For ordinary content images, native loading="lazy" should usually be considered first.

When JavaScript Lazy Loading Can Still Be Useful

Custom JavaScript can make sense when loading behavior depends on application state or when a complex component needs more control than the native attribute provides. Examples include specialized galleries, virtualized interfaces and custom media loaders.

Even in these cases, the implementation should avoid delaying critical images unnecessarily. Custom logic should also account for users with JavaScript disabled or failed scripts where appropriate.

IntersectionObserver and Lazy Loading

IntersectionObserver allows JavaScript to observe when an element intersects a viewport or another root. It is useful for custom lazy-loading systems because the application can react when an image becomes sufficiently close to being visible.

const observer = new IntersectionObserver(
  (entries) => {
    for (const entry of entries) {
      if (!entry.isIntersecting) continue;

      console.log("Image is approaching the viewport");
      observer.unobserve(entry.target);
    }
  },
  {
    rootMargin: "300px 0px",
  }
);

The rootMargin in a custom implementation can effectively create a preload distance, allowing the application to start work before the element actually becomes visible. Native lazy loading does not expose an equivalent standardized distance setting.

Why Custom Lazy Loading Can Go Wrong

A poorly implemented JavaScript loader can delay images too aggressively. If the request starts only after an image becomes visible, the user may see a blank area while the resource downloads.

Another problem is that custom loaders can interfere with responsive image behavior. A solution that assigns only image.src may accidentally ignore srcset, sizes or framework-level image optimization.

Custom lazy loading also needs to handle errors, browser compatibility, accessibility and server-rendered markup correctly. Native loading avoids much of this application complexity.

Lazy Loading and JavaScript Frameworks

React and other frontend frameworks do not change the underlying browser behavior. The loading attribute can be passed to ordinary img elements just like other HTML attributes.

export function ArticleImage() {
  return (
    <img
      src="/images/article.jpg"
      width={1200}
      height={800}
      loading="lazy"
      alt="Article illustration"
    />
  );
}

The important part is deciding whether the particular image is actually a good candidate for lazy loading. Framework abstractions do not remove the need to classify critical and non-critical images.

Lazy Loading with Next.js

Next.js applications commonly use the next/image Image component. Images are optimized and loaded according to the component's configuration and browser behavior. For images that should be deferred, the component supports lazy loading by default in common cases, while priority behavior can be used for images that are important to the initial viewport.

import Image from "next/image";

export function ArticleImage() {
  return (
    <Image
      src="/images/article.jpg"
      alt="Article illustration"
      width={1200}
      height={800}
      sizes="(max-width: 768px) 100vw, 800px"
    />
  );
}

For a critical image, the configuration should be chosen according to the Next.js version and image-loading API being used. The important principle is the same: do not unnecessarily defer an image that is responsible for the initial visual content.

Lazy Loading and Image Formats

Lazy loading works independently of the image format. JPEG, PNG, WebP, AVIF and other supported formats can all be loaded lazily.

However, the format still affects the amount of data transferred when the image is eventually requested. Combining lazy loading with efficient formats can therefore reduce both initial and eventual bandwidth consumption.

Lazy Loading and Image Compression

Compression and lazy loading address different stages of the loading process. Compression reduces the size of an image resource. Lazy loading delays when that resource is requested.

For example, a compressed 200 KB image that is below the fold may still be worth lazy-loading because the 200 KB does not need to be transferred during the initial page load. Conversely, a 2 MB hero image may need to load immediately, but it should probably be optimized rather than simply made lazy.

Lazy Loading and SEO

Lazy loading itself is not an SEO shortcut. Search engines can process modern lazy-loaded images, but important content should still be represented in accessible and crawlable markup.

For meaningful images, use normal img elements with useful alt text where appropriate. Avoid hiding important content behind JavaScript that may fail or prevent crawlers and assistive technologies from discovering it.

For image search visibility, other factors such as image context, alt text, page relevance, image URLs and structured page content also matter. Lazy loading should primarily be treated as a performance technique.

Lazy Loading and Accessibility

Lazy loading does not remove the need for accessible image markup. An informative image should have appropriate alternative text, while decorative images can use an empty alt attribute.

<img
  src="/images/chart.jpg"
  width="1200"
  height="700"
  loading="lazy"
  alt="Monthly revenue increased from January through June"
>

The fact that an image is loaded later does not change its semantic role. Screen readers and other assistive technologies still need appropriate HTML attributes when the image represents meaningful information.

Lazy Loading and Responsive Breakpoints

Responsive breakpoints can affect which images should be loaded and how large they need to be. A desktop layout may display four product cards per row while a mobile layout displays two, changing the rendered image width.

The correct solution is usually to combine responsive image candidates with accurate sizes information rather than generating completely separate loading logic for every breakpoint.

A breakpoint calculator can help reason about where a layout changes, while responsive image and dimension tools can help determine the image sizes required at those layouts.

How to Decide Which Images to Lazy Load

A practical rule is to classify images by whether they are needed for the initial viewport. Images that are clearly below the fold are strong candidates for lazy loading. Images near the top should be evaluated more carefully because their position and importance can vary by viewport.

A hero image, logo or prominent article image may be critical. A product image several rows down a catalog is usually not. The distinction should be based on the actual page layout rather than a blanket rule applied to every img element.

Image typeTypical loading choiceReason
Hero / LCP imageEagerMay be required for the initial visual content
Logo in initial headerEagerVisible immediately
First article imageUsually eagerMay be part of the initial viewport
Below-the-fold article imageLazyNot immediately visible
Lower product-grid rowsLazyUsually outside the initial viewport
Gallery imagesOften lazyMany resources may exist below the fold
Decorative backgroundDepends on CSS usageNot controlled by loading attribute

Common Lazy Loading Mistakes

The most common mistake is adding loading="lazy" to every image. This may look like a simple performance optimization, but it can delay important images and make the initial page experience worse.

Another mistake is using lazy loading instead of image optimization. A huge image that loads later is still a huge image. Responsive dimensions, compression and efficient formats should be handled separately.

Missing width and height information is another common problem. Lazy-loaded images still need to occupy the correct amount of space to avoid layout shifts.

Custom JavaScript lazy loading is also frequently more complicated than necessary. If native loading="lazy" satisfies the requirement, adding an IntersectionObserver-based implementation can increase code complexity without providing a meaningful benefit.

Finally, developers sometimes start loading an image too late in custom implementations. Waiting until an image enters the viewport can produce visible loading delays. A browser-native implementation can use its own scheduling logic to avoid requiring developers to choose a universal preload distance.

How to Test Lazy Loaded Images

Browser developer tools make it possible to verify whether lazy loading is actually working. Open the Network panel, filter for image resources and reload the page. Images below the initial viewport should generally not all be requested immediately.

Then scroll down the page and watch the network activity. As lazy images approach the viewport, their requests should begin. The exact timing depends on browser behavior, so the important observation is whether unnecessary offscreen images are being deferred rather than whether every browser uses the same threshold.

Test on different viewport sizes as well. An image that is below the fold on desktop may become visible much earlier on a narrow mobile screen because the layout changes.

It is also useful to test with throttled network conditions. Slow connections make unnecessary requests easier to identify and show whether images begin loading early enough to avoid visible gaps during scrolling.

A Practical Image Loading Strategy

Start by identifying which images are visible during the initial viewport at common desktop and mobile sizes. Keep critical images immediately discoverable and avoid unnecessary loading delays for them.

Apply loading="lazy" to images that are genuinely below the fold or otherwise non-critical. Give those images accurate width and height information so the layout can reserve their space before they load.

Then optimize the resources themselves. Generate appropriate image dimensions, use responsive candidates where necessary, choose efficient formats and compress the files to a sensible quality level.

Finally, inspect the actual network behavior. Performance optimizations should be verified in the browser rather than assumed from the presence of a particular HTML attribute.

Frequently Asked Questions

What is lazy loading for images?

Lazy loading delays the loading of images that are not immediately needed, usually because they are outside or far from the initial viewport. The browser can request them later as the user approaches them.

How do I lazy load an image in HTML?

For ordinary images, add loading="lazy" to the img element. The browser then has permission to defer loading when the image is not immediately needed.

Should every image use loading="lazy"?

No. Important above-the-fold images, especially a prominent LCP image, may need to load immediately. Lazy loading is generally more appropriate for images below the fold or otherwise non-critical.

Does lazy loading reduce image file size?

No. Lazy loading changes when an image is requested, not how large the file is. Compression, responsive image variants and efficient formats reduce the amount of data transferred.

Does loading="lazy" improve LCP?

It can improve overall loading behavior by deferring non-critical images, but applying it to the LCP image can have the opposite effect. The main image responsible for LCP should not be unnecessarily delayed.

Do lazy-loaded images need width and height?

Yes, providing intrinsic dimensions is recommended when they are known. Width and height allow the browser to reserve the appropriate space and help prevent layout shifts when the image eventually loads.

Is JavaScript required for lazy loading images?

No. Modern browsers support native lazy loading for img elements through loading="lazy". JavaScript and IntersectionObserver are mainly useful when an application needs behavior that native loading does not provide.

Helpful Image Tools

A Responsive Image Size Calculator can help determine suitable image variants for different viewport sizes, while an Image Resize Calculator can calculate target dimensions for optimized assets. An Image Dimension Checker is useful for verifying the actual width and height of generated files. When image behavior depends on responsive layouts, a Responsive Breakpoint Calculator can help identify important viewport ranges, and an Image Aspect Ratio Calculator can help preserve the correct proportions across image variants.

Conclusion

Lazy loading is a relatively simple browser feature that can have a meaningful effect on pages containing many images. By allowing non-critical images to load later, it reduces unnecessary competition for network and browser resources during the initial page load.

The most important rule is not to lazy-load everything. Critical images that contribute to the initial viewport or LCP should normally remain immediately discoverable, while images below the fold are much better candidates for deferred loading.

For the best results, combine lazy loading with responsive image delivery, appropriate dimensions, aspect-ratio handling, efficient formats and compression. Native loading="lazy" should usually be the starting point, with custom JavaScript reserved for cases that genuinely require more control.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.