はじめに
Webアプリを作っていると、「この画面を画像にして保存したい」という要望、結構あるんですよ。
ダッシュボードのグラフをPNGでエクスポートしたいとか、ユーザーが作ったカードデザインを画像として共有したいとか。個人的には、社内ツールで「この状態をキャプチャして報告書に貼りたい」という話を何度も聞いてきました。
従来はhtml2canvasを使うのが定番だったんですが、正直なところ重いし、スタイルが崩れることも多かった。そこで最近注目しているのがSnapDOM。依存関係ゼロで、速くて、精度も高い。30代になって思うのは、シンプルに動くツールほど信頼できるということ。
SnapDOMとは
SnapDOMは「次世代のDOMキャプチャエンジン」を謳うJavaScriptライブラリです。
DOM要素を自己完結した表現に変換して、SVG、PNG、JPG、WebP、Canvas、Blobなど複数の形式でエクスポートできます。zumerlabというチームが開発していて、MITライセンスで公開されています。
キャッチコピーは「Ultra fast, no dependencies」。標準のWeb APIだけを使った実装で、外部依存がゼロというのが大きな特徴ですね。
特徴・メリット
1. 依存関係ゼロで軽量
これ、意外と重要なんですけど、SnapDOMは外部ライブラリに一切依存していません。標準のWeb APIだけで動く。
npm installしたときに大量の依存関係が入ってくるストレス、ないんですよ。コスパ的に、この軽さは正義です。
2. スタイルの再現精度が高い
フルDOMキャプチャを実現していて、以下のものがしっかり埋め込まれます。
- インラインスタイル
- 疑似要素(::before、::after)
- Webフォント
- CSSカウンター
- line-clamp
html2canvasで「なんか微妙にスタイルが違う」という経験をした人、結構いると思うんですよ。SnapDOMはそこがしっかりしている。
3. 出力形式が豊富
- SVG(ベクター形式で拡大しても綺麗)
- PNG、JPG、WebP(一般的な画像形式)
- Canvas(さらに加工したい場合)
- Blob(サーバーにアップロードしたい場合)
用途に応じて選べるのがいい。
4. プラグインで拡張可能
ベータ機能ですが、プラグインシステムがあります。タイムスタンプオーバーレイを追加したり、ASCII変換したり、独自の出力形式を追加したりできる。
5. 同一オリジンiframe対応
地味に嬉しいのがこれ。iframe内のコンテンツもキャプチャできます(同一オリジンに限る)。
インストール方法
npm / yarn
npm install @zumer/snapdom
# または
yarn add @zumer/snapdom
CDN
<!-- 安定版 -->
<script src="https://unpkg.com/@zumer/snapdom/dist/snapdom.js"></script>
<!-- 開発版(最新機能を試したい場合) -->
<script src="https://unpkg.com/@zumer/snapdom@dev/dist/snapdom.js"></script>
TypeScriptの型定義は内蔵されているので、追加パッケージは不要。時短になる。
基本的な使い方
シンプルなキャプチャ
最もシンプルな使い方。要素を指定してPNGに変換するだけ。
import { snapdom } from '@zumer/snapdom'
// 要素を取得
const element = document.getElementById('capture-target')
// ワンステップでPNG化
const pngImage = await snapdom.toPng(element)
// imgタグに設定
document.body.appendChild(pngImage)
これだけ。直感的でいい。
再利用可能なキャプチャオブジェクト
同じ要素から複数の形式で出力したい場合は、キャプチャオブジェクトを作っておくと効率的。
import { snapdom } from '@zumer/snapdom'
const element = document.getElementById('chart')
// キャプチャオブジェクトを作成
const capture = await snapdom(element)
// 複数の形式で出力
const svg = await capture.toSvg()
const png = await capture.toPng()
const canvas = await capture.toCanvas()
const blob = await capture.toBlob()
const rawSvg = capture.toRaw() // SVG文字列
ファイルダウンロード
ユーザーに画像をダウンロードさせたい場合。
import { snapdom } from '@zumer/snapdom'
const element = document.getElementById('card')
const capture = await snapdom(element)
// JPGでダウンロード
await capture.download({
format: 'jpg',
filename: 'my-card',
quality: 0.9
})
filenameに拡張子は不要。formatに応じて自動で付きます。
オプション設定
細かい調整が必要な場合のオプション。
const capture = await snapdom(element, {
// サイズ調整
scale: 2, // 2倍の解像度(Retinaディスプレイ対応)
width: 800, // 幅を固定
height: 600, // 高さを固定
// 背景色(透明にしたくない場合)
backgroundColor: '#ffffff',
// フォント設定
embedFonts: true, // Webフォントを埋め込む
localFonts: true, // ローカルフォントも含める
iconFonts: ['Font Awesome 6 Free'], // アイコンフォント
// 品質(JPG/WebPの場合)
quality: 0.92,
// 要素のフィルタリング
exclude: '.no-capture', // このクラスの要素を除外
filter: 'remove', // 'hide'または'remove'
})
scale: 2は個人的によく使います。高解像度で出力したい場合に便利。
実践的なユースケース
ダッシュボードのエクスポート
グラフやチャートを画像として保存する機能。
interface ExportOptions {
format: 'png' | 'jpg' | 'svg'
scale: number
}
async function exportDashboard(
elementId: string,
options: ExportOptions
): Promise<void> {
const element = document.getElementById(elementId)
if (!element) return
const capture = await snapdom(element, {
scale: options.scale,
backgroundColor: '#ffffff',
embedFonts: true,
})
await capture.download({
format: options.format,
filename: `dashboard-${Date.now()}`,
})
}
// 使用例
await exportDashboard('sales-chart', { format: 'png', scale: 2 })
SNS共有用の画像生成
ユーザーが作成したコンテンツをOGP画像として生成。
async function generateShareImage(content: string): Promise<Blob> {
// 一時的な要素を作成
const container = document.createElement('div')
container.innerHTML = `
<div style="
width: 1200px;
height: 630px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
">
<p style="
color: white;
font-size: 48px;
font-weight: bold;
text-align: center;
">${content}</p>
</div>
`
document.body.appendChild(container)
try {
const blob = await snapdom.toBlob(container.firstElementChild as HTMLElement)
return blob
} finally {
document.body.removeChild(container)
}
}
印刷用高解像度出力
async function captureForPrint(element) {
const capture = await snapdom(element, {
scale: 3, // 印刷用に3倍解像度
backgroundColor: '#ffffff',
embedFonts: true,
})
// PNG形式で高品質出力
const png = await capture.toPng()
return png
}
Canvasでさらに加工
キャプチャした後にCanvasで加工を加えたい場合。
async function captureWithWatermark(element, watermarkText) {
const capture = await snapdom(element)
const canvas = await capture.toCanvas()
const ctx = canvas.getContext('2d')
// 透かしを追加
ctx.font = '20px sans-serif'
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
ctx.fillText(watermarkText, 10, canvas.height - 10)
return canvas.toDataURL('image/png')
}
特定要素の除外
キャプチャから除外したい要素がある場合。
// HTML側で除外したい要素にクラスを付ける
// <button class="no-capture">ダウンロード</button>
const capture = await snapdom(element, {
exclude: '.no-capture',
filter: 'remove', // 'hide'だと非表示、'remove'だと削除
})
ボタンやコントロール類はキャプチャに含めたくないことが多いので、これは便利。
html2canvasとの比較
正直なところ、比較するとSnapDOMの方がいくつかの点で優れていると感じます。
| 項目 | SnapDOM | html2canvas |
|---|---|---|
| 依存関係 | なし | なし |
| 疑似要素 | 対応 | 部分対応 |
| Webフォント | 自動埋め込み | 要設定 |
| SVG出力 | ネイティブ対応 | 非対応 |
| 速度 | 高速 | 普通 |
特にSVG出力がネイティブでできるのは大きい。ベクター形式なので拡大しても劣化しない。
ただし、html2canvasは長年の実績があって情報も多いので、「とりあえず動けばいい」という場合は選択肢として残ります。
注意点
CORS制限
外部ドメインの画像を含む要素をキャプチャする場合、CORS制限に引っかかることがあります。
const capture = await snapdom(element, {
useProxy: true, // CORSプロキシを使用(要サーバー設定)
})
パフォーマンス
大きなDOM要素をキャプチャすると、それなりに時間がかかります。ユーザーにローディング表示を見せるなどの配慮が必要。
async function captureWithLoading(element) {
showLoading()
try {
const capture = await snapdom(element)
return await capture.toPng()
} finally {
hideLoading()
}
}
まとめ
SnapDOMを使ってみた感想:
- セットアップ: npm installだけ。依存関係ゼロ
- API: シンプルで直感的。覚えることが少ない
- 精度: スタイルの再現がしっかりしている
- 出力形式: SVG、PNG、JPG、WebP、Canvas、Blobと豊富
- 拡張性: プラグインで機能追加可能
「DOMを画像化したい」という要件があるなら、SnapDOMは有力な選択肢になると思います。特にhtml2canvasでスタイル崩れに悩んでいた人には、試してみる価値があるんじゃないかと。
個人的には、SVG出力ができるのが気に入っています。ベクター形式で出力できると、後から加工するときに便利なんですよね。
まだ使ったことがない人は、公式のデモサイトで動作を確認してみてください。どんな感じでキャプチャされるか、すぐに分かりますよ。