ScreenshotNeo

BlogHow-to

How to Render HTML as an Image in PhoneGap Build

Use html2canvas to turn PhoneGap or Cordova HTML into PNG or JPEG, handle cross-origin assets, and understand when native capture is required.

By the ScreenshotNeo team1 October 202610 min read

Direct answer: In a PhoneGap or Cordova app, use html2canvas to reconstruct a DOM element as a canvas, then export that canvas as PNG or JPEG. This captures content from the DOM and CSS; it does not capture the actual WebView pixels. If you need a pixel-accurate image of what the user sees, investigate a native or platform-specific capture API for each target platform and test it with the WebView and build configuration your app actually uses.

1. What html2canvas does

html2canvas walks through the selected element, reads its DOM and styles, and draws a canvas representation. The result can be exported with toDataURL() or toBlob(). The project documentation describes this as a DOM-based reconstruction rather than an actual screenshot, so unsupported CSS, inaccessible resources, and browser rendering differences can change the output.

That distinction determines which approach to choose:

Requirement Best starting point Reason
Render a card, invoice, chart, or selected page section html2canvas JavaScript API, portable across WebViews, and exports directly from a canvas.
Render a long DOM page html2canvas with explicit dimensions You control the element, viewport, scale, and export format.
Capture exactly what the user sees, including WebView pixels Native/platform capture API DOM reconstruction cannot guarantee pixel identity.
Capture content hosted outside your app Server-side screenshot service Browser security rules may prevent a local canvas from reading cross-origin content.

2. Add html2canvas to the app

Install html2canvas with your package manager and bundle it with the app, or include a pinned release in the WebView. Keep the dependency local for offline or restricted environments. The exact PhoneGap Build lifecycle and supported plugin/version matrix are not established by the available research, so verify the build service and target WebView versions used by your project.

npm install html2canvas

Then import it from your application code:

import html2canvas from 'html2canvas';

If your build uses a script bundle instead of ES modules, load the bundled script before your application code and call the global html2canvas function.

3. Minimal working example

This example renders one element after the page and its images have loaded, then creates a downloadable PNG. It is ordinary browser JavaScript and can run inside a Cordova or PhoneGap WebView.

<article id="capture" class="receipt">
  <h1>Order 1042</h1>
  <p>Two notebooks</p>
  <strong>$18.00</strong>
</article>
<button id="save" type="button">Save image</button>

<script type="module">
  import html2canvas from 'html2canvas';

  const button = document.querySelector('#save');
  const target = document.querySelector('#capture');

  button.addEventListener('click', async () => {
    button.disabled = true;
    try {
      const canvas = await html2canvas(target, {
        backgroundColor: '#ffffff',
        useCORS: true,
        scale: window.devicePixelRatio || 1
      });

      const imageData = canvas.toDataURL('image/png');
      const link = document.createElement('a');
      link.href = imageData;
      link.download = 'order-1042.png';
      link.click();
    } catch (error) {
      console.error('Could not render image', error);
      alert('The image could not be rendered. Check image origins and browser logs.');
    } finally {
      button.disabled = false;
    }
  });
</script>

The documented basic form is:

const canvas = await html2canvas(document.querySelector('#capture'));
const imageData = canvas.toDataURL('image/png');

4. A reusable capture function

For production code, keep rendering and encoding separate. Returning a Blob avoids putting a large base64 string in memory and makes it easier to pass the result to a file or sharing flow.

import html2canvas from 'html2canvas';

export async function renderElementToBlob(element, options = {}) {
  if (!(element instanceof HTMLElement)) {
    throw new TypeError('renderElementToBlob expects an HTMLElement');
  }

  const canvas = await html2canvas(element, {
    backgroundColor: '#ffffff',
    useCORS: true,
    logging: false,
    scale: Math.min(window.devicePixelRatio || 1, 2),
    ...options
  });

  return new Promise((resolve, reject) => {
    canvas.toBlob(blob => {
      if (blob) resolve(blob);
      else reject(new Error('Canvas encoding returned no Blob'));
    }, 'image/png');
  });
}

const blob = await renderElementToBlob(document.querySelector('#capture'));
const objectUrl = URL.createObjectURL(blob);
const preview = document.querySelector('#preview');
preview.src = objectUrl;
preview.onload = () => URL.revokeObjectURL(objectUrl);

5. Options that matter

Pass an options object as the second argument to html2canvas(element, options). The following options are the ones developers most often need; consult the official configuration reference for the complete list and current defaults.

Option Use Important edge case
backgroundColor Sets the canvas background, such as '#fff'. Use null for transparency when the browser and export format support it.
scale Controls output pixel density. A device-pixel-ratio value gives sharper images. Large scales multiply width, height, memory, and encoding time.
useCORS Attempts to load images with CORS enabled. The image server must send an appropriate Access-Control-Allow-Origin header.
allowTaint Allows drawing some cross-origin resources without reading them back. A tainted canvas cannot reliably be exported with toDataURL() or toBlob().
logging Enables diagnostic logging. Turn it off after troubleshooting.
width, height Override the rendered element dimensions. Set both when producing a predictable document size.
windowWidth, windowHeight Set the virtual viewport used while rendering. Responsive CSS may select a different breakpoint than the visible WebView.
x, y Choose an offset within the element. Useful for a crop, but verify that the offset matches the element’s coordinate system.
scrollX, scrollY Control scroll offsets used for fixed-position content. Fixed headers can appear differently if these values do not match the intended view.
foreignObjectRendering Attempts an SVG foreignObject rendering path where supported. Support varies by WebView and can make output less portable.

Transparent PNG

const canvas = await html2canvas(target, {
  backgroundColor: null,
  scale: 2
});
const png = canvas.toDataURL('image/png');

JPEG output

const canvas = await html2canvas(target, {
  backgroundColor: '#ffffff'
});
const jpeg = canvas.toDataURL('image/jpeg', 0.9);

JPEG has no transparency. Set a solid background before encoding, and choose a quality value appropriate for the file size and text sharpness you need.

6. Wait for fonts, images, and app data

Call html2canvas only after the content is ready. A framework render can finish before web fonts or remote images have loaded.

await document.fonts?.ready;

const images = Array.from(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.querySelector('#capture'));

The image error handler deliberately resolves so one broken decorative image does not prevent the rest of the document from rendering. If an image is required, track failures and stop with a user-facing error instead.

7. Cross-origin images and iframes

Canvas security is the most common reason an export fails or content disappears. An image hosted on another origin must be served with CORS headers and loaded in a way that permits CORS. useCORS: true asks the browser to use that mode; it cannot add permission that the image server does not grant.

  • Prefer same-origin assets bundled with the app or served from an endpoint you control.
  • For remote images, configure the server’s Access-Control-Allow-Origin policy and test the exact URL from the WebView.
  • Do not assume a cross-origin iframe can be read. Browser security prevents access to its DOM unless it is same-origin or cooperates through an application design that does not require DOM access.
  • If the canvas becomes tainted, exporting with toDataURL() or toBlob() can throw a security error.

See the html2canvas FAQ for the project’s documented cross-origin limitations and CSS support caveats.

8. Capturing a full page or a selected element

Pass the element you want. Capturing document.body is convenient, but it can include app navigation, hidden overflow, and unexpectedly large dimensions.

const cardCanvas = await html2canvas(document.querySelector('.card'));
const pageCanvas = await html2canvas(document.body, {
  width: document.documentElement.scrollWidth,
  height: document.documentElement.scrollHeight,
  windowWidth: document.documentElement.scrollWidth,
  windowHeight: document.documentElement.scrollHeight
});

For long pages, split the document into sections if the target device has limited memory. A single very tall canvas can exceed platform bitmap limits even when the DOM itself displays correctly.

9. Saving and sharing in a Cordova app

html2canvas only creates the canvas. Storage, sharing, and permissions are separate Cordova concerns. Convert the canvas to a Blob or data URL, then pass it to the file or sharing plugin selected for your application. Keep that integration isolated so changing the capture library does not change your storage flow.

const canvas = await html2canvas(target);
const dataUrl = canvas.toDataURL('image/png');

// Pass dataUrl or a Blob to the file/share mechanism used by your app.
// The correct plugin, destination, and permissions depend on the target OS.

The Cordova camera plugin is for camera and image handling; its documentation does not make it a general WebView screenshot API. A camera plugin is therefore not a substitute for html2canvas or a native WebView capture API.

10. When native capture is the better choice

Use a platform capture API when the requirement is “the exact pixels visible in the WebView,” including browser-rendered effects that html2canvas cannot reproduce. This usually means platform-specific code or a plugin, and compatibility must be checked against each target OS, WebView version, and build configuration.

FastCanvas is not a general DOM capture replacement. Its documented capture method saves its own canvas surface, which sits over HTML and cannot be combined with arbitrary DOM elements. The repository was archived on 2026-04-02, so treat it as a historical option for its own rendering surface rather than a current solution for HTML-to-image conversion.

11. Performance and reliability checklist

  • Capture only the smallest element that contains the required content.
  • Set an explicit scale; cap it on low-memory devices.
  • Wait for fonts, images, and asynchronous application data before rendering.
  • Use same-origin or correctly CORS-enabled images.
  • Prefer toBlob() for large files instead of holding a base64 string.
  • Release preview object URLs with URL.revokeObjectURL().
  • Disable the capture button while a render is running to avoid concurrent large canvases.
  • Test the exact Android and iOS WebViews, screen sizes, fonts, and CSS used by the shipped app.
  • Measure long-page dimensions and split captures if bitmap allocation fails.

12. Troubleshooting

Symptom Likely cause Fix
Output differs from the visible page DOM reconstruction does not support every CSS feature. Simplify unsupported styles, try the documented rendering options, or use native capture for pixel fidelity.
Remote images are missing Image origin blocks CORS or the request failed. Serve the asset with CORS headers, set useCORS: true, or proxy the asset through an origin you control.
SecurityError during export The canvas is tainted by an inaccessible resource. Remove or correctly CORS-enable the resource; do not rely on allowTaint when you need to read pixels.
Text uses a fallback font Capture started before web fonts finished loading. Await document.fonts.ready and confirm the font request succeeds.
Blank or partially rendered output Capture ran before framework data or images were ready. Wait for the application-ready condition and all required image loads.
Capture is too large or crashes the WebView Canvas width, height, or scale is too high. Reduce scale, capture a smaller element, set explicit dimensions, or split the page.
Fixed elements are in the wrong place Virtual and visible scroll positions differ. Set scrollX/scrollY deliberately and test the target WebView.
Cross-origin iframe content is absent Browser same-origin policy blocks DOM access. Redesign the content boundary, use a same-origin frame, or capture through a service that can load the page separately.
Download works on desktop but not on a phone WebView download behavior differs from a desktop browser. Use the app’s file and share flow with a Blob or data URL instead of relying on an anchor download.

13. Or skip the browser setup

ScreenshotNeo provides a website screenshot API when the page is easier to render outside the PhoneGap WebView. One GET request returns PNG, JPEG, WebP, or PDF. It removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the verdict and billing status in headers. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents.

Read the ScreenshotNeo API documentation for all options. The basic request is:

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 file = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', file);

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

14. FAQ

Does html2canvas take a real screenshot?

No. It reconstructs the selected DOM from information available to the page. The result can differ from the pixels shown by the WebView.

Can it render an entire external website inside PhoneGap?

Only when the content is accessible to the WebView and its resources satisfy browser security rules. Cross-origin frames and images may be unavailable.

Which format should I use?

Use PNG for text, transparency, and lossless UI graphics. Use JPEG for photographic content when a smaller file is more important than transparency.

Is a Cordova camera plugin required?

No. html2canvas creates the image in JavaScript. A separate file or sharing integration is needed only if the app must save or share the result.

How do I guarantee identical output on every device?

You cannot guarantee that with DOM reconstruction alone. Pin fonts and assets, control dimensions, test each target WebView, and use a platform capture API when pixel identity is required.