How to check if image is loaded in Vue/Nuxt? - javascript

New to Vue and Nuxt. I am trying to show skeletons before image is loaded completely.
Here's my attempt, skeleton shows but image is never loaded and onImageLoad is never called.
<template>
<div>
<img v-if="isLoaded" #load="onImgLoad" :src="me.img">
<div v-else class="skeleton"></div>
</div>
</template>
<script lang="ts">
export default {
props: {
me: Object,
},
data() {
return {
isLoaded: false,
}
},
methods: {
onImgLoad() {
console.log(` >> isLoaded:`, this.isLoaded)
return this.isLoaded = true
},
},
}
</script>
I have some broken image url to test fallback src, is it a problem? But I tried removing those broken links and it's still not working.
Example data:
export const me = {
name: 'David',
img: 'https//david.png', // Example broken > https://no.jpg
},
Please help me, what I am doing wrong?

Because isLoaded is false on the initial render, the img element is removed from the DOM tree. If it’s not in the DOM, the image src won’t be requested, ergo— no load event.
Switch to v-show. The img element will remain in the DOM, so the #load event will fire.

Related

Vue js. Images from list not displaying properly [duplicate]

I got a parent component which sends a data object to the children component like this:
<child object-data=" url: 'url here', title: 'Title'"></child>
Then on my children component, I get this object data by doing:
<script>
export default {
props: [
'objectData'
]
}
</script>
Now, for some reason, I can use title without any problem like this {{ objectData.title }} and shows up.
But when it comes to the URL inside an img tag it's not rendering the image.
I attempted doing the following:
<img :src="objectData.url"/> <--- not rendering
<img v-bind:src="objectData.url"/> <--- not rendering
<img v-bind:src="require(objectData.url)"/> <-- throws a warning error because it's not a path but an object I guess.
<img v-bind:src="{objectData.url}"/> <--- throws an error
<img v-bind:src="{{objectData.url}}"/> <--- throws an error
When I check on the dom element, it doesn't even contain a src attribute afterwards.
If I write down the URL without an object, it works.
<img v-bind:src="src/assets/images/icon.png"/>
But I want my URL to come from a parent component.
Any ideas on how to solve this? I added the url-loader on my webpack file:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader'
}
I also attempted returning objectData.url from a computed/methods fuction:
computed: {
getImageUrl: function() {
return objectData.url;
}
}
And then use it like :src=“getImageUrl” or :src=“{{getImageUrl}}” and I wasn’t lucky either.
I faced the same issue and i fixed it by using require function :
in the parent component App.vue :
<carousel-posts :posts="posts" />
export default {
name: "app",
data() {
return {
posts: [
{
img: require("./assets/logo.png"),
title: "Title 1",
subTitle: "Sub Title 1",
body:"lorem ipsum ..."
}
...
]
};
}
...
}
in the child component i loop through the posts and bind the image src as follows :
<div class="card-body" v-for="(post,idx) in posts" >
<img class="card-img" :src="post.img" />
...
</div>
<script>
export default {
props: ["posts"],
...
So in your case you should have something like :
<child :object-data="objectData"></child>
...
data(){
return{
dataObject:{
url: require('./assets/someimg.png'),
title: 'Title'
}
}
}
Update :
my project tree :
src
|_assets
| |_logo.png
|_components
| |_CarouselPosts.vue
|_App.vue
Two alternatives:
Static links that vue resolves. But they're src-based and don't work with webpack / file-loader:
<img :src="'../assets/filename.png'"/>
Works with webpack. Note the ".default" at the end:
<img :src="require('../assets/filename.png').default"/>
Documentation relevant effective Nov 2019:
https://github.com/vuejs/vue-loader/issues/1612

dynamic script loading Nuxt route change casing crushing

I create a vue component to load scripts for ads dynamically, and when the route changes, the component should destroy herself and change back when the route enters.
So when the route leaves, there is no problem, but when I go to a page and then return to the same page, the ads do not appear anymore.
<template>
<div style="display: none;">
<slot />
</div>
</template>
<script>
export default {
props: {
async: { type: Boolean, default: true },
location: { type: String, default: '' }, // or elemnt id which will select the sapce
src: { type: String, required: false, default: '' }
},
data () {
return {
script: null
}
},
beforeDestroy () {
console.log('remove')
if (this.script) {
if (!this.location) {
this?.$el?.removeChild(this?.script)
}/** else {
const tag = document.querySelector(this.location)
tag?.parentNode?.removeChild(this.script)
} */
}
},
mounted () {
console.log('add loadjs')
this.loadJs()
},
methods: {
loadJs () {
const scriptTag = document.createElement('script')
console.log(this.$el)
scriptTag.async = this.async || true
// console.log(Object.keys(this.$slots.default[0]))
if (!this.src && this?.$slots?.default) { // when script is empty
scriptTag.text = this?.$slots?.default[0]?.text
} else { scriptTag.src = this.src }
if (!this.location) { // when location is not set load after element
this.$el.appendChild(scriptTag)
} else {
const location = document.querySelector(this.location)
location.appendChild(scriptTag)
}
this.script = scriptTag
}
}
}
</script>
the service for the ads is
<template>
<div>
ads
<div :id="id">
<script-component>
googletag.cmd.push(
function() {
googletag.display('{{ id }}');
}
);
</script-component>
</div>
</div>
</template>
<script>
const scriptLoadder = () => import('~/components/script/scriptLoadder')
export default {
components: {
'script-component': scriptLoadder
},
props: {
id: { type: String, required: true }
}
}
</script>
I have another similar component for another ads service that works on server load (when I enter the home page for the first time, this works fine). The issue is when the route changes, and then I go back to the same route. Both services of ads are just not appearing.
this is how I am using the component
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div>
<google-ads id="ATF_LB_1" :key="$route.fullPath + Math.random().toString(16) " />
or
<google-ads id="ATF_LB_1" :key="$route.fullPath" />
<script-component>
{{ pageScript.HP }}
</script-component>
or
<script-component :key="$route.fullPath">
{{ pageScript.HP }}
</script-component>
or
<script-component :key="$route.fullPath + Math.random().toString(16) ">
window.alert('test on page load works when going back not')
</script-component>
</div>
</template>
So the answer is incredibly annoying. The problem was not with my code but with the provider's code. The code they gave me was intended to run on SSR only site. The only thing to pay attention to this code to "fix" the behavior is to unmount the component or key="$router.fullpath" but this will cause another issue depend on your component place. When you are inside the component and change the page, Nuxt will run the following lifecycle destroy the component and the mount on the new page (which is a problem) and then destroyed it again. This will cause latency unless you add async or defer.
So to summarize, the problem was with the provider code; this component will load the script tag inside the template where you need it. I will open the npm repo for this component that works with Nuxt and create a git issue of the memory leak in a component lifecycle.

Iframe load event fires twice

The function bound to (#load="myFunction") fires once when the iframe is created and once when it's actually loaded.
Why does it fire when the iframe is created, and how to avoid it?
<template>
<transition name="modal">
<div v-if="webviewOpen">
<transition name="content" appear>
<div v-if="webviewOpen">
<transition name="iframe">
<iframe
v-show="showIframe"
:src="webviewUrl"
#load="iframeIsLoaded"
/>
</transition>
</div>
</transition>
</div>
</transition>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
showIframe: false
}
},
computed: {
...mapState({
webviewOpen: state => state.webview.open,
webviewUrl: state => state.webview.url
})
},
watch: {
webviewOpen () {
setTimeout(() => {
this.showIframe = true
}, 1000)
}
},
methods: {
iframeIsLoaded () {
console.log('iframe loaded')
}
}
}
</script>
It seems it may be a web kit issue with firing twice ( safari/chrome ) as it fires when added to DOM (v-if on parent) and when the content is loaded. It may help to add .once modifier to the #load.once="myFunction()"
As #tao suggested something else was interefering, namely Nuxt Lazy Load package. So if anyone uses this package AND finds out iframes onload event mysteriously fires twice AND finds this thread:
Add iframes: false in your nuxt.config.js when importing the package inside the modules section. Problem solved!
We know from your linked answer that Chrome shows this issue unless you attach the listener after the iframe is appended to the DOM. To do this, we could take advantage of Vue's lifecycle hooks. We want it to happen after the iframe is added to the DOM, but before it has a chance to load, so we'll use the updated hook.
I don't experience the problem in any of my browsers, so I unfortunately can't really test it for you. Test for yourself and see if something like this fixes it for you:
<template>
<label for="show">Show iFrame</label>
<input id="show" type="checkbox" v-model="webviewOpen">
<div v-if="webviewOpen">
<iframe
src="https://motherfuckingwebsite.com/"
#load="iframeLoadHelper"
frameborder="0"
></iframe>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
webviewOpen: false,
iframeReady: false
};
},
methods: {
// Helper method, just to keep 'if' outside of the logic of your potentially
// complex #load method
iframeLoadHelper() {
if (this.iframeReady) return this.iframeLoaded();
else return; // do nothing
},
// The real load method
iframeLoaded() {
console.log('iframe loaded');
}
},
updated() {
console.log('changing ready-state');
this.iframeReady = this.webviewOpen;
}
};
</script>
<style>
:root { font-family: sans-serif; }
</style>

Image in VueJS component is not loading

I got a parent component which sends a data object to the children component like this:
<child object-data=" url: 'url here', title: 'Title'"></child>
Then on my children component, I get this object data by doing:
<script>
export default {
props: [
'objectData'
]
}
</script>
Now, for some reason, I can use title without any problem like this {{ objectData.title }} and shows up.
But when it comes to the URL inside an img tag it's not rendering the image.
I attempted doing the following:
<img :src="objectData.url"/> <--- not rendering
<img v-bind:src="objectData.url"/> <--- not rendering
<img v-bind:src="require(objectData.url)"/> <-- throws a warning error because it's not a path but an object I guess.
<img v-bind:src="{objectData.url}"/> <--- throws an error
<img v-bind:src="{{objectData.url}}"/> <--- throws an error
When I check on the dom element, it doesn't even contain a src attribute afterwards.
If I write down the URL without an object, it works.
<img v-bind:src="src/assets/images/icon.png"/>
But I want my URL to come from a parent component.
Any ideas on how to solve this? I added the url-loader on my webpack file:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader'
}
I also attempted returning objectData.url from a computed/methods fuction:
computed: {
getImageUrl: function() {
return objectData.url;
}
}
And then use it like :src=“getImageUrl” or :src=“{{getImageUrl}}” and I wasn’t lucky either.
I faced the same issue and i fixed it by using require function :
in the parent component App.vue :
<carousel-posts :posts="posts" />
export default {
name: "app",
data() {
return {
posts: [
{
img: require("./assets/logo.png"),
title: "Title 1",
subTitle: "Sub Title 1",
body:"lorem ipsum ..."
}
...
]
};
}
...
}
in the child component i loop through the posts and bind the image src as follows :
<div class="card-body" v-for="(post,idx) in posts" >
<img class="card-img" :src="post.img" />
...
</div>
<script>
export default {
props: ["posts"],
...
So in your case you should have something like :
<child :object-data="objectData"></child>
...
data(){
return{
dataObject:{
url: require('./assets/someimg.png'),
title: 'Title'
}
}
}
Update :
my project tree :
src
|_assets
| |_logo.png
|_components
| |_CarouselPosts.vue
|_App.vue
Two alternatives:
Static links that vue resolves. But they're src-based and don't work with webpack / file-loader:
<img :src="'../assets/filename.png'"/>
Works with webpack. Note the ".default" at the end:
<img :src="require('../assets/filename.png').default"/>
Documentation relevant effective Nov 2019:
https://github.com/vuejs/vue-loader/issues/1612

Vue JS - Problem with computed property not updating

I am quite new with VueJS and I have been having trouble lately with some computed properties which do not update as I would like. I've done quite some research on Stack Overflow, Vue documentation and other ressources but i haven't found any solution yet.
The "app" is basic. I've got a parent component (Laundry) which has 3 child components (LaundryMachine). The idea is to have for each machine a button which displays its availability and updates the latter when clicked on.
In order to store the availability of all machines, I have a data in the parent component (availabilities) which is an array of booleans. Each element corresponds to a machine's availability.
When I click on the button, I know the array availibities updates correctly thanks to the console.log. However, for each machine, the computed property "available" does not update is I would want it to and I have no clue why.
Here is the code
Parent component:
<div id="machines">
<laundry-machine
name="AA"
v-bind:machineNum="0"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="BB"
v-bind:machineNum="1"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="CC"
v-bind:machineNum="2"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
</div>
</div>
</template>
<script>
import LaundryMachine from './LaundryMachine.vue';
export default {
name: 'Laundry',
components: {
'laundry-machine': LaundryMachine
},
data: function() {
return {
availabilities: [true, true, true]
};
},
methods: {
editAvailabilities(index) {
this.availabilities[index] = !this.availabilities[index];
console.log(this.availabilities);
}
}
};
</script>
Child component:
<template>
<div class="about">
<h2>{{ name }}</h2>
<img src="../assets/washing_machine.png" /><br />
<v-btn color="primary" v-on:click="changeAvailability">
{{ this.availability }}</v-btn>
</div>
</template>
<script>
export default {
name: 'LaundryMachine',
props: {
name: String,
machineNum: Number,
availableArray: Array
},
methods: {
changeAvailability: function(event) {
this.$emit('change-avlb', this.machineNum);
console.log(this.availableArray);
console.log('available' + this.available);
}
},
computed: {
available: function() {
return this.availableArray[this.machineNum];
},
availability: function() {
if (this.available) {
return 'disponible';
} else {
return 'indisponible';
}
}
}
};
</script>
Anyway, thanks in advance !
Your problem comes not from the computed properties in the children, rather from the editAvailabilities method in the parent.
The problem is this line in particular:
this.availabilities[index] = !this.availabilities[index];
As you can read here, Vue has problems tracking changes when you modify an array by index.
Instead, you should do:
this.$set(this.availabilities, index, !this.availabilities[index]);
To switch the value at that index and let Vue track that change.

Categories

Resources