Vue.jsでSVGが正しく表示されなかった時の対応 (1)

855
NO IMAGE

先日、Vue.jsでSVGファイルを表示した際に、全然違う見た目で表示される現象に出くわしました。
こんな感じです(左:元のSVG、右:表示されたSVG)。

原因がよくわからないので、表示されているSVGのHTMLを見てみました。

こっちが元のSVG

<svg xmlns="http://www.w3.org/2000/svg" width="17.95" height="17.95" viewBox="0 0 17.95 17.95">
    <defs>
        <style>.a{fill:none;stroke:#7c7c7c;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style>
    </defs>
    <g transform="translate(1 1)">
        <path class="a" d="M6.272,4.5H18.678A1.772,1.772,0,0,1,20.45,6.272V18.678a1.772,1.772,0,0,1-1.772,1.772H6.272A1.772,1.772,0,0,1,4.5,18.678V6.272A1.772,1.772,0,0,1,6.272,4.5Z" transform="translate(-4.5 -4.5)"/>
        <path class="a" d="M18,12v7.089" transform="translate(-10.025 -7.569)"/>
        <path class="a" d="M12,18h7.089" transform="translate(-7.569 -10.025)"/>
    </g>
</svg>

こっちが表示されているSVG

<svg data-v-469af010="" xmlns="http://www.w3.org/2000/svg" width="17.95" height="17.95" class="">
    <defs data-v-469af010=""></defs>
    <path data-v-469af010="" d="M2.772 1h12.406a1.772 1.772 0 011.772 1.772v12.406a1.772 1.772 0 01-1.772 1.772H2.772A1.772 1.772 0 011 15.178V2.772A1.772 1.772 0 012.772 1zM8.975 5.431v7.089M5.431 8.975h7.089" class="a"></path>
</svg>

全然違います。

Webで参考になる情報を探していたところ、似たような現象が起きてそれを解決している記事を発見。
しかし、同じように対応しても問題は解消しませんでした。

ただ一つ分かったのは、SVGを表示するのにvue-svg-loaderを使っていたのですが、どうやらvue-svg-loaderがsvgoを使ってSVGを圧縮していたため、HTMLの内容が変わっていたようです。
とりあえず、vue.config.jsにsvgoを無効化するオプションを追加してみました。

module.exports = {
  chainWebpack: (config) => {
    const svgRule = config.module.rule('svg');

    svgRule.uses.clear();

    svgRule
      .use('babel-loader')
      .loader('babel-loader')
      .end()
      .use('vue-svg-loader')
      .loader('vue-svg-loader')
      .options({
        svgo: false // これを追加してsvgoを無効化
      })
  },
}

その結果のHTMLがこれです。

元のSVG

<svg xmlns="http://www.w3.org/2000/svg" width="17.95" height="17.95" viewBox="0 0 17.95 17.95">
    <defs>
        <style>.a{fill:none;stroke:#7c7c7c;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style>
    </defs>
    <g transform="translate(1 1)">
        <path class="a" d="M6.272,4.5H18.678A1.772,1.772,0,0,1,20.45,6.272V18.678a1.772,1.772,0,0,1-1.772,1.772H6.272A1.772,1.772,0,0,1,4.5,18.678V6.272A1.772,1.772,0,0,1,6.272,4.5Z" transform="translate(-4.5 -4.5)"/>
        <path class="a" d="M18,12v7.089" transform="translate(-10.025 -7.569)"/>
        <path class="a" d="M12,18h7.089" transform="translate(-7.569 -10.025)"/>
    </g>
</svg>

表示されているSVG

<svg data-v-469af010="" xmlns="http://www.w3.org/2000/svg" width="17.95" height="17.95" viewBox="0 0 17.95 17.95" class="">
    <defs data-v-469af010=""></defs>
    <g data-v-469af010="" transform="translate(1 1)">
        <path data-v-469af010="" d="M6.272,4.5H18.678A1.772,1.772,0,0,1,20.45,6.272V18.678a1.772,1.772,0,0,1-1.772,1.772H6.272A1.772,1.772,0,0,1,4.5,18.678V6.272A1.772,1.772,0,0,1,6.272,4.5Z" transform="translate(-4.5 -4.5)" class="a"></path>
        <path data-v-469af010="" d="M18,12v7.089" transform="translate(-10.025 -7.569)" class="a"></path>
        <path data-v-469af010="" d="M12,18h7.089" transform="translate(-7.569 -10.025)" class="a"></path>
    </g>
</svg>

かなり元のファイルのHTMLに近い内容になりましたが、style要素が綺麗になくなってます。当然、SVGの表示も正しくなってません。
ということでsvgoはおそらく無罪なので、svgoの無効化はやめました。

なぜstyleが消えるのかを調査していたところ、stack overflowにこんなやり取りを発見。

なんと、Vue.jsはstyleタグを認識してくれないらしい。なので、styleタグではなく、svg:styleタグを使えと書いてあります。
SVGファイルをいちいち書き換えるのは嫌だなと思いつつ、書き換えてみると、見事に求めていた表示になりました。

上記のやり取りを読み進めると、Vue.jsではSVGのstyleタグは除去して、vueファイルのstyleで定義しなさいと書いてありました(あ〜、もう全部のSVGファイルのstyleタグをsvg:styleに書き換えてしまったよ)。
書かれている通りにstyleをvueファイルのstyleで定義しても、うまく表示されました(SVGのstyleタグの除去は不要)。こっちの方がSVGファイルに手を入れなくて済むのでスマートですね(svg:styleタグ全部戻さないと…)。

styleタグ(要素)がダメなら、styleを下記のように属性にすれば良いのでは?と思い試してみました。

<svg xmlns="http://www.w3.org/2000/svg" width="17.95" height="17.95" viewBox="0 0 17.95 17.95">
    <defs>
        <!-- <style>.a{fill:none;stroke:#7c7c7c;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style> -->
    </defs>
    <g transform="translate(1 1)">
        <path style="fill:none;stroke:#7c7c7c;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;" d="M6.272,4.5H18.678A1.772,1.772,0,0,1,20.45,6.272V18.678a1.772,1.772,0,0,1-1.772,1.772H6.272A1.772,1.772,0,0,1,4.5,18.678V6.272A1.772,1.772,0,0,1,6.272,4.5Z" transform="translate(-4.5 -4.5)"/>
        <path style="fill:none;stroke:#7c7c7c;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;" d="M18,12v7.089" transform="translate(-10.025 -7.569)"/>
        <path style="fill:none;stroke:#7c7c7c;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;" d="M12,18h7.089" transform="translate(-7.569 -10.025)"/>
    </g>
</svg>

うまくいきますね。HTMLも圧縮されて良い感じです。

<svg data-v-469af010="" xmlns="http://www.w3.org/2000/svg" width="17.95" height="17.95" class="">
    <g data-v-469af010="" fill="none" stroke="#7c7c7c" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
        <path data-v-469af010="" d="M2.772 1h12.406a1.772 1.772 0 011.772 1.772v12.406a1.772 1.772 0 01-1.772 1.772H2.772A1.772 1.772 0 011 15.178V2.772A1.772 1.772 0 012.772 1zM8.975 5.431v7.089M5.431 8.975h7.089"></path>
    </g>
</svg>

ということは、あれこれ頑張らずに、デザイナーにstyleを要素ではなく属性で出力して貰えば良いということになります。

目次

結論

styleタグを含むSVGファイルを正しく表示させるには、以下の方法があります。

  • styleタグをsvg:styleタグに書き換える(vueファイルにベタにSVGを書く場合はこれで良い)
  • SVGファイル内のstyle定義をvueファイルに定義する
  • デザイナーにお願いして、styleは要素でなく属性にしてもらう

実は、もう一つここでは触れていない問題が残ってますが、それは次回紹介します。