If absolutely positioned elements appear piled at the top of an html2canvas image, there is no universal one-line fix. First determine whether the browser’s live layout is wrong or whether html2canvas is reconstructing it incorrectly. Compare getBoundingClientRect() values immediately before capture, then test scroll coordinates, viewport dimensions, the cloned document, and any SVG or transform involved.
html2canvas does not copy the browser’s already-painted pixels. It traverses the DOM, reads supported properties, and builds its own canvas representation. The project documentation warns that CSS support is incomplete, so a correct browser view can still produce a different canvas.
What “stacking at the top” usually means
There are two different failures that look similar:
- The page layout is already wrong. The absolutely positioned nodes share an unexpected containing block, have missing dimensions, or are affected by application code before capture.
- The page is correct but the canvas is wrong. The browser displays the elements at their intended coordinates, while html2canvas loses or changes geometry, scroll origin, viewport assumptions, stacking context, transforms, clipping, or SVG serialization.
Do not begin by adding a larger z-index. Z-index changes paint order, not the coordinates returned by layout. Likewise, changing every position:absolute to relative or always scrolling to the top can hide one case while breaking the intended design.
#1 Best Overall
1. Capture the browser’s actual geometry
Run this diagnostic immediately before calling html2canvas. Replace the selectors with the affected element and the ancestor that should establish its containing block.
const target = document.querySelector('.capture-target');
const positionedAncestor = target?.offsetParent;
function report(label, el) {
if (!el) return console.log(label, 'not found');
const r = el.getBoundingClientRect();
const s = getComputedStyle(el);
console.log(label, {
rect: { x: r.x, y: r.y, width: r.width, height: r.height },
position: s.position,
top: s.top,
left: s.left,
transform: s.transform,
zIndex: s.zIndex,
overflow: s.overflow,
width: s.width,
height: s.height,
offsetParent: el.offsetParent?.className || el.offsetParent?.tagName
});
}
report('target', target);
report('offset parent', positionedAncestor);
console.log('scroll', { x: window.scrollX, y: window.scrollY });
console.log('viewport', { width: window.innerWidth, height: window.innerHeight });
If the rectangles already show every child at the top, fix the containing block or application layout first. An absolutely positioned element is laid out relative to its positioned ancestor (or another applicable containing block), not automatically relative to the element you intended. Check for a missing position:relative ancestor, a transformed ancestor, zero dimensions, or styles applied only after your capture starts.
If the browser rectangles are correct and only the canvas is wrong, continue with the capture-specific tests below.
2. Use a minimal, reproducible capture
Reduce the page to one positioned parent and two children. Keep the same relevant properties—absolute offsets, transforms, overflow, and any SVG. Capture that subtree rather than the entire application.
Free tools Windows power users keep installed
One-click scans. No signup required.
const node = document.querySelector('.capture-target');
const canvas = await html2canvas(node, {
backgroundColor: '#fff',
logging: true
});
document.body.appendChild(canvas);
Record the html2canvas version, browser and version, operating system, scroll position, device-pixel ratio, capture options, and a screenshot of the live page. A small reproduction tells you whether the issue is a CSS-support gap, an interaction with your framework, or a coordinate mistake in the larger page.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
3. Test scroll coordinates and fixed-position behavior
The configuration reference documents scrollX and scrollY as the scroll positions used when rendering. They matter especially when fixed-position elements or nested scrolling containers are present.
- Capture with the page at scroll position zero.
- Capture again at the scroll position that produces the defect.
- Test explicit values representing the coordinate frame you want.
const canvas = await html2canvas(document.querySelector('.capture-target'), {
scrollX: window.scrollX,
scrollY: window.scrollY
});
A June 2019 report for html2canvas 1.0.0-rc.3, Chrome 75 on Windows, described a blank offset when capturing after scrolling to the bottom; that reporter said window.scrollTo(0, 0) fixed that instance and that rc.1 behaved differently. This is a historical, version-specific clue—not a general prescription.
For a controlled experiment, save the current position, move to the top, capture, and restore it:
Recommended Free Tools
const oldX = window.scrollX;
const oldY = window.scrollY;
window.scrollTo(0, 0);
await new Promise(requestAnimationFrame);
const canvas = await html2canvas(document.querySelector('.capture-target'), {
scrollX: 0,
scrollY: 0
});
window.scrollTo(oldX, oldY);
If the target is inside a nested scrolling element, inspect that element’s scrollTop and scrollLeft as well. The window’s coordinates do not describe every scroll container.
4. Match the rendering viewport for large or responsive content
For a tall element that is blank, clipped, or changes layout during capture, the FAQ demonstrates using the element’s scroll dimensions:
Rank #3
const element = document.querySelector('.capture-target');
const canvas = await html2canvas(element, {
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight
});
windowWidth and windowHeight define the rendering viewport. They can therefore change media-query results as well as the available canvas area. A wider value may switch a responsive component to a different layout; compare computed styles before and after changing it.
Canvas dimensions and total area limits vary by browser and platform. An oversized canvas can be blank or partially rendered. If the small reproduction works but the full page does not, capture sections, reduce the viewport or output scale, and check the browser console for allocation errors.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →5. Check containing blocks, transforms, clipping, and stacking contexts
Change one variable at a time in the minimal reproduction:
- Add
position:relativeto the intended parent and verify that it becomes the child’soffsetParent. - Temporarily remove transforms from ancestors. A transform can create a new containing block and stacking context.
- Temporarily change
overflow:hiddenoroverflow:cliptovisibleto distinguish clipping from displacement. - Compare
position:absolutewithposition:fixedandposition:relativeonly as diagnostic variants, not as automatic fixes. - Give the parent explicit width and height if its size depends on content that has not loaded.
html2canvas internally processes stacking contexts and positioned descendants in separate buckets for negative z-index, zero or automatic z-index (including transformed or opaque elements), and positive z-index. That explains why paint order and geometry must be investigated separately; it does not establish that z-index causes every top-position symptom.
6. Use onclone for capture-only experiments
The onclone option receives the cloned document that html2canvas renders. You can alter that clone without changing the production page. Use a narrow override to test a suspected ancestor, transform, or clipping rule.
Rank #4
- 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
const canvas = await html2canvas(document.querySelector('.capture-target'), {
onclone: (clonedDocument) => {
const target = clonedDocument.querySelector('.capture-target');
const parent = target?.querySelector('.positioning-parent');
if (parent) {
parent.style.position = 'relative';
parent.style.transform = 'none';
parent.style.overflow = 'visible';
}
}
});
Do not leave a speculative override in place. If this changes the output, compare the clone’s computed geometry and identify which declaration matters. Then decide whether the real page should change or whether the workaround belongs only in the capture path.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall7. Treat SVG as a separate branch
A reported html2canvas 1.4.1 case on Chrome 111 and Windows 10 involved an absolutely positioned SVG that was not at the parent’s upper-left. The report associated incomplete output with SVG serialization through XMLSerializer. It does not prove that ordinary absolutely positioned div elements have the same defect.
Capture the SVG alone, then test a temporary clone moved in flow or to the parent’s top-left. If only the positioned SVG fails, include the SVG markup, viewBox, computed dimensions, offsets, and package/browser versions in a minimal issue report.
8. A robust diagnostic capture function
async function captureWithDiagnostics(selector) {
const element = document.querySelector(selector);
if (!element) throw new Error(`No element matches ${selector}`);
const rect = element.getBoundingClientRect();
console.table({
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
scrollX: window.scrollX,
scrollY: window.scrollY,
devicePixelRatio: window.devicePixelRatio,
html2canvas: typeof html2canvas
});
return html2canvas(element, {
scrollX: window.scrollX,
scrollY: window.scrollY,
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight,
logging: true
});
}
captureWithDiagnostics('.capture-target').then(canvas => {
document.body.appendChild(canvas);
});
Use the viewport dimensions only when they match your intended coordinate system; they are not automatically correct for every page.
Common symptoms and targeted fixes
| Symptom | Likely branch | What to test |
|---|---|---|
| Elements are at the top in the live page and canvas | Application layout or containing block | Inspect offsetParent, computed position, dimensions, and ancestor transforms. |
| Live page is correct; canvas is wrong only after scrolling | Scroll coordinate mismatch | Compare zero-scroll and explicit scrollX/scrollY captures. |
| Only tall or wide captures are blank or clipped | Viewport or canvas limits | Test scrollWidth/scrollHeight, smaller sections, and browser allocation limits. |
| Layout changes when using viewport options | Media-query breakpoint | Compare computed styles with and without windowWidth and windowHeight. |
| Only an absolutely positioned SVG is incomplete | SVG serialization path | Capture the SVG alone and test an in-flow or top-left clone. |
| Clone-only CSS change fixes the image | Unsupported or altered CSS path | Keep the override narrow and document why it is needed. |
When to file an issue or choose a real-browser screenshot
If a CSS property appears unsupported or incomplete, create a minimal test case and open an html2canvas issue. Include:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
- html2canvas version, browser version, and operating system;
- minimal HTML and CSS, including the positioning ancestor;
- live
getBoundingClientRect()values; - window and nested-container scroll positions;
- all capture options and the resulting image;
- whether the failing node is HTML, SVG, transformed, fixed, or clipped.
For server-side output where faithful browser painting is essential, the project FAQ points to Puppeteer or Playwright, which drive a real headless browser. That is an architectural alternative, not proof that every html2canvas problem has a browser-automation fix.
Or skip the browser setup
If your goal is a reliable website screenshot rather than debugging a client-side canvas, ScreenshotNeo captures a URL through its screenshot API. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
One GET request is enough:
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, including full-page lazy-image loading, CSS-selector element capture, dark mode, device presets, custom viewport and retina scale, PDF output, custom CSS or JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, cache TTL, signed links, asynchronous jobs, webhooks, bulk capture, usage data, and the OpenAPI specification. Parameters used by other screenshot APIs are also accepted to ease migration.
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)
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(`${res.status} ${res.statusText}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
Cost, reliability, and performance choices
- Client-side html2canvas: no screenshot service request, but output depends on supported CSS, browser canvas limits, page state, and your coordinate handling.
- Real-browser automation: higher setup and runtime overhead, but it renders through an actual browser engine and is suitable for server-side workflows.
- ScreenshotNeo: a single HTTP request or MCP tool call; cleanup occurs before capture, failed or unusable page classes are not billed, and caching or asynchronous jobs can reduce repeated work.
For html2canvas, wait until fonts, images, and layout-affecting data have loaded before measuring rectangles. For any method, capture smaller regions when a full-page canvas approaches browser limits, and log the response status or diagnostic headers so failures are distinguishable from valid images.
Frequently Asked Questions
Does increasing z-index fix absolutely positioned elements that move to the top?
Usually not. Z-index controls paint order; first verify the containing block, computed rectangles, scroll coordinates, and html2canvas’s CSS support.
Should I always call window.scrollTo(0, 0) before html2canvas?
No. A 2019 report found that workaround for one version and browser combination. Reproduce the scroll-dependent failure and then set explicit coordinates for the frame you intend.
Can html2canvas produce a pixel-perfect browser screenshot?
Not universally. It reconstructs a canvas from DOM information and documents incomplete CSS support, so its output can differ from the browser’s painted page.
What information should accompany a bug report?
Provide a minimal DOM/CSS case, package and browser versions, operating system, capture options, scroll positions, computed rectangles, and before/after images.
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.

