useScrollPosition Composable
Reactive scroll position tracking with debounce and direction detection
Code
import { ref, onMounted, onUnmounted } from 'vue';
interface ScrollPosition {
x: Ref<number>;
y: Ref<number>;
directionX: Ref<'left' | 'right' | 'none'>;
directionY: Ref<'up' | 'down' | 'none'>;
isTop: Ref<boolean>;
isBottom: Ref<boolean>;
}
export function useScrollPosition(
target: HTMLElement | Window = window,
debounceDelay = 16
): ScrollPosition {
const x = ref(0);
const y = ref(0);
const directionX = ref<'left' | 'right' | 'none'>('none');
const directionY = ref<'up' | 'down' | 'none'>('none');
const isTop = ref(true);
const isBottom = ref(false);
let lastX = 0;
let lastY = 0;
let scrollTimeout: ReturnType<typeof setTimeout> | null = null;
const updateScrollPosition = () => {
const newX = target === window ? window.scrollX : (target as HTMLElement).scrollLeft;
const newY = target === window ? window.scrollY : (target as HTMLElement).scrollTop;
// Update direction
directionX.value = newX > lastX ? 'right' : newX < lastX ? 'left' : 'none';
directionY.value = newY > lastY ? 'down' : newY < lastY ? 'up' : 'none';
// Update position
x.value = newX;
y.value = newY;
// Update top/bottom state
isTop.value = newY === 0;
__TOKEN_55__ (target === window) {
isBottom.value = newY + window.innerHeight >= document.documentElement.scrollHeight;
} else {
const el = target as HTMLElement;
isBottom.value = newY + el.clientHeight >= el.scrollHeight;
}
// Update last positions
lastX = newX;
lastY = newY;
};
const handleScroll = () => {
__TOKEN_59__ (scrollTimeout) clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(updateScrollPosition, debounceDelay);
};
onMounted(() => {
target.addEventListener('scroll', handleScroll, { passive: true });
updateScrollPosition(); // Initial position
});
onUnmounted(() => {
target.removeEventListener('scroll', handleScroll);
__TOKEN_60__ (scrollTimeout) clearTimeout(scrollTimeout);
});
return { x, y, directionX, directionY, isTop, isBottom };
}
// Usage example
// const { y, directionY, isTop } = useScrollPosition();
// watch(directionY, (dir) => {
// if (dir === 'down' && !isTop.value) {
// headerRef.value.classList.add('sticky');
// } else {
// headerRef.value.classList.remove('sticky');
// }
// });