CODEGURU

Vue.js и динамический src для картинок

Как обычно выглядит отображение картинки в компоненте vue?

<img src="@/assets/images/item1.jpg">

или

<img src="../assets/images/item1.jpg">

А теперь представим, что в нашем компоненте есть радиокнопки, и в зависимости от выбора пользователя, нам нужно менять картинку:

<template>
  <div>
    <div>
      <label v-for="item in items" :key="item">
        <input type="radio" :value="item" v-model="selectedItem">
        {{ item }}
      </label>
    </div>
    <!-- Здесь должна быть картинка -->
  </div>
</template>

<script>

export default {
  data () {
    return {
      selectedItem: "",
      items: ["Item1", "Item2", "Item3"]
    }
  }
}
</script>

Начинающий разработчик скорее всего скажет, что нет ничего проще. И напишет вот так:

<img :src="`../assets/images/${selectedItem.toLowerCase()}.jpg`" :alt="selecteditem">

Вроде бы всё правильно. Но если мы запустим этот код, то результат вас удивит. Картинки не будет. Почему же так происходит? И чтобы ответить на этот, вопрос нужно немного разобраться в том, как устроены однофайловые компоненты vue.

При обработке однофайловых компонентов vue используется webpack и плагин vue-loader. Именно благодаря их совместной работе мы можем наслаждаться поддержкой css-препроцессоров, автоматической перезагрузкой с сохранением стейта приложения и т.д. Они же отвечают и за обработку путей к нашим картинкам. Соответственно, после обработки, строка <img src="../assets/images/item1.jpg"> будет преобразована в рендер-функцию:

createElement('img', {
  attrs: {
    src: require('../assets/images/item1.jpg'),
    alt: 'Item1'
   }
})

Видите, путь к картинке преобразован в запрос модуля. И это отлично работает со статичными изображениями, путь к которым известен на момент компиляции. Когда же мы подставляем динамический путь, то webpack просто не успевает обработать его и понять, что нужно делать запрос модуля. Поэтому в итоге изображение не грузится.

Чтобы решить эту проблему достаточно просто помочь немного webpack, и сделать запрос модуля за него:

<img :src="require(`../assets/images/${selectedImage.toLowerCase()}.jpg`)" :alt="selectedImage">

Итоговый код нашего компонента, с учётом новых знаний, может выглядеть как-то так:

<template>
  <div>
    <div>
      <label v-for="item in items" :key="item">
        <input type="radio" :value="item" v-model="selectedItem">
        {{ item }}
      </label>
    </div>
    <img :src="itemImage" :alt="selectedItem">
  </div>
</template>

<script>

export default {
  data () {
    return {
      selectedItem: "",
      items: ["Item1", "Item2", "Item3"]
    }
  },
  computed: {
    itemImage() {
      if (!this.selectedImage) {
        return
      }
      const fileName = this.selectedImage.toLowerCase();
      return require(`../assets/images/${fileName}.jpg`);
    }
  }
}
</script>