diff --git a/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx b/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx index f9a3d04c5e..d5e4edfc41 100644 --- a/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx @@ -5,122 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import React, {useRef, useState} from 'react'; +import React from 'react'; import clsx from 'clsx'; import {translate} from '@docusaurus/Translate'; +import {ThemeClassNames, useBackToTopButton} from '@docusaurus/theme-common'; import styles from './styles.module.css'; -import { - ThemeClassNames, - useScrollPosition, - useLocationChange, -} from '@docusaurus/theme-common'; - -const threshold = 300; - -// Not all have support for smooth scrolling (particularly Safari mobile iOS) -// TODO proper detection is currently unreliable! -// see https://github.com/wessberg/scroll-behavior-polyfill/issues/16 -const SupportsNativeSmoothScrolling = false; -// const SupportsNativeSmoothScrolling = -// ExecutionEnvironment.canUseDOM && -// 'scrollBehavior' in document.documentElement.style; - -type CancelScrollTop = () => void; - -function smoothScrollTopNative(): CancelScrollTop { - window.scrollTo({top: 0, behavior: 'smooth'}); - return () => { - // Nothing to cancel, it's natively cancelled if user tries to scroll down - }; -} - -function smoothScrollTopPolyfill(): CancelScrollTop { - let raf: number | null = null; - function rafRecursion() { - const currentScroll = document.documentElement.scrollTop; - if (currentScroll > 0) { - raf = requestAnimationFrame(rafRecursion); - window.scrollTo(0, Math.floor(currentScroll * 0.85)); - } - } - rafRecursion(); - - // Break the recursion. Prevents the user from "fighting" against that - // recursion producing a weird UX - return () => raf && cancelAnimationFrame(raf); -} - -type UseSmoothScrollTopReturn = { - // We use a cancel function because the non-native smooth scroll-top - // implementation must be interrupted if user scroll down - smoothScrollTop: () => void; - cancelScrollToTop: CancelScrollTop; -}; - -function useSmoothScrollToTop(): UseSmoothScrollTopReturn { - const lastCancelRef = useRef(null); - - function smoothScrollTop(): void { - lastCancelRef.current = SupportsNativeSmoothScrolling - ? smoothScrollTopNative() - : smoothScrollTopPolyfill(); - } - - return { - smoothScrollTop, - cancelScrollToTop: () => lastCancelRef.current?.(), - }; -} export default function BackToTopButton(): JSX.Element { - const [show, setShow] = useState(false); - const isFocusedAnchor = useRef(false); - const {smoothScrollTop, cancelScrollToTop} = useSmoothScrollToTop(); - - useScrollPosition(({scrollY: scrollTop}, lastPosition) => { - const lastScrollTop = lastPosition?.scrollY; - - // No lastScrollTop means component is just being mounted. - // Not really a scroll event from the user, so we ignore it - 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); - } - } else { - setShow(false); - } - }); - - useLocationChange((locationChangeEvent) => { - if (locationChangeEvent.location.hash) { - isFocusedAnchor.current = true; - setShow(false); - } - }); - + const {shown, scrollToTop} = useBackToTopButton({threshold: 300}); return (