ScreenshotNeo

BlogHow-to

How to Replace and Remove Previous html2canvas Canvases

Replace html2canvas output safely, remove only the canvas you own, prevent stale renders, reuse nodes, and fix CORS and cleanup issues.

By the ScreenshotNeo team1 October 20267 min read

Direct answer: html2canvas(element, options) returns a Promise that resolves to an HTMLCanvasElement. Your code decides where that canvas is inserted, so your code must remove or replace the previous output. Keep a reference to the old canvas, scope cleanup to a dedicated host or marker, await the next render, and guard against stale asynchronous completions.

The library’s removeContainer option cleans temporary cloned DOM nodes created during rendering. It does not remove a canvas that your application appended to the document. See the official getting-started documentation and configuration reference.

1. Replace the previous canvas in a dedicated host

A dedicated host is the safest default because it cannot remove charts, signatures, games, or other canvases elsewhere on the page.

<button id="render" type="button">Render preview</button>
<section id="source">
  <h1>Invoice preview</h1>
  <p>This element is captured by html2canvas.</p>
</section>
<div id="preview" aria-live="polite"></div>

<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script>
  const source = document.querySelector('#source');
  const host = document.querySelector('#preview');
  const button = document.querySelector('#render');
  let previousCanvas = null;
  let renderSerial = 0;

  async function replacePreview() {
    const serial = ++renderSerial;
    button.disabled = true;

    try {
      const nextCanvas = await html2canvas(source, {
        removeContainer: true
      });

      // A newer request may have started while this one was rendering.
      if (serial !== renderSerial) return;

      if (previousCanvas?.isConnected) {
        previousCanvas.remove();
      }

      host.append(nextCanvas);
      previousCanvas = nextCanvas;
    } finally {
      if (serial === renderSerial) button.disabled = false;
    }
  }

  button.addEventListener('click', replacePreview);
</script>

The reference makes removal deterministic. isConnected prevents errors if another part of your application already detached the node. The serial guard prevents an older Promise from replacing a newer result.

2. Mark generated canvases when the host is shared

If several components render into one container, mark only the canvases owned by this feature. Do not use document.querySelectorAll('canvas') across the whole document.

async function replaceMarkedPreview(source, host) {
  host.querySelector('canvas[data-html2canvas-output]')?.remove();

  const nextCanvas = await html2canvas(source, {
    removeContainer: true
  });

  nextCanvas.dataset.html2canvasOutput = 'true';
  host.append(nextCanvas);
  return nextCanvas;
}

A class works too:

host.querySelector('canvas.html2canvas-output')?.remove();
const next = await html2canvas(source);
next.classList.add('html2canvas-output');
host.append(next);

3. Reuse an existing canvas node

The configuration includes a canvas option: an existing canvas element can be supplied as the drawing target. This is useful when other code stores the node, observes it, or depends on stable DOM identity.

const source = document.querySelector('#source');
const output = document.querySelector('#previewCanvas');

await html2canvas(source, {
  canvas: output,
  removeContainer: true
});

With this approach, html2canvas draws into the application-owned node. Confirm the dimensions and context behavior required by the rest of your code before switching from a newly returned canvas.

4. Prevent older renders from winning

html2canvas is asynchronous and documents a Promise result, but it does not document cancellation. If a user changes settings quickly, two renders can finish out of order. Either serialize requests or ignore stale completions.

Serialize captures

let renderQueue = Promise.resolve();

function queuePreview(source, host) {
  renderQueue = renderQueue.then(async () => {
    const next = await html2canvas(source);
    host.querySelector('canvas[data-html2canvas-output]')?.remove();
    next.dataset.html2canvasOutput = 'true';
    host.append(next);
  });

  return renderQueue;
}

Keep only the newest result

let newestRequest = 0;

async function renderNewest(source, host) {
  const requestId = ++newestRequest;
  const next = await html2canvas(source);

  if (requestId !== newestRequest) {
    // This result is stale. It was never inserted, so nothing needs removing.
    return;
  }

  host.querySelector('canvas[data-html2canvas-output]')?.remove();
  next.dataset.html2canvasOutput = 'true';
  host.append(next);
}

5. Understand removeContainer

removeContainer defaults to true. It controls cleanup of the cloned DOM container that html2canvas creates temporarily while rendering. It does not know which canvas your application appended, and it does not remove that output node. Leave it enabled unless you have a specific reason to inspect the temporary clone.

const canvas = await html2canvas(source, {
  removeContainer: true
});

// Application-owned output cleanup:
oldCanvas?.remove();
host.append(canvas);

6. Complete replacement helper

This helper handles a missing host, stale results, replacement, and optional html2canvas settings.

export function createCanvasReplacer({ source, host, options = {} }) {
  let previousCanvas = null;
  let serial = 0;

  return async function render() {
    const current = ++serial;
    const nextCanvas = await html2canvas(source, {
      removeContainer: true,
      ...options
    });

    if (current !== serial) return null;
    if (!host?.isConnected) {
      throw new Error('The canvas host is no longer connected');
    }

    previousCanvas?.remove();
    nextCanvas.dataset.html2canvasOutput = 'true';
    host.replaceChildren(nextCanvas);
    previousCanvas = nextCanvas;
    return nextCanvas;
  };
}

const render = createCanvasReplacer({
  source: document.querySelector('#source'),
  host: document.querySelector('#preview'),
  options: { scale: 2 }
});

render();

7. Rendering options that affect replacement

Option Use Replacement implication
canvas Draw into an existing canvas Preserves node identity; you still own its lifecycle
removeContainer Remove temporary cloned DOM Does not remove your appended output canvas
useCORS Request cross-origin images with CORS Helps keep the resulting bitmap readable when the server permits it
proxy Route image loading through a proxy Useful when assets cannot be requested directly with CORS
allowTaint Allow images that taint the canvas The canvas may render but become unreadable for export or pixel access
scale Control output pixel density Higher values increase memory, render time, and replacement frequency cost
backgroundColor Set the output background Use a stable color when transparent output is not desired

These controls are documented in the official configuration reference. html2canvas reconstructs a page from DOM and styles; it is not a native, pixel-perfect browser screenshot engine.

8. Cross-origin images and unreadable canvases

A canvas can display an image and still be blocked when your code later calls toDataURL(), toBlob(), or reads pixels. Cross-origin images can taint the bitmap under browser security rules.

const canvas = await html2canvas(source, {
  useCORS: true,
  proxy: 'https://your-approved-image-proxy.example/canvas',
  allowTaint: false
});

canvas.toBlob(blob => {
  if (!blob) {
    console.error('Canvas could not be exported; check image CORS and proxy settings.');
    return;
  }
  // upload or download blob
}, 'image/png');

Use useCORS only when the image server sends an appropriate CORS header. A proxy must be controlled and configured for your application. Setting allowTaint: true can permit rendering but is unsuitable when you need a readable export.

9. Common errors and fixes

Symptom Cause Fix
Every click adds another canvas The new result is appended without removing the old one Keep a reference, use replaceChildren, or remove a marked output first
Charts or signatures disappear A broad selector removed unrelated canvases Use a dedicated host or data-html2canvas-output marker
removeContainer did not remove the visible canvas It cleans temporary clones, not application output Remove the appended node yourself
Old content replaces newer content Promises completed out of order Serialize requests or add a request serial guard
SecurityError during export A cross-origin image tainted the canvas Use same-origin assets, useCORS with server permission, or a proxy; avoid allowTaint when exporting
Nothing is inserted The host selector returned null or the host was unmounted Validate selectors before capture and check host.isConnected
Output is blurry or huge Scale, device pixel ratio, or source dimensions are high Set an intentional scale, limit capture size, and release discarded canvases
Memory grows after many renders Old canvases remain referenced or attached Detach old nodes, clear application references, and avoid retaining large data URLs

10. Performance and reliability checklist

  • Capture only the element you need instead of a large document.
  • Use a dedicated output host and replace in one DOM operation.
  • Serialize captures when every result matters; otherwise ignore stale results.
  • Choose a deliberate scale; higher resolution consumes more memory.
  • Prefer toBlob() over large base64 data URLs for uploads.
  • Wait until fonts, images, and dynamic content are ready before calling html2canvas.
  • Handle a component unmounting while a Promise is pending.
  • Keep CORS and proxy behavior consistent across every image origin.
  • Do not rely on removeContainer for application cleanup.

11. Or skip the browser setup

If you need a URL screenshot rather than a canvas inside the current page, ScreenshotNeo provides a single GET request that returns PNG, JPEG, WebP, or PDF. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, 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. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for all options.

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', bytes);

There is a free plan with 1,000 screenshots per month and no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan. Create a free ScreenshotNeo account.

12. FAQ

Does html2canvas replace the old canvas automatically?

No. It returns a new canvas unless you supply an existing canvas through the canvas option. Insertion and replacement are application code.

Can I remove the last canvas with replaceChildren()?

Yes, when the host contains only output owned by this feature. Use a marker or dedicated host if other children must remain.

Should I set removeContainer to false to keep the result?

No. The result canvas is separate from the temporary clone. Keeping the clone does not replace the need to manage your output node.

Why does a canvas look correct but fail to download?

Most often a cross-origin image tainted it. Configure CORS or a proxy before capture and keep allowTaint disabled when you need exportable pixels.

Is a browser screenshot API better for a server-side URL capture?

For a screenshot of a remote URL, an API removes browser setup and page-cleanup code. ScreenshotNeo also reports whether a response was billed and provides an MCP server for AI-agent workflows.