FRONTEND BLOG

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>