Trying to load an image inside a assets/img folder in a v-for, but the images does not load - vuex

I'm trying to load the images in the assets/img folder in my vue3 project using a v-for inside a div, but they are not loading, just display my alt.
So, I have a vue component what will display a title and a paragraph and also a image. The images are in the assets/img folder, I'm getting the path of the image with a store that I created. When I try to just put the path of the image like this: src="../assets/img/img2.jpg" the images renders.
Here is 1 of my state in the store.js:
content: [
{
headline: 'Teste',
paragraph: 'this is a paragraph',
img: '#/assets/img/img.jpg'
},]
Here is my template using the v-for:
<template>
<div>
<div
class="slider"
v-for="item, i in content"
:key='i'
>
<h1>{{item.headline}}</h1>
<p>{{item.paragraph}}</p>
<img
:src="item.img"
alt="test"
/>
</div>
</div>
</template>
My setup:
setup () {
const store = useStore()
const content = store.getters.getContent
return { content }
}
I tryed to use :src="require(item.img)" but i got a webpack error by doing this:
Uncaught Error: Cannot find module '#/assets/img/img.jpg' webpackEmptyContext components sync:2
Also tryed to point the folder of the images in the src, :src="#/assets/img + item.img", but it didn't work.

I met this question before.
Maybe the following code could help you.
// create a function in util.js
export const getSrc = ( name ) => {
const path = `/src/assets/img/${name}`
const modules = import.meta.globEager('/src/assets/img/*.jpg')
return modules[path].default
}
// use getSrc in someItem.vue
import { getSrc } from '#/util/util.js'
content: [
{
headline: 'Teste',
paragraph: 'this is a paragraph',
img: getSrc('img.jpg')
}
]

Related

Nuxt 3 how to use dynamic variable for img src

So I'm trying to get an img url from an object and setting is as img src. But it's not working and I do not know why.
My variable looks like this:
const props = defineProps({
about: {
title: String,
text: String,
img: String,
leftSide: Boolean
}
})
const cardImg = '~assets/images/' + props.about.img // prints ~assets/images/image.png
this is what i've tried:
<img :src="cardImg" />
And I've tried to use require() as well:
<img :src="require(cardImg)" />
But when I use require() I am getting this error:
Cannot find module '~assets/images/image.png' Require stack: -
The only way I can set an img src is like this:
<img src="~assets/images/image.png" />
But I want the src to be a variable so its dynamic, do you guys know what I am doing wrong
this is the parent component
<section v-for="info in aboutInfo">
<about-card :about="info"></about-card>
</section>
<script setup>
const aboutInfo = [
{
title: "some title",
text: "some text",
img: "image.png",
leftSide: true
}
]
</script>
Assets folder is for files that can marge with bundle files like css, js. svg. For images, use folder public. If you create folder images there, just use path /images/image.png without /public/ prefix.
<img :src="'/images/image.png'" />

Vue 3 Vite - dynamic image src

I'm using Vue 3 with Vite. And I have a problem with dynamic img src after Vite build for production. For static img src there's no problem.
<img src="/src/assets/images/my-image.png" alt="Image" class="logo"/>
It works well in both cases: when running in dev mode and after vite build as well. But I have some image names stored in database loaded dynamically (Menu icons). In that case I have to compose the path like this:
<img :src="'/src/assets/images/' + menuItem.iconSource" />
(menuItem.iconSource contains the name of the image like "my-image.png").
In this case it works when running the app in development mode, but not after production build. When Vite builds the app for the production the paths are changed (all assests are put into _assets folder). Static image sources are processed by Vite build and the paths are changed accordingly but it's not the case for the composed image sources. It simply takes /src/assets/images/ as a constant and doesn't change it (I can see it in network monitor when app throws 404 not found for image /src/assets/images/my-image.png).
I tried to find the solution, someone suggests using require() but I'm not sure vite can make use of it.
Update 2022: Vite 3.0.9 + Vue 3.2.38
Solutions for dynamic src binding:
1. With static URL
<script setup>
import imageUrl from '#/assets/images/logo.svg' // => or relative path
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
2. With dynamic URL & relative path
<script setup>
const imageUrl = new URL(`./dir/${name}.png`, import.meta.url).href
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
3.With dynamic URL & absolute path
Due to Rollup Limitations, all imports must start relative to the importing file and should not start with a variable.
You have to replace the alias #/ with /src
<script setup>
const imageUrl = new URL('/src/assets/images/logo.svg', import.meta.url)
</script>
<template>
<img :src="imageUrl" alt="img" />
</template>
2022 answer: Vite 2.8.6 + Vue 3.2.31
Here is what worked for me for local and production build:
<script setup>
const imageUrl = new URL('./logo.png', import.meta.url).href
</script>
<template>
<img :src="imageUrl" />
</template>
Note that it doesn't work with SSR
Vite docs: new URL
Following the Vite documentation you can use the solution mentioned and explained here:
vite documentation
const imgUrl = new URL('./img.png', import.meta.url)
document.getElementById('hero-img').src = imgUrl
I'm using it in a computed property setting the paths dynamically like:
var imagePath = computed(() => {
switch (condition.value) {
case 1:
const imgUrl = new URL('../assets/1.jpg',
import.meta.url)
return imgUrl
break;
case 2:
const imgUrl2 = new URL('../assets/2.jpg',
import.meta.url)
return imgUrl2
break;
case 3:
const imgUrl3 = new URL('../assets/3.jpg',
import.meta.url)
return imgUrl3
break;
}
});
Works perfectly for me.
The simplest solution I've found for this is to put your images in the public folder located in your directory's root.
You can, for example, create an images folder inside the public folder, and then bind your images dynamically like this:
<template>
<img src:="`/images/${ dynamicImageName }.jpeg`"/>
</template>
Now your images should load correctly in both development and production.
Please try the following methods
const getSrc = (name) => {
const path = `/static/icon/${name}.svg`;
const modules = import.meta.globEager("/static/icon/*.svg");
return modules[path].default;
};
In the context of vite#2.x, you can use new URL(url, import.meta.url) to construct dynamic paths. This pattern also supports dynamic URLs via template literals.
For example:
<img :src="`/src/assets/images/${menuItem.iconSource}`" />
However you need to make sure your build.target support import.meta.url. According to Vite documentation, import.meta is a es2020 feature but vite#2.x use es2019 as default target. You need to set esbuild target in your vite.config.js:
// vite.config.js
export default defineConfig({
// ...other configs
optimizeDeps: {
esbuildOptions: {
target: 'es2020'
}
},
build: {
target: 'es2020'
}
})
All you need is to just create a function which allows you to generate a url.
from vite documentation static asset handling
const getImgUrl = (imageNameWithExtension)=> new URL(`./assets/${imageNameWithExtension}`, import.meta.url).href;
//use
<img :src="getImgUrl(image)" alt="...">
Use Vite's API import.meta.glob works well, I refer to steps from docs of webpack-to-vite. It lists some conversion items and error repair methods. It can even convert an old project to a vite project with one click. It’s great, I recommend it!
create a Model to save the imported modules, use async methods to dynamically import the modules and update them to the Model
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
const assets = import.meta.glob('../assets/**')
Vue.use(Vuex)
export default new Vuex.Store({
state: {
assets: {}
},
mutations: {
setAssets(state, data) {
state.assets = Object.assign({}, state.assets, data)
}
},
actions: {
async getAssets({ commit }, url) {
const getAsset = assets[url]
if (!getAsset) {
commit('setAssets', { [url]: ''})
} else {
const asset = await getAsset()
commit('setAssets', { [url]: asset.default })
}
}
}
})
use in .vue SFC
// img1.vue
<template>
<img :src="$store.state.assets['../assets/images/' + options.src]" />
</template>
<script>
export default {
name: "img1",
props: {
options: Object
},
watch: {
'options.src': {
handler (val) {
this.$store.dispatch('getAssets', `../assets/images/${val}`)
},
immediate: true,
deep: true
}
}
}
</script>
My enviroment:
vite v2.9.13
vue3 v3.2.37
In vite.config.js, assign #assets to src/assets
'#assets': resolve(__dirname, 'src/assets')
Example codes:
<template>
<div class="hstack gap-3 mx-auto">
<div class="form-check border" v-for="p in options" :key="p">
<div class="vstack gap-1">
<input class="form-check-input" type="radio" name="example" v-model="selected">
<img :src="imgUrl(p)" width="53" height="53" alt="">
</div>
</div>
</div>
</template>
<script>
import s1_0 from "#assets/pic1_sel.png";
import s1_1 from "#assets/pic1_normal.png";
import s2_0 from "#assets/pic2_sel.png";
import s2_1 from "#assets/pic2_normal.png";
import s3_0 from "#assets/pic3_sel.png";
import s3_1 from "#assets/pic3_normal.png";
export default {
props: {
'options': {
type: Object,
default: [1, 2, 3, 4]
}
},
data() {
return {
selected: null
}
},
methods: {
isSelected(val) {
return val === this.selected;
},
imgUrl(val) {
let isSel = this.isSelected(val);
switch(val) {
case 1:
case 2:
return (isSel ? s1_0 : s1_1);
case 3:
case 4:
return (isSel ? s2_0 : s2_1);
default:
return (isSel ? s3_0 : s3_1);
}
}
}
}
</script>
References:
Static Asset Handling of Vue3
Memo:
About require solution.
"Cannot find require variable" error from browser. So the answer with require not working for me.
It seems nodejs >= 14 no longer has require by default. See this thread. I tried the method, but my Vue3 + vite give me errors.
In Nuxt3 I made a composable that is able to be called upon to import dynamic images across my app. I expect you can use this code within a Vue component and get the desired effect.
const pngFiles = import.meta.glob('~/assets/**/*.png', {
//#ts-ignore
eager: true,
import: 'default',
})
export const usePNG = (path: string): string => {
// #ts-expect-error: wrong type info
return pngFiles['/assets/' + path + '.png']
}
sources
If you have a limited number of images to use, you could import all of them like this into your component. You could then switch them based on a prop to the component.

<b-form-file> does not keep the filename visible when switched to other components

I am new to Vue and using Bootstrap view for my form.
I am using b-form-file for uploading a file in component say Component2.vue and I am able to browse my local filesystem and select the file. The filename is seen in the b-form-file after the selection.
When I visit another component(Component1.vue) via the UI and come back to Component2.vue, the filename is not visible in the browse anymore. Although I have used v-model to bind it to the file and the bind is happening but the filename is not displayed in the b-form-file.
I want to keep the filename visible in the b-form-file as it is binded to 'file' throughout the session.
I am using b-form-file like this:
<form>
<b-form-file
id="form-model"
v-model="file"
required
placeholder="Choose a model..."
></b-form-file>
<form>
<script>
export default {
data() {
return { file: null }
}
}
</script>
well I was using tab view to display <b-form-file> and encountered the same behavior. As I was not using <router-view> so the <keep-alive> solution did not worked for me. I found a nice little trick by dynamically setting the placeholder prop of the <b-form-file> component to display the filename. The following code is pretty self-explanatory I think.
<template>
<b-form-file
v-model="file"
id="file"
:placeholder="fileName"
drop-placeholder="Drop file here..."
accept=".xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
/>
</template>
<script>
import {
BFormFile,
} from "bootstrap-vue";
export default {
components: {
BFormFile,
},
data() {
return {
file: null,
fileName: "Choose a .xlsx file or drop it here...",
};
},
watch: {
file(newVal, _) {
this.fileName = newVal.name;
},
},
};
</script>

retrieve content from markdown file and convert it to valid HTML code in vuejs

I want to create a documentation page and have some markdown files which represent the main content. I have a navigation sidebar where I can select the specific content.
When clicking on a navigation item I need to read the content from a markdown file. I have a method that returns me the required path but I don't know how to read the file.
Lastly I took marked to render the markdown syntax to HTML code.
I created a small example that shows what is missing
https://codesandbox.io/s/006p3m1p1l
Is there something I can use to read the markdown content?
Use VueResource to retrieve the content from your markdown file.
Import the VueResource, and add it using Vue.use method (main.js):
import Vue from "vue";
import App from "./App";
import VueResource from "vue-resource";
Vue.config.productionTip = false;
Vue.use(VueResource);
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
Then use this.$http.get() method it within your App.vue file to retrieve the markdown file conent.
You can use markdown parsing library, like Showdown.js, wrapped within a vue.js method, directive or filter.
See: https://github.com/showdownjs/showdown and http://showdownjs.com/
There is also vuejs component wrapper for Showdown:
See: https://github.com/meteorlxy/vue-showdown and https://vue-showdown.js.org/
In your case that should look something like this ( using vue-showdown):
<template>
<div id="app"><VueShowdown :markdown="fileContent"></VueShowdown></div>
</template>
<script>
import VueShowdown from "vue-showdown";
export default {
name: "App",
components: VueShowdown,
data: function() {
return {
fileContent: null,
fileToRender:
"https://gist.githubusercontent.com/rt2zz/e0a1d6ab2682d2c47746950b84c0b6ee/raw/83b8b4814c3417111b9b9bef86a552608506603e/markdown-sample.md",
rawContent: null
};
},
created: function() {
// const fileToRender = `./assets/documentation/general/welcome.md`;
//const rawContent = ""; // Read the file content using fileToRender
// this.fileContent = "### marked(rawContent) should get executed";
this.getContent();
},
methods: {
getContent() {
this.fileContent = "rendering ";
// var self;
this.$http.get(this.fileToRender).then(
response => {
// get body data
this.fileContent = response.body;
},
response => {
// error callback
this.fileContent = "An error ocurred";
}
);
}
}
};
</script>
Check in sandbox: https://codesandbox.io/s/poknq9z6q
If your markdown file load is one time thing, then you could import it data, just like you import the components, js files and libraries:
<template>
<div id="app"><VueShowdown :markdown="fileContent"></VueShowdown></div>
</template>
<script>
import VueShowdown from "vue-showdown";
import MarkDownData from './assets/documentation/general/welcome.md';
export default {
name: "App",
components: VueShowdown,
data: function() {
return {
fileContent: null,
rawContent: null
};
},
created: function() {
// const fileToRender = `./assets/documentation/general/welcome.md`;
//const rawContent = ""; // Read the file content using fileToRender
// this.fileContent = "### marked(rawContent) should get executed";
this.getContent();
},
methods: {
getContent() {
this.fileContent = MarkDownData;
}
}
};
</script>
See: https://codesandbox.io/s/xpmy7pzyqz
You could also do it with a combination of html-loader, markdown-loader & v-html.
First you need to install the loaders:
npm i html-loader markdown-loader
Then declare a computed property that returns an array with the names of the markdown files.
In data - add showContent and set the wanted default value - the init markdown file that gets loaded.
Then in the template - loop through the array and set the wanted markdown file on click.
Then finally, you can load your markdown files with a combination of v-html and template literals.
Example below:
<template>
<div class="home">
<h1>
Markdown files
</h1>
<ul>
<li
v-for="item in docs"
:key="item"
#click="shownContent = item"
>
{{ item }}
</li>
</ul>
<div v-html="require(`!!html-loader!markdown-loader!../assets/docs/${shownContent}.md`)"></div>
</div>
</template>
<script>
export default {
name: 'Home',
data() {
return {
shownContent: 'doc1',
}
},
computed: {
docs() {
return [
'doc1',
'doc2',
'doc3',
]
},
},
}
</script>
This way it's important to note, that the name in the array has to be the same as the markdownfile.
I followed the example as mentioned above. I put the code in a component, not App.vue
https://codesandbox.io/s/xpmy7pzyqz?file=/src/App.vue
I get the following error
[Vue warn]: Invalid prop: type check failed for prop "markdown". Expected String with value "[object Object]", got Object

Can't load images from relative path with Vue V-Lazy-Image Plugin

I'm using this plugin to create a portfolio gallery on a single page components application but I'm stuck loading the images from a relative path, it works fine if I use a full URL path.
When I use the full URL the image Loads and loose the blur as expected, instead when I use a relative path the image load but it keeps blurred and it doesn't load the class .v-lazy-image-loaded into the image tag. Any Ideas? here is the code.
LazyImage.vue
<template>
<v-lazy-image :src="src" />
</template>
<script>
import VLazyImage from 'v-lazy-image'
export default {
props: {
// HERE I TRIED TO USE A FUNCTION ALSO WITH NO LUCK
// src: Function
src: String
},
components: {
VLazyImage
},
// UPDATE: I added a method to test here.
methods: {
consoleSuccess (msg) {
console.log(msg)
}
}
}
</script>
Portfolio.vue
<template>
<section id="portfolio">
// I'M TRYING THIS TWO OPTIONS
// UPDATED: I Added here the events calls and its just calling the intersect but the load.
<v-lazy-image src="../assets/img/innovation.jpg" alt="alternate text" #load="consoleSuccess('LOADED!')" #intersect="consoleSuccess('INTERSECS!')"></v-lazy-image> <-- this just work with full url ex. http://cdn...
<v-lazy-image :src="require('../assets/img/innovation.jpg')" alt="alternate text"></v-lazy-image> <-- this loads the image but remains blured
</section>
</template>
<script>
import VLazyImage from '#/components/lazyImage'
export default {
components: {
VLazyImage
}
}
</script>
UPDATE
I added a method to test and realized that just the #intersect event it's called and the #load won't fire at all with relative paths.
I noticed this was caused by bug in the component code:
if (this.$el.src === this.src) {
should be replaced by
if (this.$el.getAttribute('src') === this.src) {
I've sent pull request for this.