Revert "refactor sticky scroll handler"
This reverts commit da9d7f8eb3.
This commit is contained in:
@@ -15,28 +15,10 @@ export function FloatingButton() {
|
|||||||
const { showScrollTop } = useScrollHandler();
|
const { showScrollTop } = useScrollHandler();
|
||||||
|
|
||||||
const scrollToTop = useCallback(() => {
|
const scrollToTop = useCallback(() => {
|
||||||
// Use a more stable approach to find the scroll container
|
window.scrollTo({
|
||||||
const findScrollContainer = () => {
|
top: 0,
|
||||||
const antApp = document.querySelector('.ant-app') as HTMLElement;
|
behavior: "smooth",
|
||||||
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",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -41,36 +41,10 @@ export function ScrollHandlerProvider({ children }: { children: ReactNode }) {
|
|||||||
// Function to scroll to a specific category
|
// Function to scroll to a specific category
|
||||||
const scrollToCategory = useCallback((categoryId: number) => {
|
const scrollToCategory = useCallback((categoryId: number) => {
|
||||||
const categoryRef = categoryRefs.current?.[categoryId];
|
const categoryRef = categoryRefs.current?.[categoryId];
|
||||||
|
if (categoryRef) {
|
||||||
// Use a more stable approach to find the scroll container
|
categoryRef.scrollIntoView({
|
||||||
const findScrollContainer = () => {
|
behavior: "smooth",
|
||||||
const antApp = document.querySelector('.ant-app') as HTMLElement;
|
block: "start",
|
||||||
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"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -1,58 +1,21 @@
|
|||||||
import { useScrollHandler } from "contexts/ScrollHandlerContext";
|
import { useScrollHandler } from "contexts/ScrollHandlerContext";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
export default function ScrollEventHandler() {
|
export default function ScrollEventHandler() {
|
||||||
const {
|
const {
|
||||||
setShowScrollTop,
|
setShowScrollTop,
|
||||||
setIsCategoriesSticky,
|
setIsCategoriesSticky,
|
||||||
categoriesContainerRef,
|
categoriesContainerRef,
|
||||||
categoryRefs,
|
categoryRefs,
|
||||||
setActiveCategory,
|
activeCategory,
|
||||||
|
setActiveCategory
|
||||||
} = useScrollHandler();
|
} = useScrollHandler();
|
||||||
|
|
||||||
const hasSetInitialCategory = useRef(false);
|
const hasSetInitialCategory = useRef(false);
|
||||||
const scrollContainerRef = useRef<HTMLElement | null>(null);
|
|
||||||
const lastStickyState = useRef(false);
|
|
||||||
const originalCategoriesTop = useRef<number | null>(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
|
// Set initial active category when categories are available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (categoryRefs.current && Object.keys(categoryRefs.current).length > 0 && !hasSetInitialCategory.current) {
|
||||||
categoryRefs.current &&
|
|
||||||
Object.keys(categoryRefs.current).length > 0 &&
|
|
||||||
!hasSetInitialCategory.current
|
|
||||||
) {
|
|
||||||
// Set the first category as active initially
|
// Set the first category as active initially
|
||||||
const firstCategoryId = parseInt(Object.keys(categoryRefs.current)[0]);
|
const firstCategoryId = parseInt(Object.keys(categoryRefs.current)[0]);
|
||||||
setActiveCategory(firstCategoryId);
|
setActiveCategory(firstCategoryId);
|
||||||
@@ -60,112 +23,75 @@ export default function ScrollEventHandler() {
|
|||||||
}
|
}
|
||||||
}, [categoryRefs, setActiveCategory]);
|
}, [categoryRefs, setActiveCategory]);
|
||||||
|
|
||||||
// Store the original position of categories container once
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
const handleScroll = () => {
|
||||||
categoriesContainerRef.current &&
|
const scrollTop =
|
||||||
originalCategoriesTop.current === null
|
window.pageYOffset || document.documentElement.scrollTop;
|
||||||
) {
|
setShowScrollTop(scrollTop > 300); // Show button after scrolling 300px
|
||||||
const rect = categoriesContainerRef.current.getBoundingClientRect();
|
|
||||||
originalCategoriesTop.current =
|
|
||||||
rect.top + (scrollContainerRef.current?.scrollTop || 0);
|
|
||||||
}
|
|
||||||
}, [categoriesContainerRef]);
|
|
||||||
|
|
||||||
const handleScroll = useCallback(() => {
|
// Check if we should make categories sticky
|
||||||
// Use the scrollable container instead of window
|
if (categoriesContainerRef.current) {
|
||||||
const scrollTop = scrollContainerRef.current?.scrollTop || 0;
|
// Get the original position of the categories container
|
||||||
setShowScrollTop(scrollTop > 300); // Show button after scrolling 300px
|
const originalTop = categoriesContainerRef.current.offsetTop;
|
||||||
|
|
||||||
// Check if we should make categories sticky
|
// Only make sticky when we've scrolled past the original position
|
||||||
if (
|
// and return to normal when we scroll back above it
|
||||||
categoriesContainerRef.current &&
|
// Add a small threshold (10px) to prevent flickering
|
||||||
scrollContainerRef.current &&
|
const shouldBeSticky = scrollTop > originalTop + 10;
|
||||||
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) {
|
|
||||||
setIsCategoriesSticky(shouldBeSticky);
|
setIsCategoriesSticky(shouldBeSticky);
|
||||||
lastStickyState.current = shouldBeSticky;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Find the most visible category based on scroll position
|
// Find the most visible category based on scroll position
|
||||||
if (categoryRefs.current && scrollContainerRef.current) {
|
if (categoryRefs.current) {
|
||||||
let mostVisibleCategory: { id: number; visibility: number } | null = null;
|
let mostVisibleCategory: { id: number; visibility: number } | null = null;
|
||||||
|
|
||||||
Object.entries(categoryRefs.current).forEach(([categoryId, element]) => {
|
Object.entries(categoryRefs.current).forEach(([categoryId, element]) => {
|
||||||
if (element) {
|
if (element) {
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
const containerRect =
|
const viewportHeight = window.innerHeight;
|
||||||
scrollContainerRef.current!.getBoundingClientRect();
|
|
||||||
|
// Calculate visibility ratio
|
||||||
// Calculate visibility ratio relative to the scroll container
|
const elementTop = rect.top;
|
||||||
const elementTop = rect.top - containerRect.top;
|
const elementBottom = rect.bottom;
|
||||||
const elementBottom = elementTop + rect.height;
|
const elementHeight = rect.height;
|
||||||
const containerHeight = containerRect.height;
|
|
||||||
const elementHeight = rect.height;
|
// Calculate how much of the element is visible
|
||||||
|
let visibleHeight = 0;
|
||||||
// Calculate how much of the element is visible within the container
|
if (elementTop >= 0 && elementBottom <= viewportHeight) {
|
||||||
let visibleHeight = 0;
|
// Element is fully visible
|
||||||
if (elementTop >= 0 && elementBottom <= containerHeight) {
|
visibleHeight = elementHeight;
|
||||||
// Element is fully visible
|
} else if (elementTop < 0 && elementBottom > 0) {
|
||||||
visibleHeight = elementHeight;
|
// Element is partially visible from top
|
||||||
} else if (elementTop < 0 && elementBottom > 0) {
|
visibleHeight = elementBottom;
|
||||||
// Element is partially visible from top
|
} else if (elementTop < viewportHeight && elementBottom > viewportHeight) {
|
||||||
visibleHeight = elementBottom;
|
// Element is partially visible from bottom
|
||||||
} else if (
|
visibleHeight = viewportHeight - elementTop;
|
||||||
elementTop < containerHeight &&
|
}
|
||||||
elementBottom > containerHeight
|
|
||||||
) {
|
const visibility = visibleHeight / elementHeight;
|
||||||
// Element is partially visible from bottom
|
|
||||||
visibleHeight = containerHeight - elementTop;
|
// Only consider elements that are at least 30% visible
|
||||||
}
|
if (visibility > 0.3) {
|
||||||
|
if (!mostVisibleCategory || visibility > mostVisibleCategory.visibility) {
|
||||||
const visibility = visibleHeight / elementHeight;
|
mostVisibleCategory = {
|
||||||
|
id: parseInt(categoryId),
|
||||||
// Only consider elements that are at least 30% visible
|
visibility
|
||||||
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(() => {
|
window.addEventListener("scroll", handleScroll);
|
||||||
const scrollContainer = scrollContainerRef.current;
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
if (scrollContainer) {
|
}, [categoriesContainerRef, setIsCategoriesSticky, setShowScrollTop, categoryRefs, setActiveCategory, activeCategory]);
|
||||||
scrollContainer.addEventListener("scroll", handleScroll);
|
|
||||||
return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
|
||||||
}
|
|
||||||
}, [handleScroll]);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,29 +27,11 @@ export const ScrollToTop: React.FC = () => {
|
|||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Use a more stable approach to find the scroll container
|
window.scrollTo({
|
||||||
const findScrollContainer = () => {
|
top: 0,
|
||||||
const antApp = document.querySelector('.ant-app') as HTMLElement;
|
left: 0,
|
||||||
if (antApp && antApp.scrollHeight > antApp.clientHeight) {
|
behavior: "smooth",
|
||||||
return antApp;
|
}); // Scroll to the top when the location changes
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}, [pathname]);
|
}, [pathname]);
|
||||||
|
|
||||||
return null; // This component doesn't render anything
|
return null; // This component doesn't render anything
|
||||||
|
|||||||
Reference in New Issue
Block a user