refactor: cleanup scroll handlers (#5709)

This commit is contained in:
Alexey Pyltsyn 2021-10-15 14:07:03 +03:00 committed by GitHub
parent c12fe963c9
commit e22170408a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 92 deletions

View file

@ -7,11 +7,14 @@
import React, {useRef, useState} from 'react'; import React, {useRef, useState} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import {useLocation} from '@docusaurus/router';
import {translate} from '@docusaurus/Translate'; import {translate} from '@docusaurus/Translate';
import styles from './styles.module.css'; import styles from './styles.module.css';
import {ThemeClassNames, useScrollPosition} from '@docusaurus/theme-common'; import {
ThemeClassNames,
useScrollPosition,
useLocationChange,
} from '@docusaurus/theme-common';
const threshold = 300; const threshold = 300;
@ -68,42 +71,52 @@ function useSmoothScrollToTop(): UseSmoothScrollTopReturn {
} }
function BackToTopButton(): JSX.Element { function BackToTopButton(): JSX.Element {
const location = useLocation();
const {smoothScrollTop, cancelScrollToTop} = useSmoothScrollToTop();
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const isFocusedAnchor = useRef(false);
const {smoothScrollTop, cancelScrollToTop} = useSmoothScrollToTop();
useScrollPosition( useScrollPosition(({scrollY: scrollTop}, lastPosition) => {
({scrollY: scrollTop}, lastPosition) => { const lastScrollTop = lastPosition?.scrollY;
// No lastPosition means component is just being mounted.
// Not really a scroll event from the user, so we ignore it // No lastScrollTop means component is just being mounted.
if (!lastPosition) { // Not really a scroll event from the user, so we ignore it
return; if (!lastScrollTop) {
return;
}
if (isFocusedAnchor.current) {
isFocusedAnchor.current = false;
return;
}
const isScrollingUp = scrollTop < lastScrollTop;
if (!isScrollingUp) {
cancelScrollToTop();
}
if (scrollTop < threshold) {
setShow(false);
return;
}
if (isScrollingUp) {
const documentHeight = document.documentElement.scrollHeight;
const windowHeight = window.innerHeight;
if (scrollTop + windowHeight < documentHeight) {
setShow(true);
} }
const lastScrollTop = lastPosition.scrollY; } else {
setShow(false);
}
});
const isScrollingUp = scrollTop < lastScrollTop; useLocationChange((locationChangeEvent) => {
if (locationChangeEvent.location.hash) {
if (!isScrollingUp) { isFocusedAnchor.current = true;
cancelScrollToTop(); setShow(false);
} }
});
if (scrollTop < threshold) {
setShow(false);
return;
}
if (isScrollingUp) {
const documentHeight = document.documentElement.scrollHeight;
const windowHeight = window.innerHeight;
if (scrollTop + windowHeight < documentHeight) {
setShow(true);
}
} else {
setShow(false);
}
},
[location],
);
return ( return (
<button <button

View file

@ -5,78 +5,57 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {useState, useCallback, useEffect, useRef} from 'react'; import {useState, useCallback, useRef} from 'react';
import {useLocation} from '@docusaurus/router';
import {useLocationChange, useScrollPosition} from '@docusaurus/theme-common'; import {useLocationChange, useScrollPosition} from '@docusaurus/theme-common';
import type {useHideableNavbarReturns} from '@theme/hooks/useHideableNavbar'; import type {useHideableNavbarReturns} from '@theme/hooks/useHideableNavbar';
const useHideableNavbar = (hideOnScroll: boolean): useHideableNavbarReturns => { const useHideableNavbar = (hideOnScroll: boolean): useHideableNavbarReturns => {
const location = useLocation();
const [isNavbarVisible, setIsNavbarVisible] = useState(hideOnScroll); const [isNavbarVisible, setIsNavbarVisible] = useState(hideOnScroll);
const isFocusedAnchor = useRef(false); const isFocusedAnchor = useRef(false);
const [navbarHeight, setNavbarHeight] = useState(0); const navbarHeight = useRef(0);
const navbarRef = useCallback((node: HTMLElement | null) => { const navbarRef = useCallback((node: HTMLElement | null) => {
if (node !== null) { if (node !== null) {
setNavbarHeight(node.getBoundingClientRect().height); navbarHeight.current = node.getBoundingClientRect().height;
} }
}, []); }, []);
useScrollPosition( useScrollPosition((currentPosition, lastPosition) => {
(currentPosition, lastPosition) => { if (!hideOnScroll) {
const scrollTop = currentPosition.scrollY; return;
const lastScrollTop = lastPosition?.scrollY; }
if (!hideOnScroll) {
return;
}
if (scrollTop < navbarHeight) { if (isFocusedAnchor.current) {
setIsNavbarVisible(true); isFocusedAnchor.current = false;
return; return;
} }
if (isFocusedAnchor.current) { const scrollTop = currentPosition.scrollY;
isFocusedAnchor.current = false; const lastScrollTop = lastPosition?.scrollY;
setIsNavbarVisible(false); const documentHeight =
return; document.documentElement.scrollHeight - navbarHeight.current;
} const windowHeight = window.innerHeight;
if (lastScrollTop && scrollTop === 0) { if (lastScrollTop && scrollTop >= lastScrollTop) {
setIsNavbarVisible(true); setIsNavbarVisible(false);
} } else if (scrollTop + windowHeight < documentHeight) {
setIsNavbarVisible(true);
const documentHeight = }
document.documentElement.scrollHeight - navbarHeight; });
const windowHeight = window.innerHeight;
if (lastScrollTop && scrollTop >= lastScrollTop) {
setIsNavbarVisible(false);
} else if (scrollTop + windowHeight < documentHeight) {
setIsNavbarVisible(true);
}
},
[navbarHeight, isFocusedAnchor],
);
useLocationChange((locationChangeEvent) => { useLocationChange((locationChangeEvent) => {
if (!hideOnScroll || locationChangeEvent.location.hash) { if (!hideOnScroll) {
return;
}
if (locationChangeEvent.location.hash) {
isFocusedAnchor.current = true;
setIsNavbarVisible(false);
return; return;
} }
setIsNavbarVisible(true); setIsNavbarVisible(true);
}); });
useEffect(() => {
if (!hideOnScroll) {
return;
}
if (!location.hash) {
return;
}
isFocusedAnchor.current = true;
}, [location.hash]);
return { return {
navbarRef, navbarRef,
isNavbarVisible, isNavbarVisible,

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {useEffect, useRef} from 'react'; import {useEffect} from 'react';
import {useLocation} from '@docusaurus/router'; import {useLocation} from '@docusaurus/router';
import {Location} from '@docusaurus/history'; import {Location} from '@docusaurus/history';
import {usePrevious} from './usePrevious'; import {usePrevious} from './usePrevious';
@ -20,15 +20,8 @@ type OnLocationChange = (locationChangeEvent: LocationChangeEvent) => void;
export function useLocationChange(onLocationChange: OnLocationChange): void { export function useLocationChange(onLocationChange: OnLocationChange): void {
const location = useLocation(); const location = useLocation();
const previousLocation = usePrevious(location); const previousLocation = usePrevious(location);
const isFirst = useRef<boolean>(true);
useEffect(() => { useEffect(() => {
// Prevent first effect to trigger the listener on mount
if (isFirst.current) {
isFirst.current = false;
return;
}
onLocationChange({ onLocationChange({
location, location,
previousLocation, previousLocation,