JavaScript
TypeScript
Vue.js

【Vue.js】imgタグのsrc要素は指定の仕方によって読み込み方が違う

vue.jsのimgタグはsrcの指定の方法によって実は参照するファイルが異なるっぽい。

imgタグを並べて表示する場合この挙動を知らないとハマりそうなので、どのような挙動になっているのか調べたのでまとめました。

対象はVue CLI 3で生成したプロジェクトです。


例1: 普通にimgタグを並べる場合

例えば画像を並べて表示する場合、一番単純な実装はimgタグを並べるだけで良いです。

画像はsrc/assets/以下に置いてます。

シンプル。


home.vue

<template>

<ul>
<li><img src="@/assets/img1.png"/></li>
<li><img src="@/assets/img2.png"/></li>
<li><img src="@/assets/img3.png"/></li>
</ul>
</template>


例2:v-forでimgタグを並べる場合 (失敗)

ただ画像数が少なければいいですが、数を増やしたり要素が複雑化した場合はv-forで生成したほうがよいです。

増えても画像のパスだけ追加すれば良くなるので保守性があがります。


home2.vue

<template>

<ul>
<li v-for="icon in icons" :key="icon">
<img :src="icon"/>
</li>
</ul>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class Home extends Vue {
icons = [
'@/assets/img1.png',
'@/assets/img2.png',
'@/assets/img3.png',
]
}
</script>

一見これでもうまく動きそうなのですが、ビルドは通るがブラウザ上で見ると画像が404になってしまいます。

なんで...?


解説

なぜうまくいかないかというとsrc要素を静的に指定する場合と、v-bindで動的に指定する場合では参照方法が異なるからです。

例1では静的にsrc要素を指定していますが、その場合は内部でWebpackのfile-loaderで画像をモジュールとして読み込んでいます。

例2ではv-bindで動的に指定していますが、その場合はそのままのパスとして扱われます。

以下のURLに詳細が書かれています。

HTML and Static Assets | Vue CLI 3


解決策1:publicフォルダの画像を参照する

モジュールとして解決できないなら、単純に静的ファイルとしてURLを指定すれば良い。

画像をsrc/assetsフォルダからpublic/imgフォルダに移動させて、パスを絶対パスに書き換えます。(相対パスでも可。)

publicフォルダはビルド時に出力先のフォルダにコピーされるので、そのパスを書いとけばいい。


home2.vue

<template>

<ul>
<li v-for="icon in icons" :key="icon">
<img :src="icon"/>
</li>
</ul>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class Home extends Vue {
icons = [
'/img/img1.png',
'/img/img2.png',
'/img/img3.png',
]
}
</script>


解決策2:require()を使いモジュールとして解決する

解決策1でも動作しますが、publicフォルダを使用するよりモジュールとして読み込んだほうが以下のような利点があります。


  • ファイルが見つからないと404エラーでなくコンパイルエラーになる

  • 画像ファイル名にコンテンツハッシュ(例:img1.3020e851.png)がつくため、ファイルを変更したときにブラウザキャッシュのせいで古い画像を表示してしまうという問題が発生しない

なので、できるだけモジュールとして画像を扱いたい。

その場合はプログラム上でreuqire()を呼び出せばよいです。

画像ファイルはsrc/assetsフォルダに置きます。


home2.vue

<template>

<ul>
<li v-for="icon in icons" :key="icon">
<img :src="icon"/>
</li>
</ul>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class Home extends Vue {
icons = [
require('@/assets/img1.png'),
require('@/assets/img2.png'),
require('@/assets/img3.png'),
]
}
</script>

require()でなくてimport文でも動作しますが単純に記述量が増えるためrequire()を使用しています。

これで画像をモジュールとして扱い、v-forで画像を並べることができました。


まとめ

imgのsrcをv-bindで指定する場合は、静的ファイルとして扱うか、reuqire()を使用してモジュールとして解決する。