Vue.jsでSVGの生成とPNG化をするメモ(備忘録)
SVGで図形や文字をはめ込みPNG化する仕様を実装したのでメモとして残す。
やりたいこと
SVGタグの中に動的要素を配置してそれを画像化したい
<svg width="50" height="50" viewBox="0,0,50,50" xmlns="http://www.w3.org/2000/svg">
<!-- ここを動的に生成したい -->
<rect x="10" y="10" width="10" height="10" fill="#ccc"/>
</svg>
SVGタグを生成してブラウザで表示
要素を管理するオブジェクトを用意
今回は四角形とテキストを使う
interface State {
svgItem: (SvgItemRect | SvgItemText)[];
}
//SVGの四角形用
interface SvgItemRect {
type: 'rect';
x: number;
y: number;
width: number;
height: number;
rx: number;
ry: number;
fill: string;
}
//SVGのテキスト用
interface SvgItemText {
type: 'text';
x: number;
y: number;
fontSize: number;
fill: string;
text: string;
}
const state = reactive<State>({
svgItem: [
//初期値入れてみた
{
type: 'rect',
x: 20,
y: 20,
width: 100,
height: 100,
rx: 0,
ry: 0,
fill: '#849ff0',
},
{
type: 'text',
x: 100,
y: 20,
fontSize: 20,
fill: '#e74c3c',
text: 'sample',
},
],
});
テンプレート側
<svg
width="400"
height="400"
viewBox="0, 0, 400, 400"
xmlns="http://www.w3.org/2000/svg">
<template v-for="(row, index) in state.svgItem" :key="index">
<template v-if="row.type === 'rect'">
<rect
:x="row.x"
:y="row.y"
:width="row.width"
:height="row.height"
:rx="row.rx"
:ry="row.ry"
:fill="row.fill"/>
</template>
<template v-if="row.type === 'text'">
<text
:x="row.x"
:y="row.y"
:font-size="row.fontSize"
:fill="row.fill"
font-family="serif">
{{ row.text }}
</text>
</template>
</template>
</svg>
結果
SVGタグからDataURLを生成
svgタグを格納するrefを追加
const svgElement = ref();
svgタグにrefをセット
<svg
width="400"
height="400"
viewBox="0, 0, 400, 400"
xmlns="http://www.w3.org/2000/svg"
+ ref="svgElement"
>
SVGタグからSVGのDataURLを生成する関数
※btoaやunescapeが非推奨になっている・・・・
import { Buffer } from 'buffer';
const ToBase64 = (elm: HTMLElement): string | null => {
const svgData = new XMLSerializer()
.serializeToString(elm)
.replace(/<!--[\s\S]*?-->/g, '');//一応<!--v-if-->がいらないから消す
const dataUrl =
'data:image/svg+xml;charset=utf-8;base64,' +
// btoa(unescape(encodeURIComponent(svgData))) //昔はこれで良かった
Buffer.from(svgData).toString('base64');//今の方法は?
return dataUrl;
};
SVGのDataURLをImgオブジェクトに変換する関数
const ToImage = (base64img: string): Promise<HTMLImageElement | null> => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (e) => resolve(null);
img.crossOrigin = 'anonymous';
img.src = base64img;
});
};
Canvasにimgを描画してPNGデータを取得
上の関数を使いPNGデータを生成する
const createPngImg = async () => {
let canvas: HTMLCanvasElement | null = null;
let ctx: CanvasRenderingContext2D | null = null;
try {
const svgDataUrl = ToBase64(svgElement.value);
if (svgDataUrl === null) return null;
const img = await ToImage(svgDataUrl);
canvas = document.createElement('canvas');
canvas.width = 400;
canvas.height = 400;
ctx = canvas.getContext('2d');
if (img === null || ctx === null) return null;
ctx.drawImage(img, 0, 0);
return canvas.toDataURL('image/png');
} catch (error) {
console.error(error);
return null;
} finally {
canvas = null;
ctx = null;
}
};
最終的に
あとは最初に用意したリアクティブな要素を操作できるようにすれば幸せになれるはず・・・・
最後に
イベントが使えます。
ということは、ドラッグ操作やクリック、マウスホイールなどで自在にSVGが操れますね(幸せ)
GitHubにサンプル上げてます
ついでにGitHubにデモページも・・・