Objective
I want to pass the path of an image as a prop to a component. I want the component to use the prop to dynamically generate the background image.
All my images in an assets folder in the Vue src folder. The path looks like '@/assets/images/subject/computers.jpeg'
Problem
No background image appears
This is what renders on the page:
However, nothing shows up
Strange Behaviour
For some reason, adding the exact same path name '@/assets/images/subject/computers.jpeg' in CSS works (Adding it in the <style> tags). It just doesn't work if it's v-bind inline style.
Here is what it looks like in my CSS
The problem with this though, is that it is not dynamically rendered CSS.
I inspected the element further and noticed some strange behavior. As you can see, the inline style is reading the image path as '@/assets/images/subject/computers.jpeg' (element.style in image below).
Whereas adding the path in CSS, changes '@/assets/images/subject/computers.jpeg' to 'http://localhost:8080/img/computers.7f3748eb.jpeg', which then correctly renders the background image.
Question
I suppose my question is twofold:
Solution
Okay, big thank you to tao & Bernard Borg for helping me wrap my head around this.
I was able to solve this using computed properties like so:
const imgSrc = computed(() => require(`@/assets/images/subject/${props.subject.image}`))
Because of the way VueLoader (which is default in Vue) resolves the paths, I couldn't store the '@' path in the prop itself. Instead, the prop is just the image/file name (ex. "computers.jpeg"), and the computed property with require() handles resolving the prepended path to the image.
Afterwards, I added the computed property to my binded inline style like so:
<div class="subject-wrapper" v-bind:style="{ backgroundImage: `url('${imgSrc}')` }">
Voila! It finally works.
TLDR
Pass only the image/file name as a prop, and prepend the path using require() in a computed property.
Cheers 🍻
When using Webpack (Vue-cli), the @
symbol is an alias for the src folder.
If the URL starts with @, it's also interpreted as a module request. This is useful if your webpack config has an alias for @, which by default points to /src in any project created by vue-cli
If you're using Vite, you probably have the following in vite.config.js;
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
...
}
}
VueLoader transforms asset urls when they're located in thee following html tags: video
, source
, img
, image
(svgs) and use
(svgs). Moreover, it also transforms asset urls when they're in the tag if you have VueLoader configured to use css-loader
.
All compiled CSS are processed by css-loader, which parses url() and resolves them as module requests.
This asset url transformation does not happen when you're v-binding a style property.
You can use this mechanism to pass transformed paths to a different component (for example, a component from a library). Despite this, you would still need to declare every single path as mentioned by @tao.
The relevant point here is that VueLoader has to read all the possible paths when compiling the app and resolve them.
<script setup lang="ts">
import BackgroundImageViewer from './components/BackgroundImageViewer.vue'
import { ref, onMounted } from "vue";
const hiddenImage = ref<HTMLImageElement>();
const thePath = ref();
onMounted(() => {
if (hiddenImage.value) {
thePath.value = hiddenImage.value.src;
hiddenImage.value.src = "";
}
});
</script>
<template>
<img src="@/assets/vue.svg" ref="hiddenImage" hidden />
<BackgroundImageViewer :path="thePath"/>
</template>
As @tao also mentioned, if the number of different images is too much you can opt to place the images in your project's public
folder.
Edit: You can also use the require
method with an if-else
tree, ternary
or switch
statement to get the relative path to your image (if the number of images that can be set as the background isn't too much - otherwise use the public
folder).
With Vite
you can use the vite-plugin-require
plugin to make require()
work.