Frontend Development 15 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Debugging html2canvas: Understanding Canvas Rendering and Fixing Export Bugs

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.

debuggingfrontendcanvasSVGhtml2canvasforeignObject
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.