0

I want to render images from a local folder with v-for, but how to make it 100% dynamic?

I tried the solutions offered in this thread. When I tried the most useful solution, I just get a blank page, unless I fill the array with the name of the images.

<template>
  <div class="comp__cardroster">
    <div class="container__cards"  >
      <div v-for="image in images" :key="image" class="tile--outer">
        <img class="tile--inner" :src="selectImage(image)" :alt="image"></div>
    </div>
  </div>
</template>

<script>

export default {
  data() {
    return {
      images: []
    }
  }
  methods: {
    selectImage(image) {
      return require('@/assets/card-images/' + image + ".jpg")
    }
  }
}
</script>

The code above gives me a blank page. But when I fill the array with values, like below, I do get a result. But I don't want this obviously.

data() {
    return {
      images: [1,2,3,4,5,6,7,8,9,10]
    }
  }

I would want the code to render the images dynamically, no matter how many images I do have in my "assets/card-images" folder and without having to manually add values in the array each time I add a new image to the folder.

What am I doing wrong? I thank you for any advise.

UPDATE

Things I tried;

  • moving the "images" array from data to computed
  • moving "selectImage" method from methods to computed
  • moving both "images" array and "selectImage" method into computed

Either I get a blank page, or I get the same result as before

mde86
  • 11
  • 3
  • It doesn't look like you're populating the images array anywhere. Your v-for loop will loop 0 times because the array has size 0 – James Whiteley May 02 '19 at 08:47
  • Thanks James for your answer. Yes, i understand that the array is empty, but what code I need to write to populate that array, without having to manually add an image name each time a new image gets added in the "assets/card-images" folder. Do you have an idea? – mde86 May 02 '19 at 09:05
  • 1
    Hmm... maybe, instead of putting `images` in `data`, put it in the `computed` section (read about that [here](https://vuejs.org/v2/guide/computed.html#Computed-Properties) if you're unsure about it), and do something like the thread you linked to suggested - something like `require.context('@/assets/card-images/, false, /\.jpg$/)` – James Whiteley May 02 '19 at 09:13
  • I tried that now. Doesn't seem to do anything different than before. – mde86 May 02 '19 at 09:48

2 Answers2

-1

Use a requireAll method to get an array, and your count. Or a custom loader.

How to load all files in a directory using webpack without require statements

node.js require all files in a folder?

Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
-1

I don't think I was clear enough with my comments, so I'll explain what I meant with an example; you should be able to apply this to your use case with minimal effort. My src file structure and a screenshot of the result are also at the bottom of this answer.


The template I've made is basically the same as yours (without the extra divs with classes):

<template>
  <div>
    <div v-for="image in images" :key="image">
      <img :src="selectImage(image)" :alt="image" />
    </div>
  </div>
</template>

Here's my script. I'll go through it all below:

<script>
  export default {
    name: 'app',
    computed: {
      images: function() {
        const x = require.context('@/assets/card-images/', true, /\.png$/)
        return this.importAll(x)
      }
    },
    methods: {
      importAll(r) {
        return r.keys().map(x =>
          x.substring(2, x.length) // remove "./" from file names
        )
      },
      selectImage(image) {
        return require('@/assets/card-images/' + image)
      }
    }
  }
</script>

computed

The computed section is where you define your dynamically generated or computed values. Since you want your images to be dynamically generated, I've made images a computed function (can probably just be a value, you can play around with that).

All that images does is it uses require.context to get a list of all of the .png images in my @/assets/card-images/ folder, and trims the first couple of characters from them.

methods

importAll just retrieves and trims the image names. I've done this because otherwise, it'll think the images are at @/assets/card-images/./xxxxx.png - there's probably a better way of doing this but it works well enough.

selectImage gets an image from the file name you pass in (if it exists). If the image name doesn't exist, this will break but that shouldn't happen with how this is implemented.


Note: You can technically shorten the v-for loop by putting it directly on the img tag if you really want to, though I'd argue this is less readable:

<template>
  <div>
    <img v-for="image in images"
         :key="image"
         :src="selectImage(image)"
         :alt="image" />
  </div>
</template>

Here is my src folder structure. It doesn't matter what the images are called, as long as they have the same extension as you're using in your script tag:

folder structure

Here is what the code prints out (all of the images are just copies of the Vue logo):

output


EDIT

If you want to keep your initial images array, you can move the computed stuff into the lifecycle method mounted or created (depending on your use-case). Read more about lifecycle methods here or here. Here's what my component would look like with the calculations in mounted:

<template>
  <div>
    <div v-for="image in images" :key="image">
      <img :src="selectImage(image)" :alt="image" />
    </div>
  </div>
</template>

<script>
  export default {
    name: 'app',
    data() {
      return {
        images: []
      }
    },
    mounted() {
      const x = require.context('@/assets/card-images/', true, /\.png$/)
      this.images = this.importAll(x)
    },
    methods: {
      importAll(r) {
        return r.keys().map(x =>
          x.substring(2, x.length) // remove "./" from file names
        )
      },
      selectImage(image) {
        return require('@/assets/card-images/' + image)
      }
    }
  }
</script>
tony19
  • 125,647
  • 18
  • 229
  • 307
James Whiteley
  • 3,363
  • 1
  • 19
  • 46
  • Hi James, Thanks for this. Your solution works exactly like I was hoping for. Thank you again for explaining it very clearly! It helped me a lot! And sorry for not understanding what you meant from the start. I'm still in a learning process, so it's not always immediately clear to me. – mde86 May 08 '19 at 07:50