Fix lazy loading skipping items

This commit is contained in:
Marco Realacci 2022-12-23 01:00:51 +01:00
parent 54453fea34
commit 0bd6f4461d
5 changed files with 69 additions and 57 deletions

View file

@ -0,0 +1,39 @@
<script>
export default {
name: 'IntersectionObserver',
props: {
sentinalName: {
type: String,
required: true,
},
},
data() {
return {
isIntersectingElement: false,
}
},
watch: {
isIntersectingElement: function (value) {
if (!value) return
this.$emit('on-intersection-element')
},
},
mounted() {
const sentinal = this.$refs[this.sentinalName]
const handler = (entries) => {
if (entries[0].isIntersecting) {
this.isIntersectingElement = true
}
else {
this.isIntersectingElement = false
}
}
const observer = new window.IntersectionObserver(handler)
observer.observe(sentinal)
},
}
</script>
<template>
<div :ref="sentinalName" class="w-full h-px relative" />
</template>

View file

@ -8,6 +8,7 @@ import LoadingSpinner from './components/LoadingSpinner.vue'
import PostCard from './components/PostCard.vue' import PostCard from './components/PostCard.vue'
import UserCard from './components/UserCard.vue' import UserCard from './components/UserCard.vue'
import Modal from './components/Modal.vue' import Modal from './components/Modal.vue'
import IntersectionObserver from './components/IntersectionObserver.vue'
import 'bootstrap-icons/font/bootstrap-icons.css' import 'bootstrap-icons/font/bootstrap-icons.css'
import './assets/dashboard.css' import './assets/dashboard.css'
@ -25,6 +26,7 @@ app.component("LoadingSpinner", LoadingSpinner);
app.component("PostCard", PostCard); app.component("PostCard", PostCard);
app.component("UserCard", UserCard); app.component("UserCard", UserCard);
app.component("Modal", Modal); app.component("Modal", Modal);
app.component("IntersectionObserver", IntersectionObserver);
app.use(router) app.use(router)
app.mount('#app') app.mount('#app')

View file

@ -38,21 +38,12 @@ export default {
this.loading = false; this.loading = false;
}, },
loadMore() { loadMore() {
if (this.loading || this.data_ended) return
this.start_idx += this.limit this.start_idx += this.limit
this.loadContent() this.loadContent()
}, },
scroll() {
window.onscroll = () => {
let bottomOfWindow = Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop) + window.innerHeight >= document.documentElement.offsetHeight - 5
if (bottomOfWindow && !this.data_ended) {
this.loadMore()
}
}
},
}, },
mounted() { mounted() {
this.scroll();
this.refresh(); this.refresh();
} }
} }
@ -86,6 +77,7 @@ export default {
<button v-if="(!data_ended && !loading)" @click="loadMore" class="btn btn-secondary py-1 mb-5" <button v-if="(!data_ended && !loading)" @click="loadMore" class="btn btn-secondary py-1 mb-5"
style="border-radius: 15px">Load more</button> style="border-radius: 15px">Load more</button>
<IntersectionObserver sentinal-name="load-more-home" @on-intersection-element="loadMore" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,88 +1,70 @@
<script> <script>
import IntersectionObserver from '../components/IntersectionObserver.vue';
export default { export default {
data: function () { data: function () {
return { return {
requestedProfile: this.$route.params.user_id, requestedProfile: this.$route.params.user_id,
loading: true,
loading: false,
loadingError: false, loadingError: false,
stream_data: [], stream_data: [],
data_ended: false, data_ended: false,
start_idx: 0, start_idx: 0,
limit: 1, limit: 1,
user_data: [], user_data: [],
} };
}, },
methods: { methods: {
async refresh() { async refresh() {
this.getMainData(); this.getMainData();
// this way we are sure that we fill the first page todo: check // this way we are sure that we fill the first page todo: check
// 450 is a bit more of the max height of a post // 450 is a bit more of the max height of a post
// todo: may not work in 4k screens :/ // todo: may not work in 4k screens :/
this.limit = Math.max(Math.round(window.innerHeight / 450), 1) this.limit = Math.max(Math.round(window.innerHeight / 450), 1);
this.start_idx = 0; this.start_idx = 0;
this.data_ended = false; this.data_ended = false;
this.stream_data = []; this.stream_data = [];
this.loadContent(); this.loadContent();
}, },
async getMainData() { async getMainData() {
let response = await this.$axios.get("/users/" + this.requestedProfile); let response = await this.$axios.get("/users/" + this.requestedProfile);
if (response == null) { if (response == null) {
this.loading = false this.loading = false;
this.loadingError = true this.loadingError = true;
return return;
} }
this.user_data = response.data; this.user_data = response.data;
}, },
async loadContent() { async loadContent() {
this.loading = true; this.loading = true;
let response = await this.$axios.get("/users/" + this.requestedProfile + "/photos" + "?start_index=" + this.start_idx + "&limit=" + this.limit); let response = await this.$axios.get("/users/" + this.requestedProfile + "/photos" + "?start_index=" + this.start_idx + "&limit=" + this.limit);
if (response == null) { if (response == null) {
// do something // do something
return return;
} }
if (response.data.length == 0 || response.data.length < this.limit)
if (response.data.length == 0 || response.data.length < this.limit) this.data_ended = true; this.data_ended = true;
this.stream_data = this.stream_data.concat(response.data); this.stream_data = this.stream_data.concat(response.data);
this.loading = false; this.loading = false;
console.log(this.stream_data);
}, },
loadMore() { loadMore() {
if (this.loading || this.data_ended) return
this.start_idx += this.limit this.start_idx += this.limit
this.loadContent() this.loadContent()
}, },
scroll() {
window.onscroll = () => {
let bottomOfWindow = Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop) + window.innerHeight >= document.documentElement.offsetHeight - 5
if (bottomOfWindow && !this.data_ended) {
this.start_idx += this.limit
this.loadMore()
}
}
},
}, },
created() { created() {
if (this.$route.params.user_id == "me") { if (this.$route.params.user_id == "me") {
//this.$router.replace({ path: "/profile/" + }); (It's ok to not redirect, it's just a matter of taste) //this.$router.replace({ path: "/profile/" + }); (It's ok to not redirect, it's just a matter of taste)
this.requestedProfile = this.$currentSession(); this.requestedProfile = this.$currentSession();
} else { }
else {
this.requestedProfile = this.$route.params.user_id; this.requestedProfile = this.$route.params.user_id;
} }
//this.scroll();
this.scroll();
this.refresh(); this.refresh();
} },
components: { IntersectionObserver }
} }
</script> </script>
@ -128,6 +110,7 @@ export default {
<button v-if="(!data_ended && !loading)" @click="loadMore" class="btn btn-secondary py-1 mb-5" <button v-if="(!data_ended && !loading)" @click="loadMore" class="btn btn-secondary py-1 mb-5"
style="border-radius: 15px">Load more</button> style="border-radius: 15px">Load more</button>
<IntersectionObserver sentinal-name="load-more-profile" @on-intersection-element="loadMore" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -42,22 +42,17 @@ export default {
this.loading = false; this.loading = false;
}, },
scroll() { loadMore() {
window.onscroll = () => { if (this.loading || this.dataEnded) return
let bottomOfWindow = Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop) + window.innerHeight >= document.documentElement.offsetHeight - 5 this.startIdx += this.limit
if (bottomOfWindow && !this.dataEnded) { this.loadContent()
this.startIdx += this.limit
this.loadContent()
}
}
}, },
}, },
mounted() { mounted() {
// this way we are sure that we fill the first page // this way we are sure that we fill the first page
// 72 is a bit more of the max height of a card // 72 is a bit more of the max height of a card
// todo: may not work in 4k screens :/ // todo: may not work in 4k screens :/
this.limit = Math.round(window.innerHeight / 72); this.limit = Math.round(window.innerHeight / 72)
this.scroll();
} }
} }
</script> </script>
@ -84,6 +79,7 @@ export default {
</div> </div>
<LoadingSpinner :loading="loading" /><br /> <LoadingSpinner :loading="loading" /><br />
<IntersectionObserver sentinal-name="load-more-search" @on-intersection-element="loadMore" />
</div> </div>
</div> </div>
</div> </div>