Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To capture lazy-loaded images in a full-page Puppeteer screenshot, scroll through the page in viewport-sized steps so the page can load images as they approach the viewport, check that the images you need have loaded, then capture with fullPage: true. A navigation wait such as networkidle2 alone does not guarantee that off-screen images have loaded.

Capture the page after triggering lazy loading

Page.screenshot() is Puppeteer’s screenshot method. Set fullPage: true to request the full page; the documented default is false. [Puppeteer screenshots guide; ScreenshotOptions; Page.screenshot()]

The following Node.js example scrolls in viewport-sized increments, waits for images in or near each viewport to load or fail, and makes a second pass if loading images expanded the document. It then checks the page’s image elements before taking the screenshot. Install Puppeteer in your project with npm install puppeteer and save this as a JavaScript file:

const puppeteer = require('puppeteer');

async function waitForImagesInView(page, timeoutMs = 5000) {
  await page.evaluate(async (timeout) => {
    const images = [...document.images].filter((img) => {
      const rect = img.getBoundingClientRect();
      return rect.bottom >= 0 && rect.top <= window.innerHeight;
    });

    await Promise.all(images.map((img) => {
      if (img.complete) return Promise.resolve();

      return new Promise((resolve) => {
        const finish = () => {
          clearTimeout(timer);
          img.removeEventListener('load', finish);
          img.removeEventListener('error', finish);
          resolve();
        };
        const timer = setTimeout(finish, timeout);
        img.addEventListener('load', finish, { once: true });
        img.addEventListener('error', finish, { once: true });
      });
    }));
  }, timeoutMs);
}

async function scrollToLoadImages(page, maxPasses = 3) {
  const viewportHeight = page.viewport().height;

  for (let pass = 0; pass < maxPasses; pass++) {
    const heightBefore = await page.evaluate(() =>
      document.documentElement.scrollHeight
    );

    for (let y = 0; y < heightBefore; y += viewportHeight) {
      await page.evaluate((position) => window.scrollTo(0, position), y);
      await waitForImagesInView(page);
    }

    const heightAfter = await page.evaluate(() =>
      document.documentElement.scrollHeight
    );
    if (heightAfter <= heightBefore) break;
  }

  await page.evaluate(() => window.scrollTo(0, 0));
}

(async () => {
  const browser = await puppeteer.launch({ headless: true });

  try {
    const page = await browser.newPage({
      viewport: { width: 1365, height: 900 },
    });
    await page.goto('https://example.com', {
      waitUntil: 'networkidle2',
      timeout: 60000,
    });

    await scrollToLoadImages(page);

    const imageStatus = await page.evaluate(() =>
      [...document.images].map((img) => ({
        src: img.currentSrc || img.src,
        complete: img.complete,
        naturalWidth: img.naturalWidth,
      }))
    );
    const failedImages = imageStatus.filter(
      (img) => !img.complete || img.naturalWidth === 0
    );
    if (failedImages.length) {
      console.warn('Images not successfully loaded:', failedImages);
    }

    await page.screenshot({ path: 'page.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Replace https://example.com with the page you are authorized to capture. The image check reports page image elements that did not successfully load; whether they must block your capture depends on your use case. It does not detect every visual asset: CSS background images, canvas content, and images inserted or managed by site-specific scripts may need separate checks.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why scrolling and image checks matter

Full-page capture is not lazy-load triggering

fullPage: true requests a screenshot of the full page, but it does not itself guarantee that every off-screen image has been requested or rendered. Scrolling can prompt native lazy loading, intersection observers, and other viewport-triggered site behavior. Puppeteer provides page interaction primitives; the site defines what scrolling causes. [Puppeteer page interactions]

Navigation completion is not image completion

networkidle2 means there are no more than two network connections for at least 500 ms. networkidle0 waits for no active connections for at least 500 ms. These are lifecycle conditions, not guarantees that every image has loaded, decoded, or appeared in the page. [PuppeteerLifeCycleEvent; WaitForOptions; WaitForNetworkIdleOptions]

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

In the example, the navigation wait handles initial loading, scrolling encourages viewport-triggered requests, and image-specific checks give you evidence about ordinary <img> elements. A load or error event ends the wait for that image; naturalWidth helps distinguish a successfully loaded image from a failed one. The timeout prevents one stalled image from blocking the whole capture indefinitely.

Choose a wait strategy for the page

Strategy What it waits for When it helps What it does not establish
networkidle2 At most two network connections for at least 500 ms. A practical initial navigation wait for pages that settle while allowing a small number of persistent requests. That lazy images below the fold were triggered or rendered.
networkidle0 No active network connections for at least 500 ms. Pages that become fully quiet and where waiting for that stricter condition is appropriate. That every image is present; persistent polling or other activity can also prevent the condition.
Scroll plus image checks The script scrolls through the document and waits for visible image elements to load, fail, or reach a timeout. Pages where images load as they approach the viewport. That CSS backgrounds, canvas content, or custom application rendering are complete.

The 500 ms interval is part of Puppeteer’s documented lifecycle behavior, not a recommended universal delay for image loading. Choose based on the site’s behavior, and verify the actual content you need.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Adapt the method to the site

  • Use a longer image timeout if images are slow but do eventually load. Short timeouts may let the script move on while requests are still in progress.
  • Repeat scrolling when content grows. The example compares document height before and after each pass and limits repeats to avoid looping forever on pages that continuously append content.
  • Check a specific image or selector if only a subset matters. The example’s document.images check covers image elements, not CSS backgrounds or every custom lazy-loading implementation.
  • Account for site-specific triggers. Some pages need a particular scroll direction, a click, authentication, or waiting for a selector before images appear. Adjust the interaction and wait to match the page.
  • Keep an eye on the capture dimensions. Full-page capture produces an image of the document’s full dimensions, which can be large on very long pages. If your consumer has image-size limits, consider capturing sections or using an appropriate output workflow.

Troubleshoot missing or incomplete images

The screenshot still has blank image areas

Confirm that scrolling reached the relevant portion of the document and that the site’s lazy-load trigger is actually viewport-based. Inspect currentSrc, complete, and naturalWidth for affected image elements. A complete image with a zero natural width likely failed; an incomplete image may need more wait time or a page-specific trigger.

networkidle0 never completes

A page may keep connections active for polling, analytics, or other background work. Use networkidle2 if appropriate, or use a targeted selector or image wait instead of requiring total network silence. Puppeteer’s network-idle thresholds describe connection activity, not page correctness.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

The document gets taller while scrolling

Lazy content may be inserted only after earlier images or sections load. The example makes another pass when height increases, up to its configured maximum. Raise that limit only if the page’s behavior warrants it; endless-feed pages may never reach a stable height.

Images are loaded but visually incomplete

An image load event and nonzero natural width do not prove that every application-level visual update is finished. If the page animates, swaps sources, or renders content through canvas or CSS, wait for the relevant selector or state and inspect that content separately before capture.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The capture times out or uses too much memory

Very long pages can take substantial time and produce large screenshots. Increase the navigation timeout only when a slow but valid page is expected; for large documents, capture smaller sections or reduce output dimensions where suitable. Always close the browser in a finally block so failures do not leave the process running.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo offers a one-request screenshot API; its docs are at ScreenshotNeo documentation. For example, this cURL request captures a page as WebP:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports the page verdict and billing status in headers. It also has an MCP server for AI agents, with tools including take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. See ScreenshotNeo for details and sign up free.

FAQ

Does Puppeteer have a built-in option to load every lazy image?

The documented screenshot option requests a full-page capture, but Puppeteer’s screenshot API does not prescribe a universal lazy-image loading helper. Trigger and verify loading with interactions suited to the page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use networkidle0 or networkidle2?

Use the condition that fits the site’s network behavior. Neither one verifies that lazy-loaded image elements have successfully rendered.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.