From dea9a5912795ddc21c28af60dd434e5c3d9e8587 Mon Sep 17 00:00:00 2001 From: Mohammed Al-yaseen Date: Thu, 9 Oct 2025 00:20:39 +0300 Subject: [PATCH] Revert "refactor sticky scroll handler" This reverts commit da9d7f8eb313d4c85158a9debb2365dda72ee973. --- .../FloatingButton/FloatingButton.tsx | 26 +-- src/contexts/ScrollHandlerContext.tsx | 34 +-- .../menu/components/ScrollEventHandler.tsx | 204 ++++++------------ src/routes/routes.tsx | 28 +-- 4 files changed, 78 insertions(+), 214 deletions(-) diff --git a/src/components/FloatingButton/FloatingButton.tsx b/src/components/FloatingButton/FloatingButton.tsx index a832410..42d2516 100644 --- a/src/components/FloatingButton/FloatingButton.tsx +++ b/src/components/FloatingButton/FloatingButton.tsx @@ -15,28 +15,10 @@ export function FloatingButton() { const { showScrollTop } = useScrollHandler(); const scrollToTop = useCallback(() => { - // Use a more stable approach to find the scroll container - const findScrollContainer = () => { - const antApp = document.querySelector('.ant-app') as HTMLElement; - if (antApp && antApp.scrollHeight > antApp.clientHeight) { - return antApp; - } - - const appContainer = document.querySelector('[class*="App"]') as HTMLElement; - if (appContainer && appContainer.scrollHeight > appContainer.clientHeight) { - return appContainer; - } - - return document.documentElement; - }; - - const scrollContainer = findScrollContainer(); - if (scrollContainer) { - scrollContainer.scrollTo({ - top: 0, - behavior: "smooth", - }); - } + window.scrollTo({ + top: 0, + behavior: "smooth", + }); }, []); return ( diff --git a/src/contexts/ScrollHandlerContext.tsx b/src/contexts/ScrollHandlerContext.tsx index 8d3d87f..ad0b74d 100644 --- a/src/contexts/ScrollHandlerContext.tsx +++ b/src/contexts/ScrollHandlerContext.tsx @@ -41,36 +41,10 @@ export function ScrollHandlerProvider({ children }: { children: ReactNode }) { // Function to scroll to a specific category const scrollToCategory = useCallback((categoryId: number) => { const categoryRef = categoryRefs.current?.[categoryId]; - - // Use a more stable approach to find the scroll container - const findScrollContainer = () => { - const antApp = document.querySelector('.ant-app') as HTMLElement; - if (antApp && antApp.scrollHeight > antApp.clientHeight) { - return antApp; - } - - const appContainer = document.querySelector('[class*="App"]') as HTMLElement; - if (appContainer && appContainer.scrollHeight > appContainer.clientHeight) { - return appContainer; - } - - return document.documentElement; - }; - - const scrollContainer = findScrollContainer(); - - if (categoryRef && scrollContainer) { - // Get the position of the category relative to the scroll container - const categoryRect = categoryRef.getBoundingClientRect(); - const containerRect = scrollContainer.getBoundingClientRect(); - - // Calculate the target scroll position - const targetScrollTop = scrollContainer.scrollTop + categoryRect.top - containerRect.top; - - // Smooth scroll to the target position - scrollContainer.scrollTo({ - top: targetScrollTop, - behavior: "smooth" + if (categoryRef) { + categoryRef.scrollIntoView({ + behavior: "smooth", + block: "start", }); } }, []); diff --git a/src/pages/menu/components/ScrollEventHandler.tsx b/src/pages/menu/components/ScrollEventHandler.tsx index b60cddd..4003a2d 100644 --- a/src/pages/menu/components/ScrollEventHandler.tsx +++ b/src/pages/menu/components/ScrollEventHandler.tsx @@ -1,58 +1,21 @@ import { useScrollHandler } from "contexts/ScrollHandlerContext"; -import { useCallback, useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; export default function ScrollEventHandler() { - const { - setShowScrollTop, - setIsCategoriesSticky, + const { + setShowScrollTop, + setIsCategoriesSticky, categoriesContainerRef, categoryRefs, - setActiveCategory, + activeCategory, + setActiveCategory } = useScrollHandler(); const hasSetInitialCategory = useRef(false); - const scrollContainerRef = useRef(null); - const lastStickyState = useRef(false); - const originalCategoriesTop = useRef(null); - - // Find the main scrollable container - use a more stable approach - useEffect(() => { - // Use a more stable approach - find the container once and cache it - const findScrollContainer = () => { - // Try to find the Ant Design app container first - const antApp = document.querySelector(".ant-app") as HTMLElement; - if (antApp && antApp.scrollHeight > antApp.clientHeight) { - return antApp; - } - - // Fallback to any container with App in the class name - const appContainer = document.querySelector( - '[class*="App"]', - ) as HTMLElement; - if ( - appContainer && - appContainer.scrollHeight > appContainer.clientHeight - ) { - return appContainer; - } - - // Last resort - use document element - return document.documentElement; - }; - - const scrollContainer = findScrollContainer(); - if (scrollContainer) { - scrollContainerRef.current = scrollContainer; - } - }, []); // Set initial active category when categories are available useEffect(() => { - if ( - categoryRefs.current && - Object.keys(categoryRefs.current).length > 0 && - !hasSetInitialCategory.current - ) { + 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); @@ -60,112 +23,75 @@ export default function ScrollEventHandler() { } }, [categoryRefs, setActiveCategory]); - // Store the original position of categories container once useEffect(() => { - if ( - categoriesContainerRef.current && - originalCategoriesTop.current === null - ) { - const rect = categoriesContainerRef.current.getBoundingClientRect(); - originalCategoriesTop.current = - rect.top + (scrollContainerRef.current?.scrollTop || 0); - } - }, [categoriesContainerRef]); + const handleScroll = () => { + const scrollTop = + window.pageYOffset || document.documentElement.scrollTop; + setShowScrollTop(scrollTop > 300); // Show button after scrolling 300px - const handleScroll = useCallback(() => { - // Use the scrollable container instead of window - const scrollTop = scrollContainerRef.current?.scrollTop || 0; - 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; - // Check if we should make categories sticky - if ( - categoriesContainerRef.current && - scrollContainerRef.current && - originalCategoriesTop.current !== null - ) { - // Use the cached original position to prevent flickering - const threshold = 50; // Increased threshold to prevent flickering - const shouldBeSticky = - scrollTop > originalCategoriesTop.current + threshold; - - // Only update state if it actually changed to prevent unnecessary re-renders - if (shouldBeSticky !== lastStickyState.current) { + // 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); - lastStickyState.current = shouldBeSticky; } - } - // Find the most visible category based on scroll position - if (categoryRefs.current && scrollContainerRef.current) { - let mostVisibleCategory: { id: number; visibility: number } | null = null; - - Object.entries(categoryRefs.current).forEach(([categoryId, element]) => { - if (element) { - const rect = element.getBoundingClientRect(); - const containerRect = - scrollContainerRef.current!.getBoundingClientRect(); - - // Calculate visibility ratio relative to the scroll container - const elementTop = rect.top - containerRect.top; - const elementBottom = elementTop + rect.height; - const containerHeight = containerRect.height; - const elementHeight = rect.height; - - // Calculate how much of the element is visible within the container - let visibleHeight = 0; - if (elementTop >= 0 && elementBottom <= containerHeight) { - // Element is fully visible - visibleHeight = elementHeight; - } else if (elementTop < 0 && elementBottom > 0) { - // Element is partially visible from top - visibleHeight = elementBottom; - } else if ( - elementTop < containerHeight && - elementBottom > containerHeight - ) { - // Element is partially visible from bottom - visibleHeight = containerHeight - 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, - }; + // 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); } - }); - - // Update active category if we found a visible one - if (mostVisibleCategory) { - setActiveCategory( - (mostVisibleCategory as { id: number; visibility: number }).id, - ); } - } - }, [ - setShowScrollTop, - categoriesContainerRef, - categoryRefs, - setIsCategoriesSticky, - setActiveCategory, - ]); // categoryRefs and categoriesContainerRef are refs, don't need to be in deps + }; - useEffect(() => { - const scrollContainer = scrollContainerRef.current; - if (scrollContainer) { - scrollContainer.addEventListener("scroll", handleScroll); - return () => scrollContainer.removeEventListener("scroll", handleScroll); - } - }, [handleScroll]); + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, [categoriesContainerRef, setIsCategoriesSticky, setShowScrollTop, categoryRefs, setActiveCategory, activeCategory]); return null; } diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 2cb79be..81faa06 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -27,29 +27,11 @@ export const ScrollToTop: React.FC = () => { const { pathname } = useLocation(); useEffect(() => { - // Use a more stable approach to find the scroll container - const findScrollContainer = () => { - const antApp = document.querySelector('.ant-app') as HTMLElement; - if (antApp && antApp.scrollHeight > antApp.clientHeight) { - return antApp; - } - - const appContainer = document.querySelector('[class*="App"]') as HTMLElement; - if (appContainer && appContainer.scrollHeight > appContainer.clientHeight) { - return appContainer; - } - - return document.documentElement; - }; - - const scrollContainer = findScrollContainer(); - if (scrollContainer) { - scrollContainer.scrollTo({ - top: 0, - left: 0, - behavior: "smooth", - }); // Scroll to the top when the location changes - } + window.scrollTo({ + top: 0, + left: 0, + behavior: "smooth", + }); // Scroll to the top when the location changes }, [pathname]); return null; // This component doesn't render anything