Я действительно смущен, что я делаю здесь неправильно. Я пытаюсь реализовать бесконечную прокрутку, которая будет загружать новые элементы, когда нижний край целевого элемента находится в пределах 200 пикселей от нижней части области просмотра, но она срабатывает почти сразу, как только я начинаю прокрутку.
/**
* Custom composable function for implementing infinite scroll functionality.
*
* @param {string} propName - The name of the prop that contains the data to be paginated.
* @param {HTMLElement|null} landmark - The landmark element to observe for intersection. If null, infinite scroll will not be triggered by intersection.
* @returns {object} - An object containing the paginated items, methods to load more items and reset items, and a computed property indicating if more items can be loaded.
*/
import { computed, nextTick, ref, watch, watchEffect } from 'vue'
import { router, usePage } from '@inertiajs/vue3'
import { onBeforeUnmount } from 'vue';
export function useInfiniteScroll(propName, landmark = null) {
///
const observer = useIntersectionObserver(landmark, ([{isIntersecting}], observerElement) => {
const rect = landmark.value.getBoundingClientRect();
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
if (isIntersecting && canLoadMoreItems.value){
console.info(rect.top, rect.bottom, rect.width, rect.height, viewportHeight, isIntersecting)
console.info('Root:', observer.root);
loadMoreItems();
}
},
{
rootMargin: '0px 0px 200px 0px',
});
///
}
когда он впервые срабатывает, журнал показывает:
123 6411 1871 6288 553 true
Root: undefined
Вот компонент, из которого я пытаюсь использовать эту составную часть:
<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Head} from '@inertiajs/vue3';
import CreatePost from './Partials/CreatePost.vue';
import ShowPost from './Partials/ShowPost.vue';
import { ref, computed } from 'vue';
import FollowHeader from '@/Pages/Feed/Partials/FollowHeader.vue';
import { useInfiniteScroll } from '@/composables/useInfiniteScroll';
const props = defineProps({
posts: {
type: Object,
},
users: {
type: Object,
},
featured : {
type : Object,
default: null
},
follow : {
type : Object,
default: null
},
user : {
type: Object,
default: null
},
tag : {
type: Object,
default: null
}
});
//target for infinite scroll
const target = ref(null);
let { items, canLoadMoreItems } = useInfiniteScroll('posts', target)
//is this a featured post? Then open modal
const featuredPost = ref(props.featured);
const showFeatured = computed(() => {
return featuredPost.value != null;
})
const isFeatured = computed(() => {
return featuredPost.value != null;
})
//inject trans to pull in title string
const trans = inject('trans')
const title = computed(() => {
if (props.user){
return '@' + props.user.username;
}
else if (props.tag){
return '#' + props.tag.tagname
}
return trans('feed.feedTitle');
})
</script>
<template>
<Head :title = "title" />
<AuthenticatedLayout :class = "{'blur-sm' : showFeatured}">
<template #header>
<div class = "flex items-center space-x-3" dusk = "feed-header">
<FollowHeader v-if = "user || tag" :user = "user" :tag = "tag" :follow = "follow"/>
<h2 v-else class = "font-semibold text-xl text-gray-800 leading-tight">{{title}}</h2>
</div>
</template>
<div v-show = "!user && !tag" class = "sm:py-12">
<div class = "max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class = "bg-white shadow sm:rounded-lg">
<div class = "p-2 sm:p-6 text-gray-900">
<CreatePost :placeholder = "__('feed.postPrompt', {'username': $page.props.auth.user.username})" />
</div>
</div>
</div>
</div>
<div class = "py-2 sm:py-6" ref = "target" >
<div class = "max-w-7xl mx-auto sm:px-6 lg:px-8 sm:space-y-3 space-y-2" >
<div v-for = "post in items" class = "bg-white sm:rounded-lg sm:p-3 my-2" dusk = "post">
<ShowPost :users = "users" :post = "post" :descendants = "post.descendants" :currentDepth = "1" @show-post = "toggleFeatured(post.id)"/>
</div>
</div>
</div>
</AuthenticatedLayout>
</template>
Я как-то неправильно установил root?
@nom_unique спасибо за ответ. Я добавил компонент сейчас. это элемент ref = "target" внизу.
Когда вы объявляете свой const Observer = useIntersectionObserver(landmark, ....), откуда берется переменная «ориентир»? Документ useIntersectionObserver, похоже, указывает на то, что вы должны передать ссылку на целевой HTML (в вашем случае «цель») в качестве первого параметра. vueuse.org/core/useIntersectionObserver
@nom_unique, извините, я не опубликовал полную useInfiniteScroll
композицию, я добавил еще немного, что, надеюсь, имеет больше смысла.
Спасибо! Итак, когда вы передаете здесь «target»: useInfiniteScroll('posts', target), вы передаете полный изменяемый объект ref «target», который не является фактическим элементом HTML. Вам нужно получить доступ к значению ref() с помощью target.value. Попробуйте useInfiniteScroll('posts', target.value) и дайте мне знать, как это работает.
Кроме того, если вы попытаетесь получить доступ к значению ref() в setup(), значение будет равно Null, поскольку дерево DOM компонента еще не создано. Вам нужно будет назначить { items, canLoadMoreItems} в хуке onMounted.
Еще раз спасибо, что потратили на это время, я это очень ценю. Если я неправильно прочитал документацию, я почти уверен, что useInfiniteScroll ожидает номер ссылки? И я ценю то, что вы говорите о том, что до того, как страница будет отображена, оно будет равно нулю, но я думал, что useInfiniteScroll создает экземпляр прослушивателя, который будет проверять пересечение, поэтому, хотя оно изначально равно нулю, оно не будет на протяжении всего жизненного цикла. vueuse.org/core/useInfiniteScroll/#useinfinitescroll
Вы правы насчет документации! Но, возможно, мне чего-то не хватает... в коде, которым вы поделились, вы создаете свой собственный компонуемый useInfiniteScroll и, похоже, на самом деле не используете useInfiniteScroll из библиотеки VueUse. В вашем компоненте вы можете просто импортировать { useInfiniteScroll } из @vueuse/core, затем вызвать useInfiniteScroll с вашей «целью» в качестве первого параметра и функцией обратного вызова в качестве второго параметра. В обратном вызове вы можете поместить логику, которая у вас есть в компонуемом объекте.
Хотя реализация vueuse хороша, я не могу найти способ заставить ее работать при прокрутке элемента вверх. например окно чата обычно содержит самое последнее сообщение внизу, и пользователь прокручивает вверх, чтобы загрузить больше
Это не полный ответ, но вместо этого я использовал новый scrollMargin
rootMargin
, который сработал.
Однако у меня были и другие проблемы, поскольку я пытался создать бесконечную прокрутку, которая допускала бы прокрутку как вверх, так и вниз. например чат личных сообщений, в котором мы обычно хотим прокрутить вверх и добавить новые элементы вверху ленты, а не внизу.
Для тех, кто застрял в этом, это модифицированная версия, которая теперь работает у меня:
использование: let { items, canLoadMoreItems, loading } = useInfiniteScroll('posts', infiniteScrollElement,'down', 500)
/**
* Custom composable function for implementing infinite scroll functionality.
*
* @param {string} propName - The name of the prop that contains the data to be paginated.
* @param {HTMLElement|null} landmark - The element to observe for intersection. If null, infinite scroll will not be triggered by intersection. *** N.B. Must be a scrollable element with a fixed height
* @param {string|down} scrollDirection - Which direction is user scrolling up/down
* @param {number|200} triggerDistance - how many pixels from the edge should it trigger.
* @returns {object} - An object containing the paginated items, methods to load more items and reset items, and a computed property indicating if more items can be loaded.
*/
import { computed, nextTick, ref, watch, watchEffect } from 'vue'
import { router, usePage } from '@inertiajs/vue3'
export function useInfiniteScroll(propName, landmark = null, scrollDirection = 'down', triggerDistance = 200) {
// Store loading state to stop multiple triggers
const loading = ref(false);
// Get the value of the prop that contains the data to be paginated.
const value = () => usePage().props[propName]
// Create a ref to store all the paginated items.
const items = ref(value().data)
// Watch for changes to prop and update items accordingly.
// Allows itemsto be added from an external source
watch(() => value().data, (newValue) => {
if (!loading.value) {
items.value = newValue;
}
}, { deep: true });
// Get the initial URL of the page so we can replace the URL after loading more items.
const initialUrl = usePage().url
// Computed property to check if more items can be loaded.
const canLoadMoreItems = computed(() => value().next_page_url !== null)
// Create the listeners for scoll on the landmark element
if (landmark != null) {
nextTick(() => {
landmark.value.addEventListener('scroll', () => {
checkPosition();
});
landmark.value.addEventListener('touchmove', () => {
checkPosition();
});
});
}
// Method to check if the landmark element is in range
const checkPosition = () => {
if (loading.value || !canLoadMoreItems.value) {
return;
}
//downward scroll
if (scrollDirection.toLowerCase() === 'down') {
const elementScrollBottom = landmark.value.scrollHeight - landmark.value.scrollTop;
if (elementScrollBottom - window.innerHeight <= triggerDistance) {
loadMoreItems();
}
}
//upward scroll
else if (landmark.value.scrollTop <= triggerDistance) {
loadMoreItems();
}
};
// Method to load more items.
const loadMoreItems = () => {
// Set the loading state to true.
loading.value = true;
// Load more items using Inertia's get method and append the new items to the existing items.
router.get(
value().next_page_url,
{},
{
preserveState: true,
preserveScroll: true, //only works for downward scroll
onSuccess: () => {
//reset url to initial url
window.history.replaceState({}, '', initialUrl)
// Capture scroll position
const originalScrollPosition = landmark.value.scrollHeight - landmark.value.scrollTop;
//add new items to the page
items.value = [...items.value, ...value().data]
nextTick(() => {
// only if its upward scroll reset the scroll position
if (scrollDirection.toLowerCase() === 'up') {
landmark.value.scrollTop = landmark.value.scrollHeight - originalScrollPosition;
}
});
loading.value = false;
},
onError: (error) => {
console.error(error);
loading.value = false;
}
}
)
}
// Return the items, loadMoreItems method, resetItems method, and canLoadMoreItems computed property.
return {
items,
loadMoreItems,
resetItems: () => (items.value = value().data),
canLoadMoreItems,
loading
}
}
Можно ли поделиться кодом вашего шаблона vue? Есть ли у вас атрибут v-intersection-observer или ref = "landmark" в целевом элементе?