SnapDOMGitHub★ 8K
Live demo

Live element

Component preview
Capture an editable component
1,284+12% vs last week

Editable text and an open shadow root can be captured together.

Result

Nothing captured yet

Edit the title, then run. This uses the real library from unpkg.
Vue 3 Guide

Capture HTML to PNG in Vue 3

A copy-paste single-file component using the Composition API, plus the gotchas to watch out for.

TL;DR

Bind a template ref with ref="cardRef", install @zumer/snapdom, and call snapdom.toPng(cardRef.value) from a button handler. SnapDOM returns an HTMLImageElement ready to append.

1. Install

npm install @zumer/snapdom@latest

Zero runtime dependencies and an ES module entry. In Nuxt, keep imports and capture calls inside the browser boundary.

2. Minimal capture component (Composition API)

<script setup>
import { ref } from 'vue';
import { snapdom } from '@zumer/snapdom';

const cardRef = ref(null);

async function capture() {
  if (!cardRef.value) return;
  const img = await snapdom.toPng(cardRef.value, { scale: 2 });
  document.body.appendChild(img);
}
</script>

<template>
  <div>
    <div ref="cardRef" class="card">
      <h3>Hello SnapDOM</h3>
      <p>This whole div will be captured.</p>
    </div>
    <button @click="capture">Capture</button>
  </div>
</template>

That's the entire component. The template ref name (cardRef) must match the variable declared in the script setup.

Try it live

This is the component above, running on this page. The button fires snapdom.toPng(cardRef.value) and the captured PNG appears below. SnapDOM only sees the DOM node, so the call is identical whether Vue, React or plain HTML rendered it.

Hello SnapDOM

This whole div will be captured.

The captured PNG will appear here.

3. Download instead of appending

async function download() {
  if (!cardRef.value) return;
  const result = await snapdom(cardRef.value);
  await result.download({ format: 'png', filename: 'card' });
}

4. Render a live preview

Capture to a Blob and create an object URL to use as the preview <img> source:

<script setup>
import { ref } from 'vue';
import { snapdom } from '@zumer/snapdom';

const cardRef = ref(null);
const previewUrl = ref('');

async function capture() {
  if (!cardRef.value) return;
  const blob = await snapdom.toBlob(cardRef.value, { format: 'png' });
  previewUrl.value = URL.createObjectURL(blob);
}
</script>

<template>
  <div ref="cardRef">…</div>
  <button @click="capture">Capture</button>
  <img v-if="previewUrl" :src="previewUrl" alt="preview" />
</template>

Revoke the object URL with URL.revokeObjectURL(previewUrl.value) when you're done with it to avoid leaks.

5. Reusable composable

// useSnapdom.js
import { ref } from 'vue';
import { snapdom } from '@zumer/snapdom';

export function useSnapdom(options = {}) {
  const target = ref(null);

  async function capture(format = 'png') {
    if (!target.value) return null;
    const result = await snapdom(target.value, options);
    return result.to(format);
  }

  return { target, capture };
}
<script setup>
import { useSnapdom } from './useSnapdom';

const { target, capture } = useSnapdom({ scale: 2, embedFonts: true });
</script>

<template>
  <div ref="target">…</div>
  <button @click="capture('png')">Capture</button>
</template>

Choose the Vue export target

Keep the ref on the visible card, invoice or dashboard, and keep the export controls outside that root. Use the invoice export recipe for a printable report or the dashboard recipe for several widgets. After nextTick() and asset readiness, the PNG Blob recipe provides a file for your upload endpoint.

Common gotchas

The captured image is blank

Make sure you trigger the capture from a user event (click) or inside onMounted — never directly inside setup(). The element ref is only assigned after the component has been mounted.

Also confirm the captured element isn't v-if'd off the page. SnapDOM needs the element to be in the live DOM tree.

Web fonts aren't being captured

SnapDOM v3.x.x defaults to embedFonts: 'auto' and embeds used web fonts when their files are accessible. Wait for application data and fonts to settle before capture if they can change the layout.

How do I use SnapDOM with the Options API?

Bind ref="cardRef" in the template and access this.$refs.cardRef in a method:

const img = await snapdom.toPng(this.$refs.cardRef);

Does SnapDOM work with Nuxt?

Yes — but only on the client side. Wrap your capture component in <ClientOnly> or import SnapDOM dynamically inside onMounted so it never runs on the server.

Cross-origin images don't render

Use the useProxy option pointing to a CORS proxy: { useProxy: 'https://your-proxy.example.com/' }.

Try it in your project

Drop SnapDOM into your Vue app and capture your first component from a click handler.

Open the demo Installation