Web製図・ダイアグラム・CAD系のアプリでは、「SVGを書き出す」のではなく、WebアプリでCtrl+C → ExcelでCtrl+Vしたいという要件が出ます。さらに実運用では、Excel上の図をWebへ戻し、できればPNG一枚ではなく線・文字・図形を再利用したくなります。
今回の実測で重要だったのは、「SVGがクリップボードにある」ことと「Web側でベクターとして受理できる」ことは別だった点です。貼った直後は成功しても、Excelを保存・終了・再オープンした後だけ、vector-richなSVGを受信側が拒否してPNGへフォールバックするケースも確認しました。
最初に結果を3つへ分けます。
| Contract | 今回の実測 | この記事で扱うこと |
|---|---|---|
| Web → Excel | 対応Chromeでimage/svg+xmlを書き、ExcelへSVGとして貼付できた |
最小writeコードとfeature detection |
| Excel → Web | SVGを取得できた。SVG/PNG等が同居するケースもあった | MIME、payload構造、受信側判定の分離 |
| Web → Excel → save/close/reopen → Web | Fresh成功とReopen成功は同義ではなかった | SVG serialization差、receiver reject、PNG fallback |
この記事は単なるClipboard API入門ではなく、ExcelへのSVG貼り付け、ExcelからWebへのSVG取得、ラスター表示の原因、保存再オープン後の変化を一続きに追います。最終UIだけから「ExcelがSVGを捨てた」と判断しないための切り分け方まで含めます。
Web → Excel:image/svg+xmlを書き込む
対応Browserでは、SVG文字列をBlobにしてClipboardItemへ渡せます。
const SVG_MIME = "image/svg+xml";
async function copySvgForExcel(svgText) {
if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
throw new Error("Clipboard write is unavailable");
}
if (typeof ClipboardItem.supports === "function" &&
!ClipboardItem.supports(SVG_MIME)) {
throw new Error("SVG clipboard write is unsupported");
}
const blob = new Blob([svgText], {
type: "image/svg+xml;charset=utf-8",
});
await navigator.clipboard.write([
new ClipboardItem({ [SVG_MIME]: blob })
]);
}
今回のChrome / Excel環境では、このClipboardをExcelへSVG画像として貼り付けられました。
image/svg+xml対応はブラウザーごとに差があるため、実製品ではClipboardItem.supports("image/svg+xml")などでfeature detectionを行い、未対応を成功扱いしないことが重要です。
SVGはxmlnsと正のviewBoxを持たせ、外部URLや外部CSSだけに依存しないself-containedな構成にしておく方が扱いやすくなります。アプリ独自のdata-*やmetadataは、ExcelへSVGとして渡すための必須条件ではありません。
Excel → Web:BrowserからSVGを読む
Async Clipboardでimage/svg+xmlが取得できる環境なら、最小形は次のようになります。
async function readSvgFromClipboard() {
const items = await navigator.clipboard.read();
for (const item of items) {
if (!item.types.includes("image/svg+xml")) continue;
const blob = await item.getType("image/svg+xml");
return await blob.text();
}
return null;
}
ただし、ここからが実装上の本番です。
-
image/svg+xmlがあっても、中身がRasterを包んだSVGの場合がある - SVGとPNGが同じClipboardに同居する場合がある
- PasteEventでは
image/svg+xmlが見えても本文が空で、Async Clipboardでは読める場合がある - Webアプリ側がSVGを取得した後、パーサーで拒否してPNGへフォールバックする場合がある
そのため「Rasterとして貼られた」だけでは、ExcelがSVGを捨てたとは言えません。
保存して開き直すと何が変わるのか:双方向成功とRound-trip成功は別
Web → Excel
と、
Excel → Web
がそれぞれ成功しても、
Web
→ Excel
→ save
→ close
→ reopen
→ copy
→ Web
が同じように成功するとは限りません。
今回の実測では、Excelを閉じて再オープンした後にClipboardへ出るSVGのシリアライズ表現が変わり、SVG自体にはベクター情報が残っているのに、受信側が一部のtransformを安全に正規化できずPNGへフォールバックするcaseがありました。
この記事で分かること
- WebアプリのSVGをファイル保存なしでExcelへCtrl+Vする最小コード
- ExcelからWebアプリへSVGを受け取る最小コード
-
image/svg+xmlがあるのにRasterになる理由の切り分け方 - SVG / PNG / Office形式が同居するmulti-format Clipboardの考え方
- PasteEventとAsync Clipboardを分けて観測する理由
- Freshでは成功するのに保存→再オープン後だけ失敗する問題の調べ方
- 双方向copy/pasteとRound-tripを別Acceptanceにする方法
対象読者
Web製図、diagram、whiteboard、CAD、グラフ、Office連携SaaSなどで、**「WebとExcelの間で図をベクターのままコピペしたい」**開発者向けです。
ここから先は、実際にどこで表現が変わったかを実測で分解します。
検証環境
- Microsoft Excel 2024 MSO
- Version 2608
- Build 16.0.20326.20072
- 64-bit
- Chrome 153.0.0.0 / Win64 x64
- 検証期間: 主研究 2026-09-20〜2026-09-21 / 追加の合成fixture実機確認 2026-09-26
Edge、別Excel build、macOS Excel、Word/PowerPointには一般化しません。
A〜G: まずcontent combinationを分ける
同一セッション / fresh placementで、次の7ケースを作りました。
| Case | Vector | SVG | Raster |
|---|---|---|---|
| A | ✓ | ||
| B | ✓ | ||
| C | ✓ | ||
| D | ✓ | ✓ | |
| E | ✓ | ✓ | |
| F | ✓ | ✓ | |
| G | ✓ | ✓ | ✓ |
このMatrixの目的は網羅ではなく、正常caseと異常caseの差を一つにすることです。
特にFとGはRasterの有無だけが差になります。
Windows Clipboardは一つのformatではない
今回のExcel Drawing copyでは、SVG、PNG、EMF、DIB、Office登録形式など複数representationが同時に存在しました。
したがって、
PNGがある
と、
SVGがない
は同義ではありません。
受け側はformat presenceだけでなく、どのrepresentationを優先し、どうsemanticに解釈するかを決める必要があります。
PasteEventとAsync Clipboardは分けて見る
今回のChrome環境では、PasteEvent側でimage/svg+xmlがadvertiseされていてもgetData("image/svg+xml")で本文が取れず、Async Clipboardのnavigator.clipboard.read()ではSVG Blobを取得できるcaseがありました。
const items = await navigator.clipboard.read();
for (const item of items) {
if (item.types.includes("image/svg+xml")) {
const blob = await item.getType("image/svg+xml");
const svg = await blob.text();
console.log(blob.size, svg.slice(0, 80));
}
}
API surfaceの失敗を、そのままsource absenceとみなさないことが重要です。
ラスター(Raster)だけの図でもSVGが存在した
Case CはRasterだけですが、image/svg+xmlが存在しました。
ただし中身はeditable vectorではなく、概念的には次のようなwrapperです。
<svg>
<defs>...</defs>
<use ... />
<image href="data:image/..." />
</svg>
したがって、
MIME = image/svg+xml
≠ semantic content = vector
です。
Mixed SVGにはベクター・Text・Rasterが共存した
DやGでは同一SVG内にpath、text、embedded imageが共存しました。
この時点で「Excelが全部PNG一枚へflattenした」という仮説は外れます。
SVGに情報が残っているなら、次はreceiver側のparser/canonicalizerを見ます。
Case F: ラスターに見えたが、実際はSVG reject→PNGフォールバック
FはVector + SVG / Rasterなしです。
生のSVGはvector-richでした。それでもRaster用UIへ流れました。
実際の処理は、
SVG acquisition成功
↓
strict parser
↓
Office由来と判断するsignal不足
↓
near-similarityでreject
↓
companion PNG フォールバック
↓
Raster用UI
でした。
つまり最終UIはsource classificationではありません。
保存・終了・再オープン後にだけ壊れるケース
同一セッションで通るなら十分だと思っていました。
しかし、Excelに貼った素材をブックへ保存し、Excelを閉じ、同じ.xlsxを再オープンしてコピーすると挙動が変わりました。
ここで、状態を3つに分けました。
PRE 保存前
SAVED Ctrl+S直後、Excelは開いたまま
REOPEN 保存後にExcelを閉じ、同じfileを再オープン
Ctrl+S直後
一つのcontrolではPREとSAVEDのSVG payloadが同一でした。
つまり少なくともこのオブジェクトでは、保存ボタンを押した瞬間にSVG本文が書き換わったわけではありません。
ただしSAVEDではPNG companionが追加されました。
Close / Reopen後
REOPENではSVG本文自体が別シリアライズ表現になり、source app固有metadataが消えました。
ここで初めて「保存」と「再オープン」を別境界として扱う必要があると分かりました。
独立controlでdirect Vector→reopen Rasterを再現
元のcontrolを使い回さず、別のvector-only素材で再確認しました。
Direct copy
- Clipboard:
image/svg+xml - SVG: 44,369 bytes
- result: Vector
Excelへ貼付→保存→終了→再オープン→copy
- Clipboard:
image/svg+xml+image/png - SVG: 12,805 bytes
- result: Raster フォールバック
ここで重要なのは、12,805 bytesのSVGが存在していたことです。
中にはline / path / rect / ellipse / textなどvector構造が残っていました。
Excelがvectorを全部捨ててRasterだけにしたわけではありません。
では何がrejectされたのか
reopen後のSVGを受信側パーサーへoffline replayすると、transformed ellipseでFail-Closedしました。
概念的には、
<g transform="matrix(...)">
<ellipse ... />
</g>
のような形です。
今回のcanonical modelでは、このellipse + transformをlosslessにflattenできないためrejectしました。
そこでdiagnostic controlとして、そのellipseだけを見た目が等価なpathへ置換し、他のsave/reopen構造は維持しました。
するとVector importへ変化しました。
このdiscriminatorから、今回の問題は、
Excel保存後にvectorが消えた
ではなく、
Excel再シリアライズ表現後のvector-rich SVGの一部primitive/transformをreceiverが安全にcanonicalizeできなかった
と整理できます。
2026-09-26 追加実機検証:byte差と劣化は同じではない
公開用の完全合成fixtureを使い、主研究環境でWeb→Excel→WebとSave→Close→Reopen→Webを追加確認しました。対象はBasic Vector、回転ellipse、回転text、stroke+非等方scale、Mixed、Raster wrapper、意図的なexcessive-distortion negativeの7ケースです。
7ケースすべてでWeb→Excel pasteを確認し、記録したDirect/Reopen metadataではimage/svg+xmlとimage/pngを観測しました。ここで重要だったのは、SVGのSHA-256が変わっても、それだけでは劣化を意味しないことです。
実機では、
-
rotate(...)→ 等価なmatrix(...) -
matrix(...)→ 等価なscale(...) -
href→xlink:href - 色表記や属性順序の正規化
のような再シリアライズが起きました。一方、今回比較したpairでは構造・transform意味・埋め込みRaster payloadが維持され、tested renderer上で視覚的に同等なケースを確認しました。2026-09-27の独立CairoSVG/Pillow再描画では、2つのre-serialization pairで34/204,800 pixels(0.0166%、max channel delta 1)の微小差も観測したため、pixel完全一致は一般化しません。
また、Mixed SVGとRaster wrapperはどちらもClipboard MIMEとしてはimage/svg+xmlです。したがって、
image/svg+xmlがある
≠ 純Vector
≠ receiverが安全に受理できる
≠ 編集可能modelへ戻せる
を実機でも確認できました。さらにnegative fixtureはExcelへ正常に貼れ、Reopen後も約3.616%のcondition distortionを保持しました。Excelが貼れることと、Web receiverがそのtransformを受理すべきことも別契約です。
研究Matrixはcontentだけでなくlifecycleも必要
今後はA〜Gだけでは不十分です。
最低でも、
Content axis
A〜G
Lifecycle axis
Direct / fresh
Saved-open
Closed + reopened
を分けます。
「貼った直後に動く」と「ブックを翌日開き直しても動く」は別Acceptanceです。
実装時の診断順序
- Windows native formatを確認
- PasteEvent typesを確認
- Async Clipboard typesとpayloadを確認
- 生のSVGを固定
- SVG構造を数える
- parser reject reasonを記録
- フォールバック sourceを記録
- document lifecycle stateを記録
- raw replayへ移す
- 最後の実機試験は仮説を分けるdiscriminatorにする
まとめ
今回の特定環境で確認したことは次です。
- Clipboardはmulti-formatだった
- 同一セッション A〜GではSVGを取得できた
- Raster-onlyでもSVG wrapperになるcaseがあった
- Mixed SVGにvector/text/rasterが共存するcaseがあった
- Raster用UIはRaster sourceを意味しない場合があった
- Case FはSVG reject後PNG フォールバックだった
- Ctrl+S直後とclose/reopenは別境界だった
- 独立controlでdirect Vector→reopen Rasterを再現した
- reopen後もvector-rich SVGは残っていた
- 今回はtransformed ellipseのcanonicalizationでFail-Closedしていた
- Web/HTML→Excelは
image/svg+xmlClipboard writeでSVGとして渡せた - ただしOutbound成功とsave/reopen後のRound-trip成功は別Acceptance
最終UIから原因を逆算しない。さらに、同じオブジェクトでもlifecycle stateをCase IDへ含める。
これが今回まで含めた最大の教訓です。
Limitations
本記事は一つのExcel/Windows/Chrome系環境での実測です。別Excel build、Edge、macOS、Word/PowerPointで同じシリアライズ表現になることは確認していません。
約0.516%は過去fixtureで観測した値で、Excelの仕様値ではありません。1%もMicrosoft推奨値ではなく、本研究で用いたbounded compatibility policy例です。
参考資料
- Stack Overflow: How to copy SVG images from the browser to the clipboard
https://stackoverflow.com/questions/12255444/how-to-copy-svg-images-from-the-browser-to-the-clipboard - Microsoft Office release notes: SVG Clipboard Support
https://learn.microsoft.com/en-us/officeupdates/current-channel-preview-archived - W3C: Clipboard API and events
https://www.w3.org/TR/clipboard-apis/ - MDN: ClipboardItem
https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem - MDN: ClipboardItem.supports()
https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/supports_static - Microsoft Support: Edit SVG images in Microsoft 365
https://support.microsoft.com/en-us/office/graphics-visuals/edit-svg-images-in-microsoft-365 - Microsoft Learn: SVGBlip (Office 2019+)
https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.office2019.drawing.svg.svgblip
自分の実装へ持ち帰るなら
無料で再現するだけなら、完全合成fixtureと最小Clipboard demoで十分です。実製品へ入れる場合は、次の順で進めると調査範囲を抑えられます。
- MIME/format presenceを観測
- 生のSVGを固定
- structural semanticsを数える
- parser reject reasonを固定
- フォールバック sourceを記録
- freshとreopenを別Regressionへ入れる
関連コンテンツ
この記事は、技術的な答え・実測結果・最小実装を無料で確認できる入口としてまとめています。ここから先は目的で選べます。
調査workflowを案件へ移したい
Case ID、Evidence Sheet、offline replay、discriminator、Release Acceptanceの作り方を、調査手順として整理しています。
note:ExcelとWebアプリのSVGコピペが壊れるときの調査方法
https://note.com/xxsiaxx/n/na1f8984e7fd9
実装設計まで体系的に整理したい
Clipboard観測、SVG構造、near-similarity、Fail-Closed、lifecycle regression、Web→Excel write、release acceptanceまで37章で扱っています。
Zenn Book:Web ↔ Excel SVG Clipboard 実装・診断ガイド
https://zenn.dev/xxshinxx/books/31d5a51839f1df
自分の環境で実際に観測・比較したい
完全合成fixture、Browser / Windows probe、raw SVG capture、offline analyzer、checklist、Reference Evidenceをまとめた診断Kitです。
BOOTH:Webアプリ↔Excel SVGクリップボード診断Kit
https://sia-music.booth.pm/items/8907143
有料物は結論を隠すためではなく、理解・診断・実装の作業を再構築する時間を減らすために分けています。