Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesUse ElementHandle.screenshot() for a DOM element that is taller or wider than the current viewport. Puppeteer scrolls the element into view and captures it through Page.screenshot(). If you define a manual clip instead, obtain a non-null boundingBox() and set captureBeyondViewport: true explicitly. Do not use fullPage as a substitute: it captures the whole page, not an arbitrarily clipped element.
Choose the capture path that matches your target
Puppeteer 25.12.0 documents two useful approaches. The first targets a live DOM node; the second targets a coordinate rectangle.
| Approach | Best for | Important behavior |
|---|---|---|
ElementHandle.screenshot() |
One element selected from the DOM | Scrolls the element into view, then uses Page.screenshot() to capture it. The handle must remain attached. |
Page.screenshot({ clip, captureBeyondViewport }) |
An explicit rectangle, including a box calculated from an element | Requires a valid clip. Set captureBeyondViewport: true when the rectangle extends outside the viewport. |
In the current ScreenshotOptions documentation, captureBeyondViewport defaults to false when no clip is supplied and true when a clip is supplied. Setting it explicitly makes your intent clear and avoids relying on a default that may be misunderstood.
Primary method: screenshot the element handle
This is the shortest and usually the most reliable solution for an oversized element.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →#1 Best Overall
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.setViewport({width: 1280, height: 800, deviceScaleFactor: 1});
await page.goto('https://example.com/long-page', {waitUntil: 'networkidle2'});
const element = await page.waitForSelector('.target', {visible: true});
if (!element) throw new Error('Target element was not found');
await element.screenshot({path: 'element.png'});
} finally {
await browser.close();
}
The official API description says: “This method scrolls element into view if needed, and then uses Page.screenshot() to take a screenshot of the element.” See ElementHandle.screenshot() and the screenshots guide. A selector can match a panel, article, chart, table, or any other rendered node; the resulting image is the element’s box rather than the entire document.
Wait for the content that determines the size
An element can exist before its images, fonts, or client-rendered rows have finished changing its dimensions. Wait for a meaningful selector, a known application state, or image completion before taking the shot.
await page.waitForSelector('.target img');
await page.evaluate(async () => {
if (document.fonts) await document.fonts.ready;
await Promise.all([...document.images].map(img =>
img.complete ? undefined : new Promise(resolve => {
img.addEventListener('load', resolve, {once: true});
img.addEventListener('error', resolve, {once: true});
})
));
});
const element = await page.waitForSelector('.target', {visible: true});
if (!element) throw new Error('Target element was not found');
await element.screenshot({path: 'element.png'});
This wait is application-specific. It prevents a capture taken while a lazy-loaded region is still empty, but it cannot repair a selector that is detached or hidden by the page itself.
Explicit clip: use a bounding box and capture beyond the viewport
Use this path when you need to adjust coordinates, add padding, combine regions, or diagnose a blank result. boundingBox() returns coordinates relative to the main frame, with width and height in pixels. It returns null when the node is not in layout.
Recommended Free Tools
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
await page.setViewport({width: 1280, height: 800, deviceScaleFactor: 1});
await page.goto('https://example.com/long-page', {waitUntil: 'networkidle2'});
const element = await page.waitForSelector('.target', {visible: true});
if (!element) throw new Error('Target element was not found');
const clip = await element.boundingBox();
if (!clip) throw new Error('Target element has no layout box');
if (clip.width <= 0 || clip.height <= 0) {
throw new Error(`Invalid box: ${clip.width}x${clip.height}`);
}
await page.screenshot({
path: 'element-clipped.png',
clip,
captureBeyondViewport: true,
});
} finally {
await browser.close();
}
Read the box only after the page has laid out the target. If a CSS transition is changing its size, wait for the transition to finish or temporarily disable animations in a test-only stylesheet.
Adding safe padding
You can expand the rectangle, but keep coordinates and dimensions valid for the page and image limits.
const box = await element.boundingBox();
if (!box) throw new Error('Target element has no layout box');
const padding = 16;
const clip = {
x: Math.max(0, box.x - padding),
y: Math.max(0, box.y - padding),
width: box.width + padding * 2,
height: box.height + padding * 2,
};
await page.screenshot({path: 'padded.png', clip, captureBeyondViewport: true});
Why blank space or clipping appears
The handle is detached
Frameworks often replace a node during navigation or re-rendering. A handle obtained before that replacement is no longer attached. Re-select the element immediately before capture and avoid triggering a state change between selection and screenshot.
The element has no layout box
boundingBox() returns null for nodes that are display:none, detached, or otherwise absent from layout. Check visibility, the active tab or accordion state, and whether the selector points to a template rather than the rendered node.
Rank #3
A clip was taken without beyond-viewport capture
A rectangle extending below or beside the viewport can produce incomplete output when beyond-viewport capture is not enabled. Supply captureBeyondViewport: true with the clip and verify the installed Puppeteer and Chromium versions.
Lazy content is still empty
Scrolling the element into view does not guarantee that every nested image or virtualized row has rendered. Wait for content, trigger the application’s load mechanism, or capture after the page reports completion. For virtual lists, only mounted rows can be captured; you may need to change the application’s rendering strategy for a complete image.
Sticky, fixed, transformed, or nested-scrolling layouts
position: fixed, sticky headers, CSS transforms, and an inner element with overflow: auto can make the visual result differ from the element’s ordinary box. Inspect computed styles and scroll the correct container. A transformed element may have a box whose coordinates do not match the visual bounds you expect; test a handle screenshot and a manual clip separately.
Zero dimensions caused by timing or CSS
Log the box before capture:
console.log(await element.boundingBox());
If width or height is zero, wait for layout, remove a collapsed state, or correct the selector. Do not “fix” a zero box by inventing clip dimensions.
Diagnose the two APIs systematically
- Confirm the URL loaded and the expected frame is active.
- Resolve the selector with
waitForSelector; fail loudly if it returnsnull. - Check attachment and layout with
boundingBox(). - Record the box, viewport, device scale factor, and installed Puppeteer and Chromium versions.
- Try
element.screenshot()first. If you need coordinates, usePage.screenshotwith the recorded box and explicitcaptureBeyondViewport: true. - Inspect the page for lazy loading, nested scroll containers, transforms, fixed overlays, and animation.
- Open the resulting file and verify its pixel dimensions; a valid file with unexpected dimensions usually indicates layout or clip calculations rather than a PNG/JPEG encoding problem.
Viewport resizing: historical workaround, not a universal fix
An older Puppeteer issue, #1779, described oversized element clipping in version 0.13.0 and discussed enlarging the viewport. That discussion is historical. The Puppeteer changelog records an element-screenshot viewport-setting change in 21.9.0 and removal of viewport resizing from ElementHandle.screenshot() in 23.9.0 on November 21, 2024. Resizing can also trigger media-query and resize-event side effects. Use it only when you deliberately want the page to reflow at a different viewport, not as a blanket remedy for blank output.
Performance and reliability considerations
Keep the page state deterministic
Set the viewport and device scale factor before navigation, wait for the exact content needed, and disable nonessential animations in your test environment. A stable page makes repeated captures easier to compare.
Choose the smallest useful target
Element screenshots avoid rasterizing unrelated page content. A manual clip is useful for padding or coordinate-based workflows, but calculating it after layout is essential. Extremely large raster images consume memory; split a very long report into intentional sections when your downstream system cannot handle one huge bitmap.
Do not infer guarantees from one successful run
The official API pages define behavior and defaults, but do not publish a failure rate or a percentage improvement for any setting. Validate your own pages across their loading states and the Puppeteer/Chromium versions you deploy.
Or skip the browser setup
ScreenshotNeo provides a GET-based screenshot API when you do not want to maintain Puppeteer and Chromium. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.
For a page-level capture:
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 documentation for all options. The same request in Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
And 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(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. Every feature is included on every plan: the Free plan provides 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. You can sign up free.
Frequently asked questions
Does fullPage: true capture one oversized element?
No. fullPage describes a full-page capture. Use an element handle or a clip for one DOM region.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11What if the target is inside an iframe?
Obtain the frame with Puppeteer’s frame APIs, select the element within that frame, and take the screenshot from the corresponding handle. A selector evaluated in the main page cannot reach an iframe’s document.
Can I capture an element that is intentionally hidden?
Not as rendered content. Make it visible and laid out first, or render a separate export view. A hidden node has no meaningful visual box.
Which Puppeteer version should I use?
Match your code to the version installed by the project. The behavior described here reflects the current API material identifying Puppeteer 25.12.0; Chromium changes can still affect rendering.
Frequently Asked Questions
Can a screenshot include content from a virtualized list that is not mounted?
No. Puppeteer captures rendered pixels. Ask the application to mount the required rows or create an export-specific view before capturing.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
Why does my clip include a fixed header or overlay?
Fixed and sticky elements are painted independently of normal flow. Hide them with page CSS for the capture or target a region whose visual composition intentionally includes them.
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.




