ScreenshotNeo

BlogHow-to

How to Pass Multiple Div IDs to html2canvas

html2canvas accepts one element per call. Learn how to capture several divs with a wrapper or compose separate canvases.

By the ScreenshotNeo team1 October 20269 min read

Short answer: you cannot pass an array of IDs, an array of elements, or a NodeList directly to html2canvas. The documented API accepts one DOM element per call: html2canvas(element, options?). If the divs belong together, put them in a common wrapper and capture that wrapper. If they are unrelated, capture each div separately and draw the returned canvases onto a destination canvas.

IDs are only selectors. Resolve each ID with document.getElementById() (or a CSS selector), then pass the resulting element to html2canvas. The function is asynchronous and resolves to a canvas.

1. Capture several divs with a shared wrapper

This is the simplest and usually the best solution when you control the markup. Everything inside the wrapper becomes part of the capture, so keep unrelated content outside it.

<div id="capture">
  <div id="summary" class="card">Summary</div>
  <div id="chart" class="card">Chart</div>
  <div id="notes" class="card">Notes</div>
</div>

<button id="save">Save image</button>

<script type="module">
  import html2canvas from 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/+esm';

  document.querySelector('#save').addEventListener('click', async () => {
    const wrapper = document.getElementById('capture');
    if (!wrapper) throw new Error('Missing #capture element');

    const canvas = await html2canvas(wrapper, {
      backgroundColor: '#ffffff',
      scale: window.devicePixelRatio
    });

    const link = document.createElement('a');
    link.download = 'dashboard.png';
    link.href = canvas.toDataURL('image/png');
    link.click();
  });
</script>

The official html2canvas documentation shows the same one-element call and Promise result. Capturing a wrapper preserves the layout between its descendants.

Make the wrapper contain only the intended output

A wrapper capture includes every descendant within its bounds: headings, spacing, hidden-state choices, backgrounds, and any extra controls. Create a dedicated export wrapper when the visible page contains buttons or navigation that should not appear in the image.

<section id="export-area">
  <div id="first">First panel</div>
  <div id="second">Second panel</div>
</section>

<button id="capture-button">Capture</button>

2. Capture unrelated divs with separate calls

When there is no useful common ancestor, resolve each ID and call html2canvas once for each element. The following code ignores IDs that do not exist and waits for all captures to finish.

import html2canvas from 'html2canvas';

const ids = ['first', 'second', 'third'];
const elements = ids
  .map((id) => document.getElementById(id))
  .filter((element) => element instanceof HTMLElement);

if (elements.length !== ids.length) {
  console.warn('One or more requested IDs were not found');
}

const canvases = await Promise.all(
  elements.map((element) => html2canvas(element, {
    backgroundColor: '#fff',
    useCORS: true
  }))
);

canvases.forEach((canvas) => document.body.appendChild(canvas));

This produces one canvas per div. It does not create one combined image automatically. To create one file, choose where each result belongs and compose them with the browser Canvas API.

3. Compose the canvases into one image

This example places the captured divs vertically, with a 24-pixel gap. It calculates the destination size from the actual canvas dimensions, so high-DPI captures remain sharp.

import html2canvas from 'html2canvas';

async function captureIdsVertically(ids, gap = 24) {
  const elements = ids.map((id) => {
    const element = document.getElementById(id);
    if (!element) throw new Error(`Element #${id} was not found`);
    return element;
  });

  const canvases = await Promise.all(
    elements.map((element) => html2canvas(element, {
      backgroundColor: '#ffffff',
      useCORS: true,
      scale: window.devicePixelRatio
    }))
  );

  const width = Math.max(...canvases.map((canvas) => canvas.width));
  const height = canvases.reduce((total, canvas) => total + canvas.height, 0)
    + gap * Math.max(0, canvases.length - 1);

  const output = document.createElement('canvas');
  output.width = width;
  output.height = height;

  const context = output.getContext('2d');
  if (!context) throw new Error('A 2D canvas context is unavailable');
  context.fillStyle = '#ffffff';
  context.fillRect(0, 0, width, height);

  let y = 0;
  for (const canvas of canvases) {
    context.drawImage(canvas, 0, y);
    y += canvas.height + gap;
  }

  return output;
}

const result = await captureIdsVertically(['first', 'second', 'third']);
document.body.appendChild(result);

const download = document.createElement('a');
download.download = 'combined.png';
download.href = result.toDataURL('image/png');
download.click();

Arrange items in a grid

For a grid, calculate a column count and track each tile’s maximum width and height. The key operation is still context.drawImage(canvas, x, y); html2canvas does not decide the composition coordinates.

function composeGrid(canvases, columns = 2, gap = 16) {
  const rows = Math.ceil(canvases.length / columns);
  const columnWidths = Array(columns).fill(0);
  const rowHeights = Array(rows).fill(0);

  canvases.forEach((canvas, index) => {
    const column = index % columns;
    const row = Math.floor(index / columns);
    columnWidths[column] = Math.max(columnWidths[column], canvas.width);
    rowHeights[row] = Math.max(rowHeights[row], canvas.height);
  });

  const width = columnWidths.reduce((a, b) => a + b, 0) + gap * (columns - 1);
  const height = rowHeights.reduce((a, b) => a + b, 0) + gap * (rows - 1);
  const output = document.createElement('canvas');
  output.width = width;
  output.height = height;
  const context = output.getContext('2d');
  if (!context) throw new Error('No 2D context');
  context.fillStyle = '#fff';
  context.fillRect(0, 0, width, height);

  const xOffsets = columnWidths.map((_, column) =>
    columnWidths.slice(0, column).reduce((a, b) => a + b, 0) + gap * column
  );
  const yOffsets = rowHeights.map((_, row) =>
    rowHeights.slice(0, row).reduce((a, b) => a + b, 0) + gap * row
  );

  canvases.forEach((canvas, index) => {
    const column = index % columns;
    const row = Math.floor(index / columns);
    context.drawImage(canvas, xOffsets[column], yOffsets[row]);
  });
  return output;
}

4. Select multiple IDs safely

There is no native document.getElementsById() method because IDs should be unique. Use an array of IDs when the requested elements are known, or use a selector when the elements share a class or data attribute.

const ids = ['first', 'second', 'third'];
const elements = ids.map((id) => document.getElementById(id));

const missing = ids.filter((id, index) => !elements[index]);
if (missing.length) {
  throw new Error(`Missing elements: ${missing.join(', ')}`);
}

const cards = [...document.querySelectorAll('[data-capture-card]')];
const canvases = await Promise.all(cards.map((card) => html2canvas(card)));

Do not pass ids, elements, or cards directly to html2canvas. A collection is not the documented element argument.

5. Useful html2canvas options

Options change rendering, cropping, and resource handling. They do not make multiple unrelated elements a single API argument. See the official configuration reference.

Option Use Example
scale Controls output pixel density. scale: 2
backgroundColor Sets the canvas background; use null for transparency. backgroundColor: '#fff'
useCORS Attempts to load cross-origin images that provide the required CORS headers. useCORS: true
allowTaint Allows cross-origin content to taint the canvas; a tainted canvas cannot be exported. allowTaint: false
logging Enables diagnostic logging. logging: true
x, y, width, height Crops the rendered document to a region. { x: 0, y: 0, width: 800, height: 400 }
windowWidth, windowHeight Sets the virtual viewport used during rendering. windowWidth: 1280
onclone Edits the cloned document before it is rendered. onclone: (doc) => { ... }

scale affects every individual canvas. When composing, use the returned canvas.width and canvas.height, rather than CSS width and height, so the destination canvas matches the actual pixels.

6. Crop one element or region

Cropping is useful when a wrapper contains padding or when you need a fixed rectangle. It still operates on one render target at a time.

const panel = document.getElementById('panel');
const rect = panel.getBoundingClientRect();
const canvas = await html2canvas(panel, {
  x: 0,
  y: 0,
  width: rect.width,
  height: rect.height,
  scrollX: -window.scrollX,
  scrollY: -window.scrollY
});

7. Timing, fonts, images, and dynamic content

Capture only after the content has reached the state you want. Wait for images and fonts, and trigger data rendering before calling html2canvas.

await document.fonts.ready;

const images = [...document.images];
await Promise.all(images.map((image) => {
  if (image.complete) return Promise.resolve();
  return new Promise((resolve) => {
    image.addEventListener('load', resolve, { once: true });
    image.addEventListener('error', resolve, { once: true });
  });
}));

const canvas = await html2canvas(document.getElementById('capture'));

For animations, pause them in an export stylesheet or use onclone to change the cloned document. A separate capture of each div can observe slightly different application states if the page changes between calls; freeze the data first when consistency matters.

8. Common errors and fixes

Symptom Cause Fix
TypeError or a blank result after passing an array The API received a collection instead of one element. Resolve each ID and call html2canvas separately, or capture a common wrapper.
One panel is missing The ID is misspelled, the element is not mounted, or the selector matched nothing. Check every result from getElementById before rendering.
Images are absent The image is still loading or the server does not allow cross-origin use. Wait for images, set useCORS: true, and configure the image server’s CORS headers. A proxy may be required.
SecurityError: Tainted canvases may not be exported A cross-origin resource tainted the canvas. Serve the resource with appropriate CORS headers, use a same-origin copy, or avoid exporting that resource.
Text or layout differs from the browser html2canvas reconstructs a representation from DOM and supported CSS rather than reading browser pixels. Check the library’s documented CSS limitations; simplify unsupported styles or use a browser screenshot service for pixel-level rendering.
Output is blurry The canvas has too few device pixels. Increase scale, commonly to window.devicePixelRatio, while watching memory use.
Out-of-memory or very slow capture Several large canvases at a high scale consume substantial memory. Capture fewer elements at once, lower scale, crop unused space, and release references after composition.
Different panels show different data The application updated during parallel or sequential captures. Freeze the state, await rendering, then capture; use a wrapper when one consistent layout is required.

9. Performance and reliability checklist

  • Prefer one wrapper capture when the desired output already exists as one layout.
  • Use separate calls only for genuinely independent elements.
  • Run independent captures with Promise.all, but limit concurrency for many large elements.
  • Choose the lowest scale that meets your output requirement.
  • Crop large elements and remove off-screen content that does not belong in the image.
  • Wait for fonts, images, charts, and asynchronous data before rendering.
  • Handle rejected Promises and report which ID failed.
  • Keep the composition canvas and intermediate canvases within browser memory limits.

The project describes html2canvas as a DOM-based reconstruction tool, so CSS support and cross-origin resources are practical limits. It is not a guarantee of pixel-identical browser screenshots.

10. Or skip the browser setup

If you need a URL screenshot rather than a canvas reconstructed inside your page, ScreenshotNeo provides a GET endpoint and handles the browser session for you. 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 verdict and billing status in headers. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API docs for all options. A one-call capture looks like this:

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}`);

ScreenshotNeo includes full-page and element capture, custom CSS and JavaScript, device and viewport settings, waits, headers, cookies, blocking rules, caching, signed links, PDFs, asynchronous jobs, bulk capture, and a usage API. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

11. FAQ

Can I pass multiple IDs as a comma-separated string?

No. Resolve the IDs yourself, then make one call per element or capture a wrapper.

Can html2canvas accept a NodeList?

Not as the documented element argument. Convert the NodeList to an array and call html2canvas for each item.

Does cropping combine multiple divs?

No. Cropping limits one render region. It does not turn separate DOM elements into one input.

Should I use a wrapper or compose canvases?

Use a wrapper when the elements form one layout. Compose separate canvases when the elements are unrelated or must be rearranged.

Why does my result differ from a real screenshot?

html2canvas rebuilds the image from DOM information and supported CSS. Unsupported styles and cross-origin resources can change the result.