173

I have a case where in my Vue.js with webpack web app, I need to display dynamic images. I want to show img where file name of images are stored in a variable. That variable is a computed property which is returning a Vuex store variable, which is being populated asynchronously on beforeMount.

<div class="col-lg-2" v-for="pic in pics">
   <img v-bind:src="'../assets/' + pic + '.png'" v-bind:alt="pic">
</div>

However it works perfectly when I just do:

<img src="../assets/dog.png" alt="dog">

My case is similar to this fiddle, but here it works with img URL, but in mine with actual file paths, it does not work.

What should be correct way to do it?

Samuel RIGAUD
  • 623
  • 7
  • 21
Saurabh
  • 71,488
  • 40
  • 181
  • 244

22 Answers22

155

I got this working by following code

  getImgUrl(pet) {
    var images = require.context('../assets/', false, /\.png$/)
    return images('./' + pet + ".png")
  }

and in HTML:

<div class="col-lg-2" v-for="pic in pics">
   <img :src="getImgUrl(pic)" v-bind:alt="pic">
</div>

But not sure why my earlier approach did not work.

Saurabh
  • 71,488
  • 40
  • 181
  • 244
  • 10
    According to nickmessing: "The expression inside v-bind is executed at runtime, webpack aliases at compile time." https://github.com/vuejs/vue-loader/issues/896 – antoine Aug 30 '17 at 23:00
  • @Grigio has a nice enhancement to this answer: https://stackoverflow.com/a/47480286/5948587 – samlandfried Jan 16 '19 at 23:25
  • 2
    The problem was that the path doesn't exist. The whole vue app get compiled by webpack into a single script (and images are also renamed normally, using a hash of the contents). Using require.context, you force the files to be seen by webpack and resolve to the resulting image. Check the working link in the browser and you'll see what I mean. Great solution. – estani Sep 24 '19 at 07:55
  • 1
    What if I don't want my images to be in the assets folder? What if they only exist in the website's public folder because they are uploaded by users of the admin area? – thinsoldier Nov 25 '19 at 02:08
  • 1
    hello, I have tried this but failed, then I found another solution that using template literals. It could work and I want to share this: https://stackoverflow.com/questions/57349167/vue-js-dynamic-image-src-with-webpack-require-not-working – lin Aug 24 '20 at 02:47
  • Even after so much years, this solution still works ! Thank you friend, you saved my nerves. – Just Alex Jan 21 '21 at 11:02
  • @antoine the GitHub link is correct and verified it is working in my case it is working with the require keyword. `:src="require('../assets/img/maintenance_page_concept.svg')"` – Muneer Khan May 23 '22 at 12:34
  • It is working fine in my case – Arslan Ameer May 24 '22 at 18:53
145

Here is a shorthand that webpack will use so you don't have to use require.context.

HTML:

<div class="col-lg-2" v-for="pic in pics">
    <img :src="getImgUrl(pic)" v-bind:alt="pic">
</div>

Vue Method:

getImgUrl(pic) {
    return require('../assets/'+pic)
}

And I find that the first 2 paragraphs in here explain why this works? well.

Please note that it's a good idea to put your pet pictures inside a subdirectory, instead of lobbing it in with all your other image assets. Like so: ./assets/pets/

Cody G
  • 8,368
  • 2
  • 35
  • 50
Artur Grigio
  • 5,185
  • 8
  • 45
  • 65
91

You can try the require function. like this:

<img :src="require(`@/xxx/${name}.png`)" alt class="icon" />

The @ symbol points to the src directory.

source: Vue URL transfrom rules

Jesse Reza Khorasanee
  • 3,140
  • 4
  • 36
  • 53
feng zhang
  • 1,193
  • 7
  • 8
  • Why is that `@` symbol required? – harm Dec 02 '19 at 10:16
  • 3
    `@` symbol is not required, it offten represent your `src` dir when using [Resolve | webpack](https://webpack.js.org/configuration/resolve/#resolvealias) (vue-cli is already config this.). – feng zhang Dec 03 '19 at 07:12
45

There is another way of doing it by adding your image files to public folder instead of assets and access those as static images.

<img :src="'/img/' + pic + '.png'" alt="pic" >

This is where you need to put your static images:

enter image description here

Samuel RIGAUD
  • 623
  • 7
  • 21
Rukshan Dangalla
  • 2,500
  • 2
  • 24
  • 29
13

Your best bet is to just use a simple method to build the correct string for the image at the given index:

methods: {
  getPic(index) {
    return '../assets/' + this.pics[index] + '.png';
  }
}

then do the following inside your v-for:

<div class="col-lg-2" v-for="(pic, index) in pics">
   <img :src="getPic(index)" v-bind:alt="pic">
</div>

Here's the JSFiddle (obviously the images don't show, so I've put the image src next to the image):

https://jsfiddle.net/q2rzssxr/

craig_h
  • 31,871
  • 6
  • 59
  • 68
  • Really? Here's an example with real images which seems to work fine: https://jsfiddle.net/q2rzssxr/1/ – craig_h Nov 08 '16 at 17:16
  • Not sure why, I got it working by the code I have written in another answer. You example even works without this function, see here: https://jsfiddle.net/9a6Lg2vd/1/ – Saurabh Nov 08 '16 at 17:35
  • In my case, pics is being populated asynchronously using Vuex store, may be that has something to do about it, I tried to simulate it, but did not work: https://jsfiddle.net/9a6Lg2vd/2/ – Saurabh Nov 09 '16 at 02:41
  • My case is more like this one: https://jsfiddle.net/9a6Lg2vd/4/ , but in my local pets gets data populated from an ajax call, but images don't get render. – Saurabh Nov 09 '16 at 03:02
  • This also works: https://jsfiddle.net/9a6Lg2vd/5/, not sure why it not working with file paths. – Saurabh Nov 09 '16 at 03:10
9

Vue.js uses vue-loader, a loader for WebPack which is set up to rewrite/convert paths at compile time, in order to allow you to not worry about static paths that would differ between deployments (local, dev, one hosting platform or the other), by allowing you to use relative local filesystem paths. It also adds other benefits like asset caching and versioning (you can probably see this by checking the actual src URL being generated).

So having a src that would normally be handled by vue-loader/WebPack set to a dynamic expression, evaluated at runtime, will circumvent this mechanism and the dynamic URL generated will be invalid in the context of the actual deployment (unless it's fully qualified, that's an exception).

If instead, you would use a require function call in the dynamic expression, vue-loader/WebPack will see it and apply the usual magic.

For example, this wouldn't work:

<img alt="Logo" :src="logo" />
computed: {
    logo() {
        return this.colorMode === 'dark'
               ? './assets/logo-dark.png'
               : './assets/logo-white.png';
    }
}

While this would work:

<img alt="Logo" :src="logo" />
computed: {
    logo() {
        return this.colorMode === 'dark'
               ? require('./assets/logo-dark.png')
               : require('./assets/logo-white.png');
    }
}

I just found out about this myself. Took me an hour but... you live, you learn, right?

Paul-Sebastian Manole
  • 2,538
  • 1
  • 32
  • 33
8

I also hit this problem and it seems that both most upvoted answers work but there is a tiny problem, webpack throws an error into browser console (Error: Cannot find module './undefined' at webpackContextResolve) which is not very nice.

So I've solved it a bit differently. The whole problem with variable inside require statement is that require statement is executed during bundling and variable inside that statement appears only during app execution in browser. So webpack sees required image as undefined either way, as during compilation that variable doesn't exist.

What I did is place random image into require statement and hiding that image in css, so nobody sees it.

// template
<img class="user-image-svg" :class="[this.hidden? 'hidden' : '']" :src="userAvatar" alt />

//js
data() {
  return {
    userAvatar: require('@/assets/avatar1.svg'),
    hidden: true
  }
}

//css
.hidden {display: none}

Image comes as part of information from database via Vuex and is mapped to component as a computed

computed: {
  user() {
    return this.$store.state.auth.user;
  }
}

So once this information is available I swap initial image to the real one

watch: {
  user(userData) {
    this.userAvatar = require(`@/assets/${userData.avatar}`);
    this.hidden = false;
  }
}
Alonad
  • 1,986
  • 19
  • 17
6

Here is Very simple answer. :D

<div class="col-lg-2" v-for="pic in pics">
   <img :src="`../assets/${pic}.png`" :alt="pic">
</div>
Diamond
  • 3,470
  • 2
  • 19
  • 39
6
<img src="../assets/graph_selected.svg"/>

The static path is resolved by Webpack as a module dependency through loader. But for dynamic path you need to use require to resolve the path. You can then switch between images using a boolean variable & ternary expression.

<img :src="this.graph ? require( `../assets/graph_selected.svg`) 
: require( `../assets/graph_unselected.svg`) " alt="">

And of course toggle the value of the boolean through some event handler.

Ron16
  • 445
  • 6
  • 11
6
<div
    v-for="(data, i) in statistics"
    :key="i"
    class="d-flex align-items-center"
  >
    <img :src="require('@/assets/images/'+ data.title + '.svg')" />
    <div class="ml-2 flex-column d-flex">
      <h4 class="text-left mb-0">{{ data.count }}</h4>
      <small class="text-muted text-left mt-0">{{ data.title }}</small>
    </div>
  </div>
5

You can use try catch block to help with not found images

getProductImage(id) {
          var images = require.context('@/assets/', false, /\.jpg$/)
          let productImage = ''
          try {
            productImage = images(`./product${id}.jpg`)
          } catch (error) {
            productImage = images(`./no_image.jpg`)
          }
          return productImage
},
5

I also faced this problem.

Try it:

computed {
    getImage () {
        return require(`../assets/images/${imageName}.jpg`) // the module request
    }
}

Here is a good article that clarifies this: https://blog.lichter.io/posts/dynamic-images-vue-nuxt/

Denkhis
  • 165
  • 1
  • 13
4

Tried all of the answers here but what worked for me on Vue2 is like this.

<div class="col-lg-2" v-for="pic in pics">
   <img :src="require(`../assets/${pic.imagePath}.png`)" :alt="pic.picName">
</div>
leipzy
  • 11,676
  • 6
  • 19
  • 24
3

As I am using Gridsome, this way worked for me.

**I also used toLowerCase() method

      <img
        :src="
          require(`@/assets/images/flags/${tournamentData.address.country_name.toLowerCase()}.svg`)
        "
      />
1

well the best and easiest way that worked for me was this of which i was fetching data from an API..

methods: {
       getPic(index) {
    return this.data_response.user_Image_path + index;
  }
 }

the getPic method takes one parameter which is the name of the file and it returns the absolute path of the file maybe from your server with the file name simple...

here is an example of a component where i used this:

<template>
    <div class="view-post">
        <div class="container">
     <div class="form-group">
             <label for=""></label>
             <input type="text" class="form-control" name="" id="" aria-describedby="helpId" placeholder="search here">
             <small id="helpId" class="form-text user-search text-muted">search for a user here</small>
           </div>
           <table class="table table-striped ">
               <thead>
                   <tr>
                       <th>name</th>
                       <th>email</th>
                       <th>age</th>
                       <th>photo</th>
                   </tr>
                   </thead>
                   <tbody>
                       <tr v-bind:key="user_data_get.id"  v-for="user_data_get in data_response.data">
                           <td scope="row">{{ user_data_get.username }}</td>
                           <td>{{ user_data_get.email }}</td>
                           <td>{{ user_data_get.userage }}</td>
                           <td><img :src="getPic(user_data_get.image)"  clas="img_resize" style="height:50px;width:50px;"/></td>
                       </tr>

                   </tbody>
           </table>
        </div>

    </div>
</template>

<script>
import axios from 'axios';
export default {
     name: 'view',
  components: {

  },
  props:["url"],
 data() {
     return {
         data_response:"",
         image_path:"",
     }
 },
 methods: {
       getPic(index) {
    return this.data_response.user_Image_path + index;
  }
 },
 created() {
     const res_data = axios({
    method: 'post',
    url: this.url.link+"/view",
   headers:{
     'Authorization': this.url.headers.Authorization,
     'content-type':this.url.headers.type,
   }
    })
    .then((response)=> {
        //handle success
      this.data_response = response.data;
      this.image_path = this.data_response.user_Image_path;
       console.log(this.data_response.data)
    })
    .catch(function (response) {
        //handle error
        console.log(response);
    });
 },
}
</script>


<style scoped>

</style>
user8453321
  • 344
  • 1
  • 5
  • 12
1

I encountered the same problem. This worked for me by changing '../assets/' to './assets/'.

 <img v-bind:src="'./assets/' + pic + '.png'" v-bind:alt="pic">
Bex
  • 43
  • 5
0

As of today, working with VUE 3 + Typescript & composition API, what I have done is wrap require function in try catch to handle crash.

computed: {
    getImage() {
        let imgSrc = "";
        try {
            imgSrc = require(`../assets/weather-widget-icons/ww-icon-${this.weatherForecast.main.toLowerCase()}.svg`);
        } catch (error) {
            console.error(`Image '../assets/weather-widget-icons/ww-icon-${this.weatherForecast.main.toLowerCase()}.svg' not found!`);
        }
        return imgSrc;
    }
}

and call this function in image tag:

<div class="weather-icon">
    <img :src="getImage" :alt="weatherForecast.main" />
</div>
Arslan Ameer
  • 1,010
  • 16
  • 30
0

The image needs to be transcribed. What worked for me is putting the images in public folder. i.e public/assets/img

Dynamic Image Tag:
<div v-for="datum in data">
 <img
     class="package_image"
     style="max-width:200px;"
     alt="Vue logo"
     :src="`./assets/img/${datum.image}`"
 >
<div>
Brisstone
  • 31
  • 1
  • 2
  • 6
0

I have a solution you may want to try.

Define a method like below

    methods: {
     getFlagImage(flag){
    return new URL('/resources/img/flags/'+flag+'.png', import.meta.url);
    },

    }

then images can be called with the established for loop

   <li :class=" 'nav-item', {'active': language === key} " v-for="(value, 
       key)  in locals" :key="value ">
      <a class="dropdown-item"  @click="switchLanguageTo(key)">
        <img :src="getFlagImage(key)"  /> {{value}}
      </a>
  </li>
0

I think I found the best solution to this problem by accident! The only thing you have to do is to start addressing from the root.

Doesn't work

<img :src="'../assets/' + pic + '.png">

Work:

<img :src="'src/assets/' + pic + '.png">
itmysm
  • 1
  • 2
0

Tried it several ways but In this way, it works for me. No issues.

<div class="anyClass" :style="{ backgroundImage: `url('${airport.photo}')` }">
</div>

photo is the property of array named airport.

R. Mahbub
  • 342
  • 6
  • 14
0
methods: {
  getPic(index) {
    return '../assets/' + this.pics[index] + '.png';
  }
}

then do the following inside your v-for:

<div class="col-lg-2" v-for="(pic, index) in pics">
   <img :src="getPic(index)" v-bind:alt="pic">
</div>