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>