22
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Vue】svg画像をそのまま出力して色を変える

Last updated at Posted at 2019-03-13

Vue.jsでsvgを扱うときにはまった出来事です。

みなさんsvg使っていますか?
svgはどんな環境でもきれいに表示できますし、色もスタイルでかえられるので便利ですよね。

そのsvgをVue.jsで出力したいときにはいくつか方法があるかと思います。

  1. テンプレートに直接書く
  2. imgタグのsrcにsvgファイルを指定する
  3. 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が必要です
Icon.vue
<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というモジュールを利用しています。

Icon.vue(一部だけ切り出し)
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でもらえて便利なのでぜひ!

22
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?