Debugging html2canvas: Understanding Canvas Rendering and Fixing Export Bugs
This article walks through debugging html2canvas by examining its rendering pipeline, comparing foreignObject and pure canvas methods, analyzing stacking contexts, and presenting practical fixes for export bugs, including configuration tweaks and DOM manipulation techniques.
Problem Reproduction
A colleague reported that images exported with html2canvas were corrupted, showing a red‑boxed rendering issue that had never been seen before. The DOM structure of the problematic page was examined, revealing the cause of the bug.
Debugging Start
Breakpoints were set inside html2canvas to trace its execution. The entry point is the renderElement method, which validates the node, merges options, and builds a rendering context.
renderElement Execution Flow
Validate the incoming node (e.g., Node.ownerDocument , document.defaultView ).
Merge user options with defaults.
Construct a context object that holds caches, logs, etc.
Cloning the Original DOM
The DocumentCloner creates a clone of the original DOM to avoid mutating the page, then renders it into an iframe using window.getComputedStyle to capture all styles.
Canvas Rendering
html2canvas offers two ways to draw a canvas:
Using foreignObject rendering.
Using a pure canvas rendering path.
foreignObject Rendering
foreignObject allows embedding HTML inside an SVG. An example SVG snippet: <svg xmlns="http://www.w3.org/2000/svg"> <foreignObject width="120" height="50"> <body xmlns="http://www.w3.org/1999/xhtml"> <div>Test</div> </body> </foreignObject> </svg> This method works in all modern browsers except IE, but it can cause image loss if external resources are referenced.
Pure Canvas Rendering
The pure canvas path converts the cloned DOM into an ElementContainer tree via parseTree . Each node contains style data, text nodes, child elements, bounds, and flags. The tree is then transformed into stacking contexts, respecting CSS stacking order, and finally painted onto a canvas using standard Canvas APIs (e.g., fillText , drawImage ).
Stacking Context Generation
During rendering, parseStackingContexts builds a hierarchy of StackingContext objects that dictate the painting order (negative z‑index, background, floats, inline, positioned, etc.). The renderStackContent method follows the W3C painting order to draw each layer.
Root Cause Analysis
The bug was traced to the renderNodeContent method, where width and height were incorrectly inherited from the parent node for certain inline elements, causing clipping.
Solutions
Enable foreignObject Rendering
Setting foreignObjectRendering: true in the html2canvas options resolves the clipping, but may drop images because SVG cannot reference external resources. Converting images to base64 mitigates this.
Handle Inline <mark> Tags with Backgrounds
A custom function splits a tall <mark> element into separate characters to avoid line‑break issues:
const handleMarkTag = (ele: HTMLElement) => {
const markElements = ele.querySelectorAll('mark')
for (let sel of markElements) {
const { height } = sel.getBoundingClientRect()
let parentElement = sel.parentElement
while (parentElement?.tagName !== 'P') {
parentElement = parentElement?.parentElement!
}
const { height: parentHeight } = (parentElement as unknown as HTMLElement).getBoundingClientRect()
if (height < parentHeight / 2) continue
const innerText = sel.innerText
const outHtml = sel.outerHTML
let newHtml = ''
innerText.split('').forEach((text) => {
newHtml += outHtml.replace(innerText, text)
})
sel.outerHTML = newHtml
}
}Applying this fix together with foreignObjectRendering: true resolves the export issue.
Summary
The investigation revealed how html2canvas converts HTML to canvas, the role of foreignObject versus pure canvas rendering, the importance of stacking contexts, and practical ways to fix export bugs. The author also reflects on the versatility of canvas in modern web tools.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.