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,18 +71,23 @@ 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.
// No lastScrollTop means component is just being mounted.
// Not really a scroll event from the user, so we ignore it // Not really a scroll event from the user, so we ignore it
if (!lastPosition) { if (!lastScrollTop) {
return;
}
if (isFocusedAnchor.current) {
isFocusedAnchor.current = false;
return; return;
} }
const lastScrollTop = lastPosition.scrollY;
const isScrollingUp = scrollTop < lastScrollTop; const isScrollingUp = scrollTop < lastScrollTop;
@ -101,9 +109,14 @@ function BackToTopButton(): JSX.Element {
} else { } else {
setShow(false); setShow(false);
} }
}, });
[location],
); useLocationChange((locationChangeEvent) => {
if (locationChangeEvent.location.hash) {
isFocusedAnchor.current = true;
setShow(false);
}
});
return ( return (
<button <button

View file

@ -5,47 +5,34 @@
* 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) => {
const scrollTop = currentPosition.scrollY;
const lastScrollTop = lastPosition?.scrollY;
if (!hideOnScroll) { if (!hideOnScroll) {
return; return;
} }
if (scrollTop < navbarHeight) {
setIsNavbarVisible(true);
return;
}
if (isFocusedAnchor.current) { if (isFocusedAnchor.current) {
isFocusedAnchor.current = false; isFocusedAnchor.current = false;
setIsNavbarVisible(false);
return; return;
} }
if (lastScrollTop && scrollTop === 0) { const scrollTop = currentPosition.scrollY;
setIsNavbarVisible(true); const lastScrollTop = lastPosition?.scrollY;
}
const documentHeight = const documentHeight =
document.documentElement.scrollHeight - navbarHeight; document.documentElement.scrollHeight - navbarHeight.current;
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
if (lastScrollTop && scrollTop >= lastScrollTop) { if (lastScrollTop && scrollTop >= lastScrollTop) {
@ -53,30 +40,22 @@ const useHideableNavbar = (hideOnScroll: boolean): useHideableNavbarReturns => {
} else if (scrollTop + windowHeight < documentHeight) { } else if (scrollTop + windowHeight < documentHeight) {
setIsNavbarVisible(true); 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,