Short answer: jsPDF can only add what html2canvas successfully rendered and what the browser allows your script to read. A cross-origin image without a valid Access-Control-Allow-Origin response is skipped or taints the canvas; a redirect can silently move an image to a different origin; unloaded assets, unsupported CSS, and oversized canvases can produce blank or clipped output. First verify that the canvas contains the image and can be converted with toDataURL(). Only then hand the data to jsPDF.addImage().
Use this diagnosis order
Do not start by changing jsPDF options. The failure normally occurs earlier, while html2canvas reconstructs the DOM. Work through these checks in order:
- Check the image response. In DevTools Network, open each image request, inspect the final response (after redirects), and look for a usable
Access-Control-Allow-Originheader. - Check the canvas. Log its width and height, then call
canvas.toDataURL('image/png'). ASecurityErrormeans the canvas is tainted; zero dimensions or an empty result points to capture or loading problems. - Check the jsPDF input. Pass a valid data URL, canvas, image element, byte array, or RGBA object to
addImage, with a supported format and sensible coordinates. - Check timing and size. Wait for images and fonts, and set capture dimensions from the element’s actual scroll size rather than the current viewport.
- Check feature support. Cross-origin iframes and CSS that html2canvas cannot reconstruct will not become pixels merely because the browser displays them.
Cross-origin images: the most common cause
Why the browser blocks the handoff
html2canvas draws DOM content into a new canvas. If an image comes from another origin, drawing it can taint that canvas. With the default allowTaint:false, html2canvas avoids images that would make the canvas unreadable. If a tainted image is drawn anyway, browser security prevents scripts from exporting the pixels, so the later toDataURL() call fails.
The image server must opt in with Access-Control-Allow-Origin. The value must allow the origin running your page (or use an appropriate wildcard for non-credentialed requests). A header on your HTML page does not grant permission to an image fetched from a different server.
#1 Best Overall
- EDIT text, images & designs in PDF documents. ORGANIZE PDFs. Convert PDFs to Word, Excel & ePub.
- READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.
- CREATE, COMBINE, SCAN and COMPRESS PDFs
- FILL forms & Digitally Sign PDFs. PROTECT and Encrypt PDFs
- LIFETIME License for 1 Windows PC or Laptop. 5GB MobiDrive Cloud Storage Included.
What useCORS:true actually does
useCORS:true tells html2canvas to attempt a CORS-enabled image request. It is not a bypass and cannot manufacture a missing response header. If the server does not return a permitted header, the image still cannot be safely exported.
const canvas = await html2canvas(document.querySelector('#invoice'), {
useCORS: true,
allowTaint: false
});
Redirects can change the origin
A URL that appears same-origin may redirect to a CDN or image host. Inspect the final request and response, not only the URL in your markup. A redirect can prevent html2canvas from taking the CORS path you expected. Fix the redirect, serve the asset from your application origin, or configure the final host to return the required header.
Choose a CORS remedy
| Fix path | Requires control of image server? | Best use | Main limitation |
|---|---|---|---|
| Same-origin hosting | Yes | First-party assets | You must move or serve the files from the application origin. |
useCORS:true |
Yes, including the correct response header | A CDN or object store that supports CORS | It does not override browser policy; redirects and credentials still need care. |
| html2canvas proxy | No, but you need a controlled proxy | Third-party images whose server cannot be changed | Adds infrastructure and must fetch only permitted URLs to avoid an open-proxy risk. |
| Inline/base64 data URI | No request during capture | Small, preprocessed assets | Requires an earlier fetch/conversion step and increases document size. |
Do not rely on allowTaint:true as a solution. It permits drawing content that the browser will not let your script read; the normal canvas-to-data-URL handoff can still fail.
Prove whether html2canvas produced a usable canvas
Run these checks immediately after the promise resolves, before creating the PDF:
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 & 11Rank #2
- Edit PDFs with Ease. Modify text, images, and layouts directly within your PDF documents.
- Convert & Organize. Export PDFs to Word, Excel, or ePub, and organize files with ease.
- Read & Annotate. Enjoy intuitive reading modes and powerful tools to comment, highlight, and mark up PDFs.
- Create & Manage PDFs. Create new PDFs, combine multiple files, scan documents, and compress for easy sharing.
- Fill & Sign Forms. Complete forms and digitally sign documents with secure e-signature tools.
const canvas = await html2canvas(element, {
useCORS: true,
onError: err => console.warn('html2canvas resource failed', err)
});
console.log({ width: canvas.width, height: canvas.height });
try {
const png = canvas.toDataURL('image/png');
console.log('data URL length:', png.length);
} catch (error) {
console.error('Canvas is not readable:', error);
}
- Width or height is zero: the target is hidden, not laid out, or the capture dimensions are wrong.
- A
SecurityError: an inaccessible cross-origin resource tainted the canvas. - A very short or visually empty data URL: the capture ran before content loaded, or the source element has no painted pixels.
- A readable canvas that visibly lacks one image: inspect that image’s request, redirect target, and CORS headers; jsPDF has not run yet.
Hand the image to jsPDF correctly
jsPDF’s addImage accepts a base64 data URL, an HTMLCanvasElement, an image element, a byte array, or RGBA data. The format, coordinates, and dimensions must be valid. A canvas that cannot be read cannot be repaired by jsPDF: jsPDF’s HTML pipeline also calls canvas.toDataURL() before creating an image.
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ unit: 'mm', format: 'a4' });
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const margin = 10;
const drawWidth = pageWidth - margin * 2;
const drawHeight = (canvas.height * drawWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', margin, margin, drawWidth,
Math.min(drawHeight, pageHeight - margin * 2));
pdf.save('capture.pdf');
Use 'JPEG' only for JPEG data and keep the dimensions finite and greater than zero. If the image is taller than one page, split it deliberately or add additional pages rather than squeezing a large capture into a single page.
A complete browser example
This example waits for image resources and fonts, captures the full scroll area, reports failed resources, verifies readability, and then creates a PDF.
async function waitForImages(root) {
const images = [...root.querySelectorAll('img')];
await Promise.all(images.map(img => {
if (img.complete) return Promise.resolve();
return new Promise(resolve => {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
});
}));
const failed = images.filter(img => !img.complete || img.naturalWidth === 0);
if (failed.length) {
console.warn('Images that did not load:', failed.map(img => img.src));
}
}
async function makePdf() {
const element = document.querySelector('#invoice');
if (!element) throw new Error('Missing #invoice');
await waitForImages(element);
if (document.fonts?.ready) await document.fonts.ready;
const canvas = await html2canvas(element, {
useCORS: true,
allowTaint: false,
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight,
imageTimeout: 15000,
onError: error => console.warn('html2canvas resource failed', error)
});
if (!canvas.width || !canvas.height) {
throw new Error(`Invalid canvas size: ${canvas.width}x${canvas.height}`);
}
let imgData;
try {
imgData = canvas.toDataURL('image/png');
} catch (error) {
throw new Error('Canvas is tainted or otherwise unreadable');
}
const pdf = new jsPDF({ unit: 'mm', format: 'a4' });
const margin = 10;
const width = pdf.internal.pageSize.getWidth() - margin * 2;
const height = canvas.height * width / canvas.width;
pdf.addImage(imgData, 'PNG', margin, margin, width, height);
pdf.save('invoice.pdf');
}
makePdf().catch(console.error);
The imageTimeout value above is 15,000 milliseconds, the documented library default. It is a timeout, not a guarantee that a slow or blocked image will render.
Rank #3
- Create and edit PDFs. Collaborate with ease. E-sign documents and collect signatures. Get everything done in one app, wherever you go.
- Edit text and images without jumping to another app.
- E-sign documents or request e-signatures on any device. Recipients don’t need to log in to e-sign.
- Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
- Share PDFs for collaboration. Commenting features make it easy for reviewers to comment, mark up, and annotate.
Blank or clipped output caused by canvas dimensions
Browsers impose maximum canvas dimensions and total pixel-area limits. When a page is exceptionally long or wide, html2canvas can return a blank or partial bitmap instead of throwing a useful error. Capture a smaller region, split a long document into sections, or reduce scale. Set windowWidth and windowHeight from the element’s scroll dimensions so responsive CSS does not reflow the page to the wrong viewport.
For a full-page capture, inspect element.scrollWidth and element.scrollHeight. For a viewport capture, use the actual viewport values intentionally. Do not confuse a clipped PDF with a missing image: if the image appears in the canvas but is outside the page rectangle, adjust the PDF placement or add pages.
Loading state, fonts, and framework timing
An <img> element can exist in the DOM before its pixels are available. Check both img.complete and img.naturalWidth > 0. Wait for document.fonts.ready when web fonts affect layout; otherwise text and boxes can shift between the live page and the capture.
React, Vue, and other frameworks may render a placeholder first and replace it asynchronously. Trigger the capture after the state that displays the final image has committed, not directly in the same event that changes state. The onError callback and Network panel help separate a failed resource from a race condition.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Rank #4
- Perfect Adobe Acrobat Pro alternative – lifetime license for Windows 10 and 11.
- EDIT text, images, pages, hyperlinks, designs in PDF documents. ORGANIZE PDFs.
- READ and Comment on PDFs – Intuitive reading modes & document commenting and mark up tools!
- CREATE, COMBINE, SCAN and COMPRESS PDFs.
- FILL forms & Digitally Sign PDFs. Work with Digital certificates
Content html2canvas cannot reproduce
html2canvas reconstructs the DOM and the CSS features it supports; it is not a pixel-perfect browser screenshot. A browser-visible effect can therefore be absent from the canvas. Cross-origin iframes are a hard boundary because their contentDocument is inaccessible. Video frames, plugins, and unsupported CSS need a different capture strategy or a static substitute that your page controls.
Troubleshooting by symptom
| Symptom | Likely cause | Fix |
|---|---|---|
| Image missing, rest of canvas correct | Cross-origin response lacks permission, or image redirected to another origin | Inspect the final response; add the correct CORS header, serve it same-origin, use a controlled proxy, or inline the asset. |
SecurityError from toDataURL |
Tainted canvas | Remove the offending resource or make it CORS-readable. allowTaint:true does not make export readable. |
| Canvas is zero by zero | Hidden target, display timing, or invalid dimensions | Capture a laid-out element and log its bounding and scroll dimensions. |
| Entire PDF blank | Capture occurred before rendering, canvas exceeded browser limits, or invalid image data was passed | Wait for images/fonts, test toDataURL, reduce capture size, and verify addImage arguments. |
| Only the top portion appears | Viewport dimensions are smaller than the content or PDF height is clipped | Set windowWidth/windowHeight to scroll dimensions and paginate a tall canvas. |
| Output varies between full, half, and blank | Framework or image-loading race | Trigger after final render, await image completion and fonts, and log failed resources. |
| Image loads in the page but not in the capture | Unsupported format/feature, blocked request, or inaccessible iframe | Check Network, simplify the source to a normal image, and keep cross-origin frame content out of the target. |
Performance, reliability, and security considerations
- Pixel cost: a large width, height, and scale multiply memory use. Capture only the region you need and paginate long content.
- Network determinism: self-hosted or same-origin assets are easier to make repeatable than third-party URLs that redirect or change headers.
- Proxy safety: if you implement a proxy, restrict allowed destinations, validate schemes, enforce response-size and timeout limits, and avoid exposing internal network addresses.
- Diagnostics: keep the canvas-size log,
toDataURLtest, Network response headers, andonErroroutput together; that evidence identifies the failing layer quickly. - PDF size: PNG preserves sharp text and transparency but can be large. JPEG can reduce size for photographic content when transparency is not needed.
Or skip the browser setup
If your goal is a clean screenshot or PDF rather than reproducing a DOM canvas, ScreenshotNeo makes the capture request from its screenshot service. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Use the API documented at https://screenshotneo.com/docs/:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
It also supports full-page and selector captures, device presets and custom viewports, retina scale, dark mode, custom CSS and JavaScript, click and wait actions, resource blocking, headers/cookies/user agents, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and PDF options such as paper size, margins, landscape, and page ranges. Every feature is included on every plan. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →FAQ
Can I inspect an image’s CORS header from JavaScript?
Use the browser’s Network panel for the authoritative response, including redirects and blocked requests. A script running on your page cannot bypass the browser’s cross-origin rules to reveal an otherwise inaccessible response.
Best Value
- ALL-IN-ONE SOLUTION – read, edit, convert, merge and protect your PDF files
- MAXIMUM FUNCIONALITY – create interactive forms, compare PDFs, bates numbering, find and replace text or colors, convert documents, OCR engine, comment, highlight, fill out and print forms, document protection and others
- EASY TO INSTALL AND USE – well-structured user-interface, in-program instructions, free tech support whenever you need it
- GREAT VALUE FOR MONEY - why spend a fortune if you can have maximum functionality at a reasonable price - this also fits the requirements of companies very well
Should I convert every image to base64?
No. Inline only small or preprocessed assets when it is practical. Large data URIs increase HTML and memory usage; correctly configured same-origin or CORS-enabled URLs are usually simpler.
Why does the live page look right while the PDF is wrong?
The live page is painted by the browser with privileges that do not transfer to a script-readable canvas. html2canvas must reconstruct supported DOM/CSS and obey origin policy, so visual equivalence is not guaranteed.
When should I replace html2canvas?
Use another capture approach when you need cross-origin iframe pixels, unsupported browser effects, or a pixel-perfect browser rendering. html2canvas is appropriate when the content is controllable DOM/CSS and its resources can be made readable.
Recommended Free Tools
Frequently Asked Questions
Does setting allowTaint:true fix missing images in jsPDF?
No. It can allow an image to be drawn while leaving the canvas unreadable, so toDataURL() and the jsPDF handoff may still fail.
What is the fastest way to tell whether jsPDF is at fault?
Call canvas.toDataURL('image/png') before invoking jsPDF. If that conversion fails or the canvas already lacks the image, the problem is in loading, origin policy, sizing, or html2canvas—not in addImage().
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.

