Reputation: 10946
I have an CurrentItem component, which shows an image and a description of some item. The item regularly changes based on events from the server. The problem I'm having is that when it does, the new item's image takes a few moments to load during which time the component just has a big blank space where the image is supposed to go, which does not look good.
What I would prefer instead is to keep showing the old item until the new item's image is loaded, and only at that point update the component. Is there a simple way to achieve that?
Some example code:
For the purposes of the question, you can think of an item as a simple JSON structure:
{
"id": "ABC123",
"description": "A fine yellow specimen",
"image": "//example.com/images/ABC123.jpeg"
}
The events from the server JSON over a WebSocket, and look something like this:
{ "nextItemId": "ABC124" }
When we receive one of those, we send a GET request to fetch the next item at //example.com/items/ABC124.json
.
Upvotes: 1
Views: 926
Reputation: 63059
Preload the image and wait for its onload
event to fire before setting the item
:
methods: {
onReceived(newItem) {
const image = new Image(); // Create new empty image
image.onload = () => { // CALLBACK: Will fire when image is done loading
this.item = newItem; // Will set the current item
this.isLoading = false; // Will stop animation
}
this.isLoading = true; // Start animation
image.src = newItem.image; // Begin loading
}
}
Once it's finished, the current item will be set. Here's a demo with a large image:
Vue.component('item', {
props: ['item'],
template: `
<div>
<div>ITEM (id: {{ item.id }})</div>
<div>{{ item.description }}</div>
<div><img :src="item.image" ref="img" width="100" height="100" /></div>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
isLoading: false,
item: {
"id": "ABC123",
"description": "A fine yellow specimen",
"image": "https://cdn.iconscout.com/icon/free/png-256/google-470-675827.png"
}
}
},
methods: {
simulateEvent() {
this.onReceived({
"id": "ZYX987",
"description": "A funky jaundiced specimen",
"image": "https://upload.wikimedia.org/wikipedia/commons/f/fa/Very_Large_Array_in_New_Mexico.jpg"
});
},
onReceived(newItem) {
this.isLoading = true;
const image = new Image();
image.onload = () => {
this.item = newItem;
this.isLoading = false;
}
image.src = newItem.image;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<item :item="item"></item>
<!-- Simulating events -->
<button @click="simulateEvent">
Simulate event
</button>
<span v-if="isLoading">[ Loading... ]</span>
</div>
Upvotes: 2
Reputation: 953
you can set a time out function until the image loads
.. codes to load new image
setTimeout(function() {
... your old image codes
... 5000 means 5Seconds
},5000);
.. codes to display new image
Upvotes: -1
Reputation: 10946
This is my own best shot at a solution.
Basically my idea is to have another copy of <CurrentItem />
in the DOM which has display:none;
and which holds the item that is about to be the current. Then, when the image in that component is loaded, we update our actual CurrentItem. Something like this:
<template>
<CurrentItem
:key="currentItemId"
:item="currentItem"
/>
<CurrentItem
:key="aboutToBecomeCurrentItemId"
:item="aboutToBecomeCurrentItem"
@imageLoaded="onImageReady"
style="display:none;"
/>
</template>
<script>
// ...
export default {
// ...
data() {
return {
currentItem = null,
aboutToBecomeCurrentItem = null
},
methods: {
onItemUpdated(newItem) {
this.aboutToBecomeCurrentItem = newItem;
},
onImageReady() {
this.currentItem = this.aboutToBecomeCurrentItem;
}
}
}
// ...
}
</script>
I think this would work, but I would love to know it if there are simpler or more idiomatic ways to achieve the same.
Upvotes: 1