简体   繁体   中英

How to create a dynamic background image for components in Vue using props?

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:

  1. How would you dynamically render a background image in Vue with props? Or is there a better way to do this?
  2. Why does binding inline styles not resolve the path (keeps using '@'), while adding it directly in the <script> tags does resolve the path?

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

Source

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.

Source

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM