Free tools Windows power users keep installed
One-click scans. No signup required.
Use Puppeteer’s ElementHandle.screenshot() to capture one live <div>. It scrolls the element into view when necessary and runs the normal page screenshot pipeline. The complete pattern is: launch Chromium, navigate, wait for a visible selector, wait for layout-sensitive assets, call element.screenshot(), and close the browser. Use page.screenshot({ clip }) instead when you need to add padding, combine coordinates, or control the crop yourself.
Capture a div with ElementHandle.screenshot()
This runnable ES module captures the element whose ID is card and writes a lossless PNG. Replace the URL and selector with your own page and target.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
const card = await page.waitForSelector('#card', { visible: true });
if (!card) throw new Error('Target #card was not found');
// Useful when fonts change the final geometry or text pixels.
await page.evaluate(() => document.fonts.ready);
await card.screenshot({ path: 'card.png', type: 'png' });
await card.dispose();
} finally {
await browser.close();
}
Install Puppeteer with npm install puppeteer, then run the file in a project configured for ES modules (for example, set "type": "module" in package.json). ElementHandle.screenshot() returns a Uint8Array when no path is supplied. Set encoding: 'base64' to receive a base64 string instead.
Why this is the correct API
An element handle represents the current DOM node, not merely a selector string. Puppeteer scrolls the target into view if needed and then uses Page.screenshot() to capture it. You therefore do not need a manual scroll for an ordinary off-screen element. The handle must still refer to a live node at capture time; a detached element causes an error.
#1 Best Overall
Use a robust selector and visibility check
Prefer a stable ID, data attribute, or semantic class over a generated CSS class. waitForSelector(selector, { visible: true }) waits for the node and requires it to be visible. A selector can exist while its element is hidden, has zero dimensions, or is covered by a loading state, so visibility is an important minimum check.
Wait for the pixels that affect the div
Navigation completion alone does not guarantee a stable image. Choose waits based on what the component renders.
- Fonts:
await page.evaluate(() => document.fonts.ready)prevents a late font swap from changing line breaks. - Images: wait for images inside the component to finish decoding when they determine its size. A practical in-page wait is:
await page.$eval('#card', async (el) => {
const images = [...el.querySelectorAll('img')];
await Promise.all(images.map((img) => img.complete
? img.decode().catch(() => {})
: new Promise((resolve) => {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
})));
});
- Application state: wait for a selector that means the card is populated, or wait for the request that supplies its data.
- Animations: disable or pause transitions when deterministic pixels matter. You can inject a style before capture:
await page.addStyleTag({ content: `
*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}
` });
For a component that updates continuously, capture only after its content and layout have reached the state you want. A fixed delay can help with a known animation, but a state-based wait is generally less brittle.
Capture with an explicit bounding-box clip
Use a clip rectangle when the screenshot must include deliberate padding, combine a measured region with another coordinate, or otherwise require page-level crop control.
Recommended Free Tools
Rank #2
const element = await page.waitForSelector('#card', { visible: true });
if (!element) throw new Error('Target not found');
const box = await element.boundingBox();
if (!box) throw new Error('Target has no visible bounding box');
const padding = 16;
await page.screenshot({
path: 'card-clip.png',
type: 'png',
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
},
captureBeyondViewport: true
});
boundingBox() returns coordinates in CSS pixels. It can return null when the node is hidden, has no rendered box, or is otherwise not measurable. Check the result before constructing clip. If the element moves between measuring and capturing, the crop can miss content; freeze layout or capture with the element handle instead.
Clip, full page, and viewport are different scopes
| Goal | API | Behavior | Main failure to handle |
|---|---|---|---|
| One live DOM element | elementHandle.screenshot() |
Uses the element’s bounds and scrolls it into view | Missing, detached, hidden, or zero-size element |
| Custom crop or padding | page.screenshot({ clip }) |
Captures the explicit rectangle you provide | Null or stale bounding box; incorrect coordinates |
| Entire document | page.screenshot({ fullPage: true }) |
Captures the page rather than one div | Very tall or dynamically changing pages |
| Visible viewport | page.screenshot({ path: 'viewport.png' }) |
Captures what is currently in the viewport | Target is outside the viewport or obscured |
Output formats and reproducible dimensions
PNG is the safest default for interface text, sharp borders, and pixel comparisons. JPEG and WebP can reduce file size when some quality loss is acceptable; use the format’s quality setting deliberately. Puppeteer supports a file path, binary output, base64 encoding, type, quality, omitBackground, clipping, and related screenshot controls.
Set the viewport and deviceScaleFactor explicitly. A 1280 CSS-pixel viewport at scale 2 produces a different physical image from the same viewport at scale 1. Keep these values fixed in visual tests and documentation examples. If transparency is required, use omitBackground: true with a format that supports it, normally PNG.
Complete reusable helper
import puppeteer from 'puppeteer';
export async function screenshotSelector({
url,
selector,
output = 'element.png',
width = 1280,
height = 800,
deviceScaleFactor = 1
}) {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width, height, deviceScaleFactor });
await page.goto(url, { waitUntil: 'networkidle0' });
const handle = await page.waitForSelector(selector, { visible: true });
if (!handle) throw new Error(`No visible element matched ${selector}`);
await page.evaluate(() => document.fonts.ready);
await handle.screenshot({ path: output, type: 'png' });
await handle.dispose();
return output;
} finally {
await browser.close();
}
}
await screenshotSelector({
url: 'https://example.com',
selector: '#card',
output: 'card.png'
});
The finally block closes Chromium even when navigation, waiting, or capture fails. In a long-running service, reuse a browser process carefully, but create an isolated page per job and always close pages after the job completes.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Troubleshooting common failures
“Target not found” or a selector timeout
- Verify the URL and selector in the same browser context.
- Increase the wait only when the page genuinely loads slowly; do not hide a wrong selector with an arbitrary delay.
- If the element is inside an iframe, obtain the frame first and query within that frame.
- If content appears after a click, perform the click and then wait for the resulting state selector.
Detached element errors
Frameworks often replace a node during rendering. Query the element after the update, wait for the stable state, and capture immediately. Do not retain a handle across a navigation or component rerender. Reacquire the handle rather than trying to use a disposed or detached one.
boundingBox() returns null
The element may be display: none, have zero size, be outside a rendered layout, or belong to a frame you are not querying. Wait for visibility, inspect computed layout in the correct frame, and ensure an ancestor is not collapsed. Element screenshots are preferable when you do not need a custom rectangle because Puppeteer handles scrolling for that operation.
The image is blank, clipped, or too small
Wait for the component’s data, fonts, and images. Check that a cookie dialog, modal, or overlay is not covering the target. For clips, confirm that x, y, width, and height are positive CSS-pixel values and that the page has not changed after measuring. Set captureBeyondViewport: true when a deliberate clip extends outside the current viewport.
Different pixels on different runs
Fix viewport size and device scale, use the same browser version, wait for fonts and images, and stop animations. Remote assets can still change; for strict visual regression, serve deterministic fixtures or control the relevant network responses. Avoid capturing while a layout shift is in progress.
Rank #4
Navigation never reaches networkidle0
Analytics, long polling, and chat connections can keep the network busy. Use a more appropriate navigation condition, then wait for the specific selector that proves the card is ready. A short, bounded delay is a fallback for a known transition, not a substitute for an observable readiness condition.
Performance, reliability, and cost choices
- Capture only the element you need; full-page screenshots require more rendering and storage.
- Reuse a launched browser for batches, but isolate pages and close each page in a
finallyblock. - Use PNG for fidelity and WebP or JPEG when transfer size matters more than lossless text.
- Do not dispose the handle until the screenshot promise resolves.
- Set explicit timeouts around navigation and readiness waits so a failed site cannot hold a worker forever.
- Record the URL, selector, viewport, device scale, output type, and error so a failed image can be reproduced.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API when you want one request instead of managing Chromium. It can capture one element by CSS selector, along with full pages and PDFs, and offers waits, custom JavaScript and CSS, device settings, cookies and headers, blocking controls, caching, and async jobs. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
For API parameters, including the element selector option, see the ScreenshotNeo documentation. A basic image request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The same endpoint works from 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(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', data));
ScreenshotNeo includes 1,000 shots per month free with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. Create a free ScreenshotNeo account to try the API.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →FAQ
Can I capture an element without saving a file?
Yes. Omit path and keep the returned Uint8Array in memory, upload it, or convert it to another representation. Request base64 explicitly with encoding: 'base64'.
Does ElementHandle.screenshot() capture an element below the fold?
Yes. Puppeteer scrolls the element into view before using the page screenshot pipeline. A custom clip still requires you to obtain and validate the correct bounding box.
When should I choose PNG over WebP?
Choose PNG for lossless text, borders, and visual comparisons. Choose WebP or JPEG when a smaller file is more important and the resulting compression is acceptable.
What happens if the div is replaced after I find it?
The original handle becomes detached and capture can fail. Wait for the replacement state, find the element again, and take the screenshot with the new handle.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Frequently Asked Questions
Can I capture an element without saving a file?
Yes. Omit path to receive a Uint8Array, or request encoding: 'base64' for a base64 string.
Does ElementHandle.screenshot() capture an element below the fold?
Yes. Puppeteer scrolls the element into view before capture.
When should I choose PNG over WebP?
Use PNG for lossless UI text and comparisons; use WebP or JPEG when smaller files matter more than lossless quality.
Quick Recap
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.

