Can a screenshot API capture lazy-loaded background images?
Yes, if the page loads the background before capture. Learn how to trigger lazy loading, wait for the right content, and troubleshoot missing images.
Yes. A screenshot API can capture a lazy-loaded CSS background image if the page requests and renders that image before the screenshot is taken. For images loaded when an element approaches the viewport, scroll to the element, wait until its background is ready, then capture. A full-page screenshot or a network-idle wait alone does not necessarily trigger offscreen content.
The key distinction: HTML loading="lazy" applies to <img> elements, not CSS background-image. Backgrounds may load when CSS and the browser need them, or when page JavaScript assigns the background after a visibility trigger. The right fix depends on which mechanism the page uses. See MDN’s image loading documentation and Google’s lazy-loading guidance.
1. Why a background image can be missing
A CSS background can be absent from a screenshot for several different reasons:
- The page has not triggered its lazy loader. A script may wait for the element to approach the viewport, often using
IntersectionObserver. If capture starts without scrolling there, the script may not assign or request the image. - The stylesheet or image request failed. The CSS rule may not have loaded, the URL may be invalid, or the request may be blocked or return an error.
- The element is not visibly sized. A background can be loaded but impossible to see if its element has zero height, is hidden, or is covered by another element.
- The capture excludes the relevant region. Full-page modes and maximum dimensions vary by service. A screenshot can be full-page in name but still omit content beyond a limit.
If the background is declared in ordinary CSS and is immediately needed, inspect whether the stylesheet loaded, the element has dimensions, and its computed background-image is correct. If JavaScript defers assigning it until visibility, the browser must reach the element or satisfy the page’s trigger before capture. That diagnosis follows from visibility-triggered loading behavior described in the browser guidance above.
2. Why full-page capture and network idle may not be enough
Full-page describes the output extent; it does not guarantee that automation scrolled through each viewport before capture. Likewise, a network-idle condition waits for qualifying network activity to settle. If the page has not initiated the background request, waiting does not initiate it. These limits follow from the documented scroll-to-trigger workflows offered by screenshot services.
Navigation completion and network idle are useful readiness signals, but neither proves that every offscreen background image has loaded. Cloudflare documents that JavaScript-heavy pages may be incomplete at a default navigation event and offers wait modes and selector waits; those controls still need to be paired with the trigger the page requires. See Cloudflare’s screenshot endpoint documentation.
3. A reliable capture workflow
- Identify the target element. Find the selector for the component whose background is missing.
- Trigger its lazy behavior. Use the API’s scroll-before-capture option, or browser automation to scroll through the page in viewport-sized steps until the element enters view.
- Wait for the specific content. Prefer a selector, page condition, or image-ready signal that indicates the component has rendered. Use network idle or a short delay as supporting signals, not universal proof.
- Capture the needed extent. Select full-page or a clipped region as appropriate, and check the provider’s height and dimension limits.
- Verify the result. If the background remains missing, inspect the computed CSS, element dimensions and visibility, and whether the image request succeeded.
When you control the site, consider exposing a reliable readiness marker for the component, or avoid deferring a background that must be present in the initial viewport. This makes automation less dependent on arbitrary sleeps.
4. DIY browser example with Playwright
This runnable Node.js example opens a page, scrolls down in viewport-sized increments to trigger visibility-based loaders, waits for network activity to settle after each step, returns to the top, and saves a full-page screenshot. Install Playwright and its Chromium browser with npm install playwright and npx playwright install chromium, then save the code as capture.mjs and run node capture.mjs.
import { chromium } from 'playwright';
const targetUrl = 'https://example.com';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 45000 });
// Scroll through the document so viewport-triggered content can load.
await page.evaluate(async () => {
const step = Math.max(400, window.innerHeight * 0.8);
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
for (let y = 0; y < document.documentElement.scrollHeight; y += step) {
window.scrollTo(0, y);
await pause(250);
}
window.scrollTo(0, document.documentElement.scrollHeight);
await pause(500);
window.scrollTo(0, 0);
});
// Supplement scrolling with a network-settle signal.
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
The example uses a bounded pause during scrolling because some sites do not reach a strict network-idle state due to analytics, streaming, or long polling. For production, replace or supplement that pause with a target-specific readiness check when you know the site’s DOM. For example, if the target component adds a class after loading, wait for that class with page.locator('.hero.loaded').waitFor(). If the background URL is known, inspect the computed style after the element becomes visible.
Target a specific element
If you know the selector, scroll directly to it and wait for the component to become ready before capturing. Adapt the readiness condition to the target site; the class below is an example, not a universal convention.
const target = page.locator('.lazy-background');
await target.scrollIntoViewIfNeeded();
await target.waitFor({ state: 'visible', timeout: 10000 });
await page.waitForFunction((selector) => {
const el = document.querySelector(selector);
if (!el) return false;
const value = getComputedStyle(el).backgroundImage;
return value && value !== 'none';
}, '.lazy-background', { timeout: 15000 });
await page.screenshot({ path: 'component.png', fullPage: true });
A computed background URL indicates that CSS has assigned a background, not necessarily that every pixel is decoded and painted. If exact readiness matters, inspect the request and page rendering, or have the application expose a completion marker after its own image-loading logic finishes.
5. Screenshot API controls to check
API capabilities and defaults differ. Before choosing a service, check its current documentation for these controls:
| Control | Why it matters |
|---|---|
| Scroll before capture | Can trigger visibility-based loading for offscreen elements. |
| Wait for selector or condition | Lets capture wait for the component that matters rather than only page navigation. |
| Navigation wait mode | DOM ready, load, and network idle represent different milestones. |
| Capture extent and size limits | Determines whether the target region fits in the output. |
| Timeout and failure reporting | Helps distinguish a slow page from a failed request or unsupported behavior. |
| Authentication and request controls | Some pages require cookies, headers, or other session state before their assets are accessible. |
For examples of provider-specific controls, ScreenshotAPI documents doScroll, waitUntil, and waitForSelector; Browserless documents scrollPage; and Cloudflare documents scroll, selector wait, and navigation wait options. These docs show that such workflows are supported by particular APIs, not that every provider enables them by default or guarantees every site will load. The capture-website project also illustrates a browser-library approach that scrolls and waits before capture; it is a library behavior, not a hosted API guarantee.
6. Troubleshooting missing backgrounds
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Background is absent only below the fold | Visibility trigger never ran. | Scroll through the page or directly to the element before capture; wait for its component to load. |
| Full-page screenshot is blank in a region | Full-page output did not trigger loading, or the region is outside a dimension limit. | Enable pre-capture scrolling if available; check the output dimensions and service limits. |
| Waiting for network idle changes nothing | The image request has not started, or persistent requests prevent idle. | Trigger the element first. Use a selector/readiness condition, and treat network idle as supplementary. |
Computed background-image is none |
Stylesheet missing, selector mismatch, or script has not assigned the URL. | Inspect loaded CSS, computed styles, page scripts, and whether the visibility condition was met. |
| Computed style contains a URL but image looks blank | The request may have failed, the element may be covered or too small, or rendering may not have settled. | Check the image request status, element dimensions, visibility, overlays, and target-specific readiness. |
| Image works in a normal browser but not the API | The page may require authentication, a particular user agent, cookies, geolocation, or resources blocked by the service. | Compare browser and capture requests; configure the required session inputs if the API supports them. |
| Capture times out on a dynamic page | Waiting for global network idle may never finish, or the page is slow. | Use a bounded timeout and a target-specific wait. Avoid relying on a global idle event for pages with ongoing requests. |
7. Performance, reliability, and cost
Scrolling the page adds browser work and capture time, especially for long pages. A viewport-sized step with a short settling interval is a practical starting point; larger steps are faster but can skip a narrow visibility threshold, while tiny steps create more work. Tune based on the page’s loader and use a target selector when possible.
Reliability improves when the workflow waits on the content that matters, reports navigation and request failures, and uses explicit timeouts. Fixed delays are simple but can be too short on slow pages and wasteful on fast ones. Network idle can be useful after triggering the content but may be unsuitable for pages with persistent requests. Browser behavior, page scripts, and provider limits all affect results; no scroll or wait option guarantees every site will render successfully.
For a DIY workflow, account for browser compute, runtime, and any infrastructure you operate. For a hosted API, check its billing rules, timeout behavior, and treatment of failed captures. Do not assume a provider’s full-page or wait option has identical cost or semantics to another provider’s.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its full-page capture loads lazy images, and the API supports waits for a selector, delay, or network idle. A simple call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for the request options. Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
9. FAQ
Does loading="lazy" work on a CSS background?
No. That HTML attribute is for images in <img> elements. CSS backgrounds are requested according to CSS/browser needs or site-specific JavaScript behavior.
Does a full-page screenshot always scroll the page first?
No. Capture extent and pre-capture scrolling are separate behaviors. Check whether the service offers a scroll control.
Should I wait for network idle or a fixed delay?
Use a target-specific readiness condition where possible. A bounded delay or network-idle wait can supplement it, but neither guarantees that an untriggered background will load.
How do I tell whether the page or screenshot API caused the problem?
Inspect the page’s computed background style and image request in browser automation. If the page never assigns or requests the image after scrolling, investigate the site’s loader; if it does, check capture extent, timing, and provider limits.


