The usual cause is a viewport-sized render boundary. html2canvas defaults windowWidth and windowHeight to window.innerWidth and window.innerHeight. ForeignObjectRendering then serializes and draws an SVG foreignObject using the configured render width and height. If those values remain equal to the visible viewport, content below or beside it is outside the canvas even when the document itself is much larger.
Measure the target element, pass its scroll dimensions as the cloning window, and set explicit output dimensions:
The working full-page configuration
const element = document.documentElement;
const canvas = await html2canvas(element, {
foreignObjectRendering: true,
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight,
width: element.scrollWidth,
height: element.scrollHeight
});
document.body.appendChild(canvas);
The project FAQ recommends windowWidth: element.scrollWidth and windowHeight: element.scrollHeight when output is cut off. Supplying width and height makes the intended canvas boundary explicit. This example targets the root document; use the actual element you intend to capture if you need a component or a nested scrolling region.
Why the default call follows the visible viewport
windowWidth and windowHeight are cloning bounds
At the entry point, html2canvas obtains default window dimensions from the document’s default view. In a normal browser window, those defaults are the current innerWidth and innerHeight. It creates window bounds with those values and clones the document using them. Turning on foreignObjectRendering changes the renderer, not those defaults.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems#1 Best Overall
That is why this call commonly captures only what you can see:
const canvas = await html2canvas(document.documentElement, {
foreignObjectRendering: true
});
The page may have a large scrollHeight, but the cloned render window is still viewport-sized. Anything outside that boundary can be omitted from the serialized output.
ForeignObjectRenderer uses the render dimensions
In the current source, ForeignObjectRenderer creates a canvas with options.width * options.scale by options.height * options.scale. It creates an SVG foreignObject with those same scaled dimensions, loads the serialized SVG as an image, and draws it into the canvas after translating by the configured x and y offsets. A small width or height therefore produces a small drawing surface, regardless of how far the document can scroll.
The source labels this renderer experimental. Browser behavior, CSS support and external resources can therefore differ from the default renderer.
Measure before changing options
Do not assume that the document’s scroll size is the same as the element you want. Inspect all three measurements:
Rank #2
const element = document.documentElement;
const rect = element.getBoundingClientRect();
console.table({
rectWidth: rect.width,
rectHeight: rect.height,
scrollWidth: element.scrollWidth,
scrollHeight: element.scrollHeight,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight
});
getBoundingClientRect()describes the element’s current layout box in the viewport.scrollWidthincludes horizontally overflowing content and the element’s scrollable width.scrollHeightincludes vertically overflowing content and the element’s scrollable height.innerWidthandinnerHeightdescribe the browser viewport that html2canvas uses by default.
If a page has a horizontal overflow region, use its measured scrollWidth as well as its height. If you are capturing a nested element, read dimensions from that element rather than automatically using document.documentElement.
A robust capture helper
This helper validates dimensions, lets you choose the target, and makes the scroll position explicit:
async function captureFullPage(target = document.documentElement) {
const width = target.scrollWidth;
const height = target.scrollHeight;
if (!width || !height) {
throw new Error(`Target has invalid dimensions: ${width} x ${height}`);
}
return html2canvas(target, {
foreignObjectRendering: true,
windowWidth: width,
windowHeight: height,
width,
height,
scrollX: window.scrollX,
scrollY: window.scrollY
});
}
const canvas = await captureFullPage();
const link = document.createElement('a');
link.download = 'full-page.png';
link.href = canvas.toDataURL('image/png');
link.click();
scrollX and scrollY are the scroll positions used for rendering. Set them deliberately when fixed-position content, a scrolled target or a component inside a scrolling container affects the result. For a reproducible page capture, record the values you use and avoid changing the page between measuring and rendering.
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 →What each option controls
| Option | Purpose | Typical full-page choice |
|---|---|---|
foreignObjectRendering |
Selects the experimental SVG foreignObject-based renderer. |
true |
windowWidth |
Width of the window bounds used while cloning the document. | Target scrollWidth |
windowHeight |
Height of the window bounds used while cloning the document. | Target scrollHeight |
width |
Output render width before scaling. | Target scrollWidth |
height |
Output render height before scaling. | Target scrollHeight |
scale |
Multiplies the renderer’s canvas and SVG dimensions. | Choose with browser limits in mind |
scrollX, scrollY |
Scroll positions used during rendering. | Set explicitly for scrolled or fixed-position cases |
Do not confuse the cloning window with the output size. Passing only width and height can leave the cloned document constrained by viewport defaults; passing only windowWidth and windowHeight can leave the output boundary implicit. For a predictable full-page attempt, set both pairs from measured dimensions.
Fixed elements, nested scrollers and dynamic content
Fixed and sticky UI
A fixed header or chat panel is positioned relative to a viewport or containing block, not simply appended below normal flow. Decide whether it should appear once at its rendered position or be hidden for a clean document image. Set scrollX and scrollY deliberately, and test at the scroll position your output is meant to represent.
Rank #3
Nested scrolling containers
If the page contains a panel with its own scrollbar, the root document’s scrollHeight may not represent the panel’s complete content. Capture that panel and use its own scrollWidth and scrollHeight, or temporarily arrange the panel so its content is expanded before measuring. The important rule is to measure the element whose pixels you want.
Lazy-loaded and late layout changes
Images, fonts and scripts can change layout after the first measurement. Wait until the content is in its final state, then measure and call html2canvas. If the page changes while the clone is being built, the measured boundary and the actual layout can disagree; freeze animations and avoid mutating the DOM during capture when possible.
Canvas limits can look like a renderer bug
The html2canvas FAQ warns that the canvas may hit browser size limits. Maximum width, height and total pixel area vary by browser and platform. The FAQ gives rough evergreen-browser maximum dimensions around 32,767 pixels, while area limits also vary. An oversized canvas may be blank or partially rendered without throwing an exception.
Reduce the risk
- Log the measured width, height and
scalebefore rendering. - Capture a smaller element or split a very long document into sections.
- Lower
scalewhen the resulting pixel dimensions are excessive. - Try the default renderer as a diagnostic comparison.
- Test in the browser and platform that will run the production capture.
The effective pixel dimensions are approximately width × scale by height × scale. A modest CSS size can become a very large bitmap at a high scale.
ForeignObjectRendering versus other capture approaches
| Approach | Dimension control | How pixels are produced | Important trade-offs |
|---|---|---|---|
| html2canvas default renderer | Uses html2canvas options, including explicit dimensions. | Reconstructs the page from DOM and CSS. | CSS, image/font loading and cross-origin restrictions can affect fidelity. |
| html2canvas ForeignObjectRendering | Uses the cloning bounds and the renderer’s width and height. | Serializes the DOM into an SVG foreignObject, then draws it to a canvas. |
Experimental; browser support and CSS/resource behavior can vary. |
| Browser-native automation | Typically controls a browser viewport and screenshot surface directly. | Captures a native browser-rendered surface. | Requires browser setup and has its own viewport, resource and deployment considerations. |
html2canvas is not a native screenshot facility: it reconstructs a representation from the DOM and CSS in the page. Unsupported CSS, cross-origin resources and browser differences can change the result. Use the default renderer as a comparison when ForeignObjectRendering produces an unexpected image, but do not interpret one browser’s result as a universal guarantee.
Rank #4
Troubleshooting checklist
Only the visible viewport is captured
Cause: windowWidth, windowHeight, width or height remained at viewport-sized defaults. Fix: measure the target and pass scrollWidth/scrollHeight to all relevant options, as in the full-page example.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →windowWidth and windowHeight appear ignored
Check that you are passing numbers from the intended element, not stale values captured before layout completed. Confirm that the target’s measured dimensions are larger than innerWidth/innerHeight, and inspect the resulting canvas dimensions. Historical issue #1754, opened on 2019-02-08 for html2canvas 1.0.0-alpha.12 in Firefox 56 on Windows 10, reported a ForeignObjectRendering case where explicit window dimensions behaved differently from the plain renderer. That is version-specific historical evidence, not proof that every current release has the same defect.
The bottom is blank or cut off with no exception
Check the browser’s maximum canvas dimensions and total area first. Reduce the target or scale, then retry. A blank or partial output can be a platform limit rather than a JavaScript error.
Content is shifted or fixed controls repeat
Set scrollX and scrollY deliberately, and test with fixed or sticky elements temporarily hidden. Verify that you are capturing the intended scrolling element rather than the root document.
Styles or images differ
Reduce the page to a reproducible example and compare the default renderer. ForeignObjectRendering depends on browser support for the serialized SVG and on resource availability. Cross-origin images, fonts and unsupported CSS can prevent a pixel-for-pixel result even when the dimensions are correct.
The canvas is unexpectedly huge
Print scrollWidth, scrollHeight and scale. An off-screen element, an accidental horizontal overflow or a very large child can inflate the boundary. Capture the specific content element instead of the whole document when that is what the user needs.
Best Value
Or skip the browser setup
If the goal is a dependable website image rather than a client-side DOM reconstruction, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
Here is the one-call cURL version (the ScreenshotNeo documentation lists the options):
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 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 in 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 fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Options for production captures
ScreenshotNeo supports full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets and arbitrary viewports, retina scale, PDF paper sizes/margins/orientation/page ranges, HTML/CSS-to-image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, waits for a selector, delay or network idle, ad/tracker/request/resource blocking, custom headers/cookies/user agents and Authorization, timezone and geolocation, transparent backgrounds, image resizing, configurable-TTL caching, signed links for public <img> tags, asynchronous jobs with signed webhooks, bulk capture of 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.
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 & 11Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients, so an AI agent can request captures without you building a browser harness. Plans include 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to start.
Cost, reliability and operational choices
Client-side html2canvas
- No screenshot-service request is required, but the page’s browser, CSS, resources and canvas limits determine the result.
- Every user’s viewport, device pixel ratio, fonts and browser can produce different output.
- Very tall pages may require splitting or reduced scale.
ScreenshotNeo
- Only clean shots are billed; failed loads, bot checks, blank pages, timeouts and cache hits are identified and not billed.
- Cleanup of consent banners, popups and chat widgets happens before capture.
- Free usage is 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Yearly billing provides two months free, and every feature is available on every plan.
Choose html2canvas when the capture must run inside the current page and you need a client-side canvas. Choose an API when you need repeatable server-side captures, PDF output, automation controls or AI-agent integration without maintaining browser setup.
Frequently Asked Questions
Does setting only foreignObjectRendering: true make a screenshot full page?
No. That flag selects the renderer; it does not replace the default viewport-sized window bounds. Set the cloning and output dimensions from the target’s scroll size.
Should I use document.body or document.documentElement?
Use the element whose complete content you intend to capture and measure that element. For a document-wide attempt, document.documentElement is a clear starting point; nested scrolling regions require their own measurements.
Recommended Free Tools
Why can a correct configuration still produce a blank canvas?
The requested bitmap may exceed browser width, height or total-area limits. Reduce the target or scale, or capture in sections.
Is ForeignObjectRendering a native browser screenshot?
No. html2canvas reconstructs a representation from DOM and CSS, serializes it for the foreign-object renderer and draws it into a canvas.
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.




