Vue.jsでsvgを扱うときにはまった出来事です。
みなさんsvg使っていますか?
svgはどんな環境でもきれいに表示できますし、色もスタイルでかえられるので便利ですよね。
そのsvgをVue.jsで出力したいときにはいくつか方法があるかと思います。
- テンプレートに直接書く
-
img
タグのsrc
にsvgファイルを指定する - svgファイルをそのまま出力する
エンジニアがsvgを書ける場合は1.
の方法をとれば良いかと思うのですが、デザイナーがsvgを書いてそのファイルをそのまま読み込まなければならないこともあるかと思います。
そのときは2.
か3.
の方法になるのですが、2.
では基本的に色を変えることができなく、svgのメリットを享受できません。(やる方法もないわけではないらしいですが)
ということで3.
の「svgファイルをそのまま出力する」方法を共有します。
方針
以下の方針で進めていきます。
- 使いやすいようにコンポーネントに切り出す
- svgファイルは一箇所に集める
- 今回は
@/assets/images/svg/
以下にsvgファイルが置いてある想定@/assets/images/svg/hogehoge.svg
- 今回は
- コンポーネントにはファイル名と色を渡せるようにする
- 色はcssで指定できる形
- 色の指定がなければ継承する
実装
- 色は直接
style
にバインド- 色の継承のため
currentColor
を使用
- 色の継承のため
- ファイル名はデフォルトスロットで受け取る
-
prop
でも良かったが、Vuetify
の作法に近い方法をとった
-
-
svg-inline-loader
を使って動的にsvgを読み込む-
webpack
が必要です
-
<template lang="pug">
.icon(v-html="getImageSource()" :style="styles" @click="$emit('click')")
</template>
<script lang="ts">
Vue.extend({
props: {
color: {
type: String,
default: null,
},
},
computed: {
styles() {
return {
color: this.color || "currentColor",
};
},
},
methods: {
getImageSource() {
let source = "";
try {
source = require(`!svg-inline-loader!@/assets/images/svg/${this.getLabel()}.svg`);
} catch (e) {
// 読み込み失敗時は何もしない
}
return source;
},
// Vue.jsの仕様により$slotsはリアクティブでないため、computedにはできない
getLabel() {
const children = this.$slots.default || [];
return (children[0] && children[0].text) || "";
},
},
});
</script>
<style lang="stylus" scoped>
.icon
// 自由にスタイルをあててください
</style>
こうすると、以下のように使うことができます。
(my-icon
としてコンポーネント登録した状態です。)
div
my-icon(color="#fff") hogehoge
span fugafuga
注意点
svgファイル内での色指定
svgファイルで色が直指定されていたらそちらが優先されるので、そんなときはデザイナーと調整が必要です。
defs
での定義
svgファイルをはき出すアプリケーションによってはdefs
を使ってパス等を定義して、use
を使って参照しているものがあります。
これらは基本idで管理されていて、svgファイル内ではユニーク性が保たれますが、複数のsvgファイルを扱うとidが重複してしまい、意図しない描画となってしまいます。
そんなときは読み込みのタイミングでidをsvgファイル毎にユニークにしてしまうと良いでしょう。
※ uuid
というモジュールを利用しています。
const PREFIX = "my-icon";
Vue.extend({
data: () => ({
v4: "",
}),
mounted() {
this.v4 = UUID.v4();
},
methods: {
getImageSource() {
let source = "";
try {
source = require(`!svg-inline-loader?idPrefix=${PREFIX}--!@/assets/images/svg/${this.getLabel()}.svg`);
} catch (e) {
// 読み込み失敗時は何もしない
}
// idをユニークにする
const regExp = new RegExp(`id="${PREFIX}--.+?"`, 'g');
source.match(regExp).forEach((result: string) => {
const id = result.replace('id="', '').replace('"', '');
const uuid = id.replace(PREFIX, `${PREFIX}--${this.v4}`);
source = source.split(id).join(uuid);
});
return source;
},
},
});
まとめ
Vue.jsでのsvgファイルの扱い方をまとめてみました。
いろいろ便利なsvgですが、自由度が高い分、扱い方はしっかりと決めておくのがよいかと思います。
コンポーネントにまとめておけばサイズとかもprop
でもらえて便利なのでぜひ!