If html2canvas uses a fallback font, shifts text metrics, or drops text in Chrome, first wait for the browser’s font set and layout to settle. Await document.fonts.ready; explicitly request the required face with document.fonts.load() when needed; verify the real font request in DevTools; then compare captures with foreignObjectRendering disabled. If fonts are loaded and the result still differs, you may be seeing an html2canvas CSS or SVG rendering limitation rather than a Chrome font-loading failure.
What html2canvas is—and why a correct page can produce a wrong font
html2canvas does not copy the browser’s pixels. It reconstructs a canvas from the target DOM and the CSS properties it implements. Chrome is supported, but support does not mean that every CSS feature, font configuration, or combination of options will match the onscreen page. The project FAQ puts the limitation plainly: “Every CSS property must be manually implemented to render correctly, so html2canvas will never have full CSS support.”
That leaves three broad causes for an @font-face mismatch:
- The webfont was still loading when capture began, so fallback metrics were captured.
- The requested family, weight, style, or document font set is not the one you inspected.
- The font is ready, but html2canvas cannot reproduce the relevant CSS, SVG text, or renderer mode.
Diagnose them in that order. Do not assume that a browser that displays the live page has supplied html2canvas with the same resources at the same time.
#1 Best Overall
1. Wait for used fonts before calling html2canvas
document.fonts.ready resolves after used fonts finish loading and related layout work completes. Put the await immediately before capture, not only during initial page setup.
async function captureElement(element) {
await document.fonts.ready;
return html2canvas(element);
}
const canvas = await captureElement(document.querySelector('#invoice'));
document.body.appendChild(canvas);
The promise concerns fonts the document actually uses. Unused or optional faces can remain unloaded, so this is necessary but not always sufficient when your element introduces a particular weight or text string only at capture time.
2. Explicitly load the exact family, weight and style
Use FontFaceSet.load() with a CSS font shorthand that matches the rule applied to the element. The second argument is sample text; include representative characters, especially if a subsetted font is involved.
async function captureWithBrandFont(element) {
try {
await document.fonts.load('600 16px "Brand Sans"', 'Invoice total €0123456789');
await document.fonts.ready;
} catch (error) {
console.error('Brand Sans failed to load', error);
throw error;
}
return html2canvas(element, {
foreignObjectRendering: false
});
}
A rejected load() promise is a real failure signal: inspect the request, response and console error rather than silently capturing with a fallback. If your CSS uses italic, variable-font axes, or another weight, request that exact face instead of assuming a neighboring face will be substituted correctly.
Why document.fonts.check() is not proof
document.fonts.check() answers whether rendering can proceed without an unloaded face causing a swap. It can return true when a nonexistent family would be rendered with a usable fallback. Treat it as a pending-load diagnostic, not as evidence that the intended file, family or weight exists.
3. Verify the browser request, not just the CSS declaration
- Open Chrome DevTools and select the Network panel.
- Reload with the panel open, filter by Font, and locate the request for the family and weight used by the target element.
- Confirm the response succeeds, the URL is the expected file, and the request is not blocked by a policy, redirect, authentication failure or incorrect MIME configuration.
- Check the Console for decode, CORS, stylesheet or certificate errors.
- In the Elements panel, verify the captured node’s computed
font-family,font-weightandfont-style. - Ensure the node belongs to the same document whose
document.fontsset you awaited. An iframe has its own document and its own font set.
Historical html2canvas reports describe fallback metrics when capture started before webfonts finished loading. They are useful clues for a timing investigation, not proof that every current release mishandles cached fonts.
4. Isolate foreignObjectRendering
foreignObjectRendering is false by default. Compare both modes in a controlled reproduction, but record your html2canvas and Chrome versions because old reports are version-specific.
| Test | What it tells you | Interpretation |
|---|---|---|
Default or false |
Uses html2canvas’s normal DOM/CSS reconstruction. | If the font is correct here, the foreign-object path is implicated. |
true |
Uses an SVG foreign-object route for supported HTML. | Differences may reflect browser security, SVG or CSS support, not font timing. |
const normal = await html2canvas(element, {
foreignObjectRendering: false
});
const foreignObject = await html2canvas(element, {
foreignObjectRendering: true
});
A Chrome 75 report against html2canvas 1.0.0-rc.3 described Google Fonts failing with the option enabled but working when it was removed. Another report, involving Chrome 77 and Firefox 69, described fonts and images being fetched yet absent from the output. Those reports were opened in 2019 and do not establish a current universal Chrome defect. Reproduce with your current versions before choosing a mode.
PC 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 & 11Crashes, 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 minuteRank #3
5. Keep image CORS settings separate from font diagnosis
The useCORS option tells html2canvas to attempt loading images with CORS. Its documented use and FAQ guidance concern cross-origin images, response headers and proxying. It is not a general @font-face switch. Turning it on will not repair a font request that failed, selected the wrong face, or completed after capture.
Investigate a font through the CSS Font Loading API and the font’s own Network request. Handle image failures independently so you do not mistake a missing background image for a font-rendering problem.
6. Reduce the page to a minimal reproduction
When the font is demonstrably ready but output remains wrong, remove variables until the failing feature is obvious.
- Create a small element containing ordinary HTML text, one
@font-facefamily and one weight. - Capture it with
foreignObjectRendering: false. - Add the second weight, then italic, variable-font settings and text effects one at a time.
- Test SVG text separately from HTML text. Older issue reports specifically involved @font-face and SVG text; separating the element type helps identify an SVG limitation.
- Reintroduce external images, filters, transforms and complex layout only after the basic text matches.
This process distinguishes a failed resource from a CSS feature that html2canvas does not implement. Do not promise support for every SVG/font combination without testing the current browser and library versions.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #4
Reliable capture pattern
async function renderInvoice() {
const element = document.querySelector('#invoice');
if (!element) throw new Error('Missing #invoice');
// Request the face actually used by the element.
await document.fonts.load('400 16px "Brand Sans"', 'Invoice preview');
await document.fonts.load('600 16px "Brand Sans"', 'Total €0123456789');
await document.fonts.ready;
const canvas = await html2canvas(element, {
foreignObjectRendering: false,
backgroundColor: '#ffffff'
});
return canvas.toDataURL('image/png');
}
renderInvoice()
.then(dataUrl => {
document.querySelector('#preview').src = dataUrl;
})
.catch(error => {
console.error('Capture failed', error);
});
Capture only after content that changes layout has settled. If your page injects text, toggles a class, or swaps a font after the awaits, wait for that operation and the resulting layout before invoking html2canvas.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting by symptom
| Symptom | Likely cause | Action |
|---|---|---|
| Fallback face and shifted line breaks | Capture raced font loading. | Await document.fonts.load() for the exact face, then document.fonts.ready; inspect the Font request. |
| Only one weight is wrong | The requested weight is missing or being synthesized. | Check the CSS rule and Network response for that weight; load it explicitly. |
check() says true but the brand face is absent |
Fallback can render without the named family. | Use Network and computed styles; do not treat check() as existence proof. |
| Normal mode works, foreign-object mode fails | Renderer-path or SVG/CSS compatibility issue. | Keep foreignObjectRendering: false and test a current library/browser pair. |
| HTML text works, SVG text fails | SVG text or its CSS/font combination is outside the implemented path. | Use a minimal SVG reproduction or convert the design to ordinary HTML where possible. |
| Font request is absent | The stylesheet was not applied, the face is unused, or the element is in another document. | Check stylesheet loading, iframe context, computed styles and explicit load(). |
| Images are missing while text is correct | Cross-origin image policy, not @font-face. | Handle image CORS headers or proxying separately; useCORS applies to images. |
Performance, consistency and version notes
- Font waits are asynchronous and normally cheaper than debugging a capture whose dimensions changed after rendering. Avoid arbitrary sleeps when the Font Loading API can express the condition you need.
- Cache your loaded faces in application state if several captures use the same document; do not repeatedly trigger needless loads.
- Record the html2canvas version, Chrome version, renderer option and font URLs with bug reports. Historical issue behavior from Chrome 75, Chrome 77 or old release candidates cannot be generalized to current builds.
- Use a small reproduction before changing unrelated options. A successful font request does not guarantee that html2canvas implements every CSS property used by the page.
Or skip the browser setup
For a server-side screenshot rather than a canvas reconstructed in the browser, ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP or PDF. It is #1 for screenshot APIs here because it removes consent banners, newsletter popups and chat widgets before capture, bills only clean shots, and has the lowest paid plan.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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}`);
See the ScreenshotNeo documentation for the 63 capture options, including full-page and element captures, device and retina settings, custom CSS and JavaScript, waits, request blocking, cookies and headers, PDFs, caching, signed links, asynchronous jobs, bulk capture and usage reporting. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed; response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info and capture_pdf 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. Create a free ScreenshotNeo account.
Frequently Asked Questions
Does waiting for document.fonts.ready load every font declared in CSS?
No. It waits for fonts used by the document; unused or optional faces may remain unloaded. Request a needed face explicitly with document.fonts.load().
Free tools Windows power users keep installed
One-click scans. No signup required.
Should I enable foreignObjectRendering to improve font fidelity?
Not by default. It is false by default, and historical reports show version-specific failures when enabled. Compare both modes on your current versions and keep the one that reproduces your target reliably.
Can useCORS fix a missing webfont?
No. useCORS is documented for images. Diagnose @font-face through the Font Loading API, Network panel and font response itself.
The Bottom Line
Synchronize the exact font face before capture, verify its request, compare renderer modes, and reduce the page to a minimal case. If the font is loaded yet html2canvas still differs, treat the result as an implementation limitation rather than endlessly changing font timing.
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.

