🐞

Nuxt3 img v-bind:srcで画像が表示されない時の解決法

2022/05/22に公開
5

imgタグで動的srcを使おうとしたときに画像が表示されなくて困りました。
解決法を見つけたので紹介します。

新たな解決方法(2022/8/1追記)

この度kensukeさんよりコメントを頂き、
再検証を行う過程で先に紹介していたものより
簡潔で楽な解決方法を見つけましたのでご紹介しようと思います。

sample.vue
<script setup lang="ts">
type Item = { id: string, name: string }

const items: Array<Item> = [
  { id: "1", name: "sample1" },
  { id: "2", name: "sample2" }
]
</script>

<template>
  <template v-for="item in items">
    <img :src="`../assets/images/${item.name}.png`" />
  </template>
</template>

静的srcでは@/assets/images/~~みたいに書いても動きますが、
動的srcではこれでは動かず、../assets/imagesと書く必要があるみたいです!


結論。これで解決(古い解決方法です)

new URLを使って、動的srcを変換する方法で解決しました。

sample.vue
<script setup lang="ts">

//imgのpathを生成する関数
const generateImgPath = (fileName: string): string => {
  return new URL(`../assets/images/${fileName}.png`, import.meta.url).href
}

</script>

<template>
  <template v-for="items in items">
    <img :src="generateImgPath(item.name)" :alt="item.name">
  </template>
</template>
const generateImgPath = (path: string): string => {
  return new URL(path, import.meta.url).href
}

この記事読んでやっと解決しました!
(Nuxt3はデフォルトでviteを使っているので、Viteのドキュメントを読む必要がありました)
https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url

ビハインドストーリー

静的srcでは問題なく動いていた。

sample.vue
<template>
 <img src="@/assets/images/sample.png"
</template>

これをv-bindを使って変数を指定したところ、画像が表示されなくなった(汗)

sample.vue
<template v-for="item in items">
  <img :src='`@/assets/images/${item.name}.png`'>
</template>

ブラウザでHTMLを確認すると、srcが文字列のままで表示されていた。これだとURLが正しくないので画像が読み込まれるはずがない。

<img src="@/assets/images/sample.png">

本来はこうなってなっているべき

<img src="http://localhost:3000/_nuxt/assets/images/sample.png" >

解決策を探したところ、requireを使えばいいとのことで、以下コードに直したが、
ページが真っ白になり、コンソールにはrequire is not definedと出ていた。

sample.vue
<template v-for="item in items">
  <img :src="require('@/assets/images/${item.name}.png')"
</template>

この記事によるとViteだとrequireは動かないらしい。
(Nuxt3はデフォルトでviteが使われている)
https://github.com/nuxt/framework/issues/1950

この記事をみて、new URLを使ったところローカルでは無事動くようになった!!!
https://stackoverflow.com/questions/66419471/vue-3-vite-dynamic-img-src

その時のコード↓

sample.vue
<script setup lang="ts">

//動的srcをvueに認識させるための変換ツール
const convertImgSrc = (src: string): string => {
  return new URL(src, import.meta.url).href
}

</script>

<template>
  <template v-for="items in items">
    <img :src="convertImgSrc(`../assets/images/${item.name}.png`)" :alt="item.name">
  </template>
</template>

でも、、GitHub Pagesにデプロイしたところ、エラーが出て画像が表示されない。。
Failed to construct 'URL': Invalid URL

諦めかけた時に、偶然この記事を見つけて、
new URLの第一引数に変数を入れると動かないことに気付き、
https://vitejs.dev/guide/assets.html#new-url-url-import-meta-url

以下コード(冒頭で紹介したコード)に直したところ、ローカルでも本番環境でも無事動いた!

sample.vue
<script setup lang="ts">

//imgのpathを生成する関数
const generateImgPath = (fileName: string): string => {
  return new URL(`../assets/images/${fileName}.png`, import.meta.url).href
}

</script>

<template>
  <template v-for="items in items">
    <img :src="generateImgPath(item.name)" :alt="item.name">
  </template>
</template>

imageの実装でこんなに詰まると思っていなかったけど、無事解決して良かったです。
最近公式ドキュメントををしっかり読むことで解決するケースが多いので、公式のありがたみを感じています><

同じ問題で困っている人のお役にたてたら幸いです!

Discussion

KensukeKensuke

Lazyをコンポーネントの前につけることで解決できるかもしれません。(dynamic imports)
→こちらではうまくいかないことがわかりました。すみません。
https://v3.nuxtjs.org/guide/directory-structure/components#dynamic-imports

basicactorbasicactor

kensukeさん

コメントありがとうございます!
画像表示に使っているのがコンポーネントではなく、HTMLの標準imgタグではありますが、
試しに<lazy-img>、<Lazy-img>、<LazyImg>などにタグ名を変えて試してみましたが、
imgタグとして認識されなくなり、問題の解決には至りませんでした。
(処理を別コンポーネントに移し、<LazyXXX>みたいに使ってみましたが結果は変わらず..)

ただ、お陰様で今回再検証行った結果、より簡単な解決方法を見つけることが出来ました!

失敗コード:(path先頭に@を使用)

<img :src="`@/assets/images/${item.name}.png`" />

成功コード(path先頭に..を使用)

<img :src="`../assets/images/${item.name}.png`" />

ありがとうございます^^記事に追記させて頂きます!

KensukeKensuke

こちらの場合では、build時にうまく表示されないケースがあるかもしれないのですが、適切にbuildされましたでしょうか。(上記で誤りのあるコメントをし、すみません。)

<img :src="`../assets/images/${item.name}.png`" />
くまくま

@basicactor

試してみましたが、dev起動で確認できても、buildではうまく表示されないようです。

buildの場合、 new URL('画像相対パス', import.meta.url)にしないとだめでした。
@も使えず。
また、 nuxt@3.0.0-rc.6 はバグが有るようで、動的URLはどうやってもうまくいきませんでした。

下記で検証しました。

コメントの test00:ooo は、
1番目:yarn dev -o で確認
2番目:buildしてFirebaseエミュレータで確認(nuxt@3.0.0-rc.6版)
3番目:buildしてFirebaseエミュレータで確認(npm:nuxt3@latest版, 3.0.0-rc.6-27669113.5aa7288)

rc.6で失敗しているのは、__NUXT_BASE__/_nuxt/ に置換できていないのが原因のようです。
どうもデグレしている模様。

<template>
<div>
<img src="../assets/images/image00.jpg" />      // test00:ooo
<img src="@/assets/images/image00.jpg" />       // test01:ooo
<img :src="`../assets/images/image00.jpg`" />   // test10:oxx
<img :src="`@/assets/images/image00.jpg`" />    // test11:xxx
<img :src="test20" />                           // test20:oxo
<img :src="test21" />                           // test21:xxx
<img :src="`../assets/images/${image00}`" />    // test30:oxx
<img :src="test40()" />                         // test40:oxo
</div>
</template>

<script lang="ts" setup>
const image00 = 'image00.jpg'
const test20 = new URL('../assets/images/image00.jpg', import.meta.url)
// ↑rc6:http://127.0.0.1:5050/__NUXT_BASE__/image00.xxxx.jpg
const test21 = new URL('@/assets/images/image00.jpg', import.meta.url)
// ↑変換エラーdoesn't exist at build time, it will remain unchanged to be resolved at runtime
// http://127.0.0.1:5050/@/assets/images/bg_other01.jpg
const test40 = () => new URL('../assets/images/image00.jpg', import.meta.url)
</script>