Lazyloading with intersection observer when sorting - lazy-loading

I output a bunch of images, each wrapped in an intersection observer using svelte-intersection-observer:
<script>
import IntersectionObserver from "svelte-intersection-observer";
let elements = {};
let intersects = {};
export let posts;
</script>
<section id="posts">
{#each posts as p}
<IntersectionObserver
once
element={elements[p]}
bind:intersecting={intersects[p]}
>
<div>
<img
bind:this={elements[p]}
src={intersects[p] ?
`/${p}/thumb.jpg`
:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='}
loading="lazy"
alt=""
width="350"
height="150"
/>
</div>
</IntersectionObserver>
{/if}
{/each}
</section>
On the page, I also offer links to sort the posts array (e.g., random order, by id ascending/descending).
The intersection observer works well on first page load and when scrolling.
But as soon as the order of the posts array is changed (on the fly, simply by sorting the array), the intersect event is not fired for those images that are now in the viewport.
When I scroll down, images are loaded. But the ones that are at the top are not.

Interesting. I was able to solve this with a keyed each loop:
{#each posts as p (p)}
<IntersectionObserver ... />
{/each}

Related

Vue Components Losing Image When Rerendered

I have a sidebar component located in a view-router view component that renders several child component buttons. It renders the child components using v-for loops.
<div v-for="type in this.facebookLightButtons" :key="type">
<SidebarButton :type="type"/>
</div>
<div v-for="type in this.facebookButtons" :key="type">
<SidebarButton :type="type"/>
</div>
facebookLightButtons is simply an array of strings containing the text of the button (which is also the name of the image source of the button). The result is the following:
The corresponding code in the SidebarButton components are:
<template>
<div>
<router-link :to="'/' + capitalizeFirstLetter(type)" class="sidebar-button">
<table>
<tr>
<td>
<img class="sidebar-button-icon" :src="getImage" />
</td>
<td>
<p>{{capitalizeFirstLetter(type)}}</p>
</td>
</tr>
</table>
</router-link>
</div>
</template>
and
computed: {
getImage() {
return require('#/assets/images/SidebarIcons/' + this.type + '.png');
}
}
When I change to a new view-router view and then return to the home page where this sidebar is located, the images are all missing:
I am trying to figure out how to keep the images there when returning to the home page.
Things I have tried:
Clearing and setting the image source in the SidebarButton components in the created, beforeMount, mounted, activated, deactivated, updated, and unmounted lifecycle hooks (non of which seemed to work).
Clearing the image, waiting for 2000 milliseconds using setTimeout() and then setting the image to the correct image source
Wrapping both the contents of the SidebarButton template as well as the v-for loop in the parent component with a <keep-alive> tag
Hard coding each SidebarButton component instead of using a v-for loop
It seems like something must be missing. What can I do to ensure that these images stored locally on my computer not only show when first displayed, but also appear when the user returns to the homepage after entering a separate view-router view?

How to turn off drag slide in Vue agile

I am using vue-agile, and I have to show videos and images in it. The images work fine but for videos, whenever I drag the pointer to move to a certain position in the player, the slider drags and moves to the next slider. Is there any way to stop the drag/swipe feature. Thanks.
https://www.npmjs.com/package/vue-agile
As a workaround, I first watch the mouse events on the clicked element (#mousedown, #mouseup)
On mousedown, I set the swipe-distance prop to a very high pixel value in the example below I used 1,000,000,000 (Default is 50).
Doc: https://github.com/lukaszflorczak/vue-agile
This worked for my case.
<template>
<agile :dots="false" :infinite="false" :center-mode="true"
:nav-buttons="false" :swipe-distance="noSweep ? 1000000000 : 50" >
<div v-for="(card, index) in cards" :key="index">
<div
#mousedown="noSweep=true"
#mouseup="noSweep=false"
>
Card content - example
</div>
</agile>
</template>
<script>
export default {
data() {
return {
noSweep: false,
cards:['A','B','C']
}
}
}
</script>

Using v-model inside nested v-for

I am trying to use multiple carousel components inside card components with nested v-for loops but I’m having trouble figuring out the correct way to assign the carousel v-model so that it’s unique and doesn’t update all the carousels when the slide is changed which is what I currently have,
Here is the code I have so far:
<q-card
v-for="(item, index) in inventory"
:key="index"
style="width: 20rem;"
>
<q-card-section
class="q-pa-none text-white"
>
<q-carousel
animated
arrows
navigation
infinite
style="height: 15rem;"
v-model="slide" // What should this be assigned so that
>
<q-carousel-slide
v-for="(image, index) in item.images"
:key="index"
:name="index" //It works with the slide name and only updates the respective q-carousel
:img-src="image.url"
>
</q-carousel-slide>
</q-carousel>
</q-card-section>
</q-card>
slide is simply a data prop assigned to 0, this works but when I change the slide of one carousel all of the carousels change too.
Hopefully this makes sense, It’s a bit hard for me to explain it but let me know anything that needs clarification
Edit: Dropped the code in codepen here is the link: https://codepen.io/launchit-studio/pen/jOVrKzQ
The issue I am having is that the v-model affects all of the carousel slides not just the one that is clicked. I understand this is because the slide prop is shared by all the carousels but I am not sure of what to use in order for it to be 'independent'
Instead of using a single model slide for all the slides, use an array of models. (An object would work too).
data() {
return {
slide: 1, ❌
activeSlides: [] ✅
}
}
The index of the carousel in the v-for will also be used as the index in the array:
<q-carousel
animated
arrows
navigation
infinite
style="height: 15rem;"
v-model="activeSlides[index]"
>
Since each carousel's model needs to start at 1, you can initialize activeSlides accordingly:
created() {
const numCarousels = this.inventory.length;
this.activeSlides = Array(numCarousels).fill(1);
},
Here is the updated CodePen.

Why isn't the value of the object properties inserted here?

I started learning vue yesterday and I'm now fiddling around on the CLI3.
Currently I'm trying out the different approaches to inserting data into my markup.
Here, I basically want to make a "list of Lists".
This here is list1:
<template>
<div>
<ul v-for="item in items">
<li :text="item"></li>
</ul>
</div>
</template>
<script>
export default{
name:"list1",
data() {
return {
items: {
item1 : "itemA",
item2 : "itemB",
item3 : "itemC"
}
}
}
}
</script>
This is the list of lists:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
And this is inserted into myComplexView.vue inside views (im working with routing as well, though it doesnt work perfectly yet as you will see on the screenshot), which you can see here:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
</script>
This is the result Im getting:
https://imgur.com/H8BaR2X
Since routing doesnt work correctly yet, I had to enter the url into the browser manually. Fortunately, the site at least loaded that way as well, so I can tackle these problems bit by bit ^^
As you can see, the data was iterated over correctly by the v-for.
However, the data wasn't inserted in the text attribute of the li elements.
I'm a bit clueless about the cause though.
Maybe I'm not binding to the correct attribute? Vue is using its own naming conventions, based off standard html and jquery as far as I understood.
You've got this in your template:
<li :text="item"></li>
This will bind the text attribute to the value, outputting, e.g.:
<li text="itemA"></li>
You should be able to see this in the developer tools. In the picture you posted you hadn't expanded the relevant elements so the attributes can't be seen.
I assume what you want is to set the content. For that you'd either use v-text:
<li v-text="item"></li>
or more likely:
<li>{{ item }}</li>
Either of these will output:
<li>itemA</li>
On an unrelated note, I would add that this line will create multiple lists:
<ul v-for="item in items">
It's unclear if that's what you want. You're going to create 3 <ul> elements, each with a single <li> child. If you want to create a single <ul> then move the v-for onto the <li>.

How to avoid vue component redraw?

I have prepared tag input control in Vue with tag grouping. Templates includes:
<script type="text/x-template" id="tem_vtags">
<div class="v-tags">
<ul>
<li v-for="(item, index) in model.items" :key="index" :data-group="getGroupName(item)"><div :data-value="item"><span v-if="typeof model.tagRenderer != 'function'">{{ item }}</span><span v-if="typeof model.tagRenderer == 'function'" v-html="tagRender(item)"></span></div><div data-remove="" #click="remove(index)">x</div></li>
</ul>
<textarea v-model="input" placeholder="type value and hit enter" #keydown="inputKeydown($event,input)"></textarea>
<button v-on:click="add(input)">Apply</button>
</div>
</script>
I have defined component method called .getGroupName() which relays on other function called .qualifier() that can be set over props.
My problem: once I add any tags to collection (.items) when i type anything into textarea for each keydown .getGroupName() seems to be called. It looks like entering anything to textarea results all component rerender?
Do you know how to avoid this behavior? I expect .getGroupName to be called only when new tag is added.
Heres the full code:
https://codepen.io/anon/pen/bKOJjo?editors=1011 (i have placed debugger; to catch when runtime enters .qualifier().
Any help appriciated.
It Man
TL;DR;
You can't, what you can do is optimize to reduce function calls.
the redraws are dynamic, triggered by data change. because you have functions (v-model and #keydown) you will update the data. The issue is that when you call a function: :data-group="getGroupName(item)" it will execute every time, because it makes no assumptions on what data may have changed.
One way of dealing with is is setting groupName as a computed key-val object that you can look up without calling the function. Then you can use :data-group="getGroupName[item]" without calling the function on redraw. The same should be done for v-html="tagRender(item)"
Instead of trying to fight how the framework handles data input events and rendering, instead use it to your advantage:
new Vue({
el: '#app',
template: '#example',
data() {
return {
someInput: '',
someInputStore: []
};
},
methods: {
add() {
if (this.someInputStore.indexOf(this.someInput) === -1) {
this.someInputStore.push(this.someInput);
this.someInput = '';
}
},
}
});
<html>
<body>
<div id="app"></div>
<template id="example">
<div>
<textarea v-model="someInput" #keyup.enter.exact="add" #keyup.shift.enter=""></textarea>
<button #click="add">click to add new input</button>
<div>
{{ someInputStore }}
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</body>
</html>
Event modifiers modifying
In the example, you can see that I am using 4 different event modifiers in order to achieve the desired outcome, but I will focus on the combination of them here:
#keyup.enter.exact - allows control of the exact combination of system modifiers needed to trigger an event. In this case, we are looking for the enter button.
#keyup.shift.enter - this is the interesting bit. Instead of trying to hackily prevent the framework from firing on both events, we can use this (and a blank value) to prevent the event we added into #keyup.enter.exact from firing. I must note that ="" is critical to the whole setup working properly. Without it, you aren't giving vue an alternative to firing the add method, as shown by my example.
I hope this helps!