SVGが使えない場面とブラウザでのPNG変換の仕組み
公開日: 2026-06-30
SVGはXMLで図形を記述するベクター画像形式で、どのサイズに拡大しても劣化しない、CSSで色を動的に変更できるなど、フロントエンド開発では扱いやすい形式です。それでも「SVGで作ったアイコンをOGPに設定したのに反映されない」「Slackに貼り付けてもプレビューが出ない」という経験をした人は多いはずです。
SVGを受け付けないプラットフォームは思いのほか多く、そういった場面ではPNGへの変換が必要になります。この記事では、どの場面でPNGが必要になるのか、そしてブラウザがどうやってSVGをPNGに変換しているのかを整理します。
SVGを受け付けないプラットフォーム
OGP(Open Graph Protocol)の og:image にSVGを指定しても、Facebook・Twitter(現X)・LINE・Discordのいずれもプレビューに使いません。OGP仕様自体は画像フォーマットを制限していませんが、各プラットフォームが独自にJPEGとPNGを対象として処理しているためです。
メールも同様です。GmailやOutlookのHTMLメールに <img src="*.svg"> を埋め込んでも、表示されないクライアントが多く存在します。特にOutlookはSVGレンダリングに対応しておらず、代替テキストまたは空白として扱われます。
iOSのホーム画面ショートカットアイコン(apple-touch-icon)はPNG必須で、Slackのカスタム絵文字・アイコンもPNG・GIF・JPEGのみ受け付けます。
これらに共通する理由は2つあります。第一に、プラットフォームが画像をサーバーサイドでリサイズ・キャッシュする際、SVGのラスタライザを持っていないことです。第二に、SVGはJavaScriptや外部リソースを埋め込める構造を持つため、ユーザー生成コンテンツとして扱う場面ではXSSのリスクとして弾かれます。
ベクターとラスターの根本的な違い
SVGは「この座標に半径20pxの円を描く」「このパスに沿って線を引く」という命令の集合です。数式で図形を記述するため、1000×1000で表示しても4000×4000で表示しても計算し直すだけで輪郭が劣化しません。
PNGはピクセルの集合です。256×256ピクセルのPNGは縦横それぞれ256個のピクセルの色情報を持つだけです。これを4倍に拡大すると1ピクセルを4×4に引き伸ばすことになり、輪郭がぼやけます。
プラットフォームがSVGを弾く最も単純な理由は、「受け取った画像をピクセル単位で操作したい」からです。サムネイルの生成にもキャッシュにも、最終的にはピクセルの集合が必要になります。
ブラウザがSVGをPNGに変換する仕組み
SVGからPNGへの変換はサーバーを必要とせず、ブラウザだけで完結します。Canvas APIを使った変換は4ステップで構成されます。
① SVG文字列をBlobに変換してURL化する
const svgBlob = new Blob([svgCode], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(svgBlob);
URL.createObjectURL() でBlobを指すローカルURL(blob:https://... 形式)を生成します。Blob URLはオリジンをまたがないローカルリソースとして扱われるため、Canvas のセキュリティ制限(tainted canvas)を回避できます。クロスオリジンのURLを <img> に読み込んでCanvasに描画するとCanvasがtainted状態になり、toDataURL() がセキュリティエラーを投げます。Blob URLを経由すれば同一オリジンのリソースとして扱われるため、その問題が起きません。
② <img> 要素にBlobのURLを読み込む
const img = new Image();
img.onload = () => { /* 次のステップへ */ };
img.src = url;
SVGを直接Canvas APIに渡す方法はないため、一旦 <img> 要素を経由してブラウザにSVGをパース・レンダリングさせます。onload が発火した時点で、SVGはブラウザ内部でビットマップとして展開されています。
③ Canvasに drawImage() で描画する
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
URL.revokeObjectURL(url);
drawImage() でSVGの内容がCanvasのピクセルバッファに書き込まれます。Canvasのサイズが出力PNGの解像度を決定します。使い終わったBlob URLは revokeObjectURL() で解放します。
④ toDataURL() でPNGデータを取得する
const pngDataUrl = canvas.toDataURL('image/png');
Canvasのピクセルバッファが image/png に圧縮され、data:image/png;base64,... という形式で返されます。ダウンロードリンクの href に設定するか、fetch() でBlobに変換してからClipboard APIでコピーすることもできます。
Retina対応でなぜ2倍サイズが必要か
RetinaディスプレイなどHigh DPIディスプレイは、CSSの1ピクセルが物理的な2ピクセル(またはそれ以上)に対応します。デバイスピクセル比(DPR)が2のディスプレイで256×256のPNGを表示すると、実際には512×512の物理ピクセルに引き伸ばされ、輪郭がぼやけます。
これを防ぐには、Canvasを目標の2倍(512×512)で作成してPNGを出力し、表示時は width: 256px で半分のサイズに指定します。SVGであれば解像度を意識する必要はありませんが、PNG変換の時点でどの解像度を選ぶかは決めておく必要があります。OGP画像は各プラットフォームが推奨サイズを定めており(Facebookは1200×630px)、そのサイズで出力することで劣化なしに表示されます。
透明背景はPNGでも保持できる
SVGは背景を持たない(透明)のがデフォルトで、PNGもアルファチャンネルをサポートするため透明背景を保持できます。JPEGはアルファチャンネルを持てないため、透明が必要な用途では必ずPNGを選びます。
Canvas APIの変換で透明背景を保持するには、fillRect() による背景塗りつぶしを行わないだけで済みます。getContext('2d') で取得した直後のCanvasはすべてのピクセルのアルファ値が0(完全透明)で初期化されており、何もしなければ透明背景のPNGが出力されます。
使い分けは単純で、Slackの絵文字やロゴ素材として使う場合は透明背景で出力し、OGP画像やファビコンなど背景色が必要な場合は fillRect() で塗りつぶしてからSVGを描画します。
ブラウザ上でSVGコードのリアルタイムプレビューとPNG変換を試したい場合は、以下のツールでキャンバスサイズ・背景色・Retinaスケール・透明背景の設定を切り替えながら確認できます。