98 lines
3.7 KiB
TypeScript
98 lines
3.7 KiB
TypeScript
import { useScrollHandler } from "contexts/ScrollHandlerContext";
|
|
import { useEffect, useRef } from "react";
|
|
|
|
export default function ScrollEventHandler() {
|
|
const {
|
|
setShowScrollTop,
|
|
setIsCategoriesSticky,
|
|
categoriesContainerRef,
|
|
categoryRefs,
|
|
activeCategory,
|
|
setActiveCategory
|
|
} = useScrollHandler();
|
|
|
|
const hasSetInitialCategory = useRef(false);
|
|
|
|
// Set initial active category when categories are available
|
|
useEffect(() => {
|
|
if (categoryRefs.current && Object.keys(categoryRefs.current).length > 0 && !hasSetInitialCategory.current) {
|
|
// Set the first category as active initially
|
|
const firstCategoryId = parseInt(Object.keys(categoryRefs.current)[0]);
|
|
setActiveCategory(firstCategoryId);
|
|
hasSetInitialCategory.current = true;
|
|
}
|
|
}, [categoryRefs, setActiveCategory]);
|
|
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
const scrollTop =
|
|
window.pageYOffset || document.documentElement.scrollTop;
|
|
setShowScrollTop(scrollTop > 300); // Show button after scrolling 300px
|
|
|
|
// Check if we should make categories sticky
|
|
if (categoriesContainerRef.current) {
|
|
// Get the original position of the categories container
|
|
const originalTop = categoriesContainerRef.current.offsetTop;
|
|
|
|
// Only make sticky when we've scrolled past the original position
|
|
// and return to normal when we scroll back above it
|
|
// Add a small threshold (10px) to prevent flickering
|
|
const shouldBeSticky = scrollTop > originalTop + 10;
|
|
setIsCategoriesSticky(shouldBeSticky);
|
|
}
|
|
|
|
// Find the most visible category based on scroll position
|
|
if (categoryRefs.current) {
|
|
let mostVisibleCategory: { id: number; visibility: number } | null = null;
|
|
|
|
Object.entries(categoryRefs.current).forEach(([categoryId, element]) => {
|
|
if (element) {
|
|
const rect = element.getBoundingClientRect();
|
|
const viewportHeight = window.innerHeight;
|
|
|
|
// Calculate visibility ratio
|
|
const elementTop = rect.top;
|
|
const elementBottom = rect.bottom;
|
|
const elementHeight = rect.height;
|
|
|
|
// Calculate how much of the element is visible
|
|
let visibleHeight = 0;
|
|
if (elementTop >= 0 && elementBottom <= viewportHeight) {
|
|
// Element is fully visible
|
|
visibleHeight = elementHeight;
|
|
} else if (elementTop < 0 && elementBottom > 0) {
|
|
// Element is partially visible from top
|
|
visibleHeight = elementBottom;
|
|
} else if (elementTop < viewportHeight && elementBottom > viewportHeight) {
|
|
// Element is partially visible from bottom
|
|
visibleHeight = viewportHeight - elementTop;
|
|
}
|
|
|
|
const visibility = visibleHeight / elementHeight;
|
|
|
|
// Only consider elements that are at least 30% visible
|
|
if (visibility > 0.3) {
|
|
if (!mostVisibleCategory || visibility > mostVisibleCategory.visibility) {
|
|
mostVisibleCategory = {
|
|
id: parseInt(categoryId),
|
|
visibility
|
|
};
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update active category if we found a visible one
|
|
if (mostVisibleCategory) {
|
|
setActiveCategory((mostVisibleCategory as { id: number; visibility: number }).id);
|
|
}
|
|
}
|
|
};
|
|
|
|
window.addEventListener("scroll", handleScroll);
|
|
return () => window.removeEventListener("scroll", handleScroll);
|
|
}, [categoriesContainerRef, setIsCategoriesSticky, setShowScrollTop, categoryRefs, setActiveCategory, activeCategory]);
|
|
|
|
return null;
|
|
}
|