docs: fix irregular scrolling in code tabs (#5239)
* docs: fix blocking scroll util * remove unnecessary util methods
This commit is contained in:
@@ -53,11 +53,19 @@ type ScrollController = {
|
||||
enableScrollEvents: () => void
|
||||
/** Disable scroll events in `useScrollPosition`. */
|
||||
disableScrollEvents: () => void
|
||||
/** Retrieves the scrollable element. By default, it's window. */
|
||||
getScrollableElement: () => Element | Window
|
||||
}
|
||||
|
||||
function useScrollControllerContextValue(): ScrollController {
|
||||
function useScrollControllerContextValue(
|
||||
scrollableSelector: string
|
||||
): ScrollController {
|
||||
const scrollEventsEnabledRef = useRef(true)
|
||||
|
||||
const getScrollableElement = useCallback(() => {
|
||||
return (document.querySelector(scrollableSelector) as Element) || window
|
||||
}, [scrollableSelector])
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
scrollEventsEnabledRef,
|
||||
@@ -67,8 +75,9 @@ function useScrollControllerContextValue(): ScrollController {
|
||||
disableScrollEvents: () => {
|
||||
scrollEventsEnabledRef.current = false
|
||||
},
|
||||
getScrollableElement,
|
||||
}),
|
||||
[]
|
||||
[getScrollableElement]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,10 +87,12 @@ const ScrollMonitorContext = React.createContext<ScrollController | undefined>(
|
||||
|
||||
export function ScrollControllerProvider({
|
||||
children,
|
||||
scrollableSelector = "",
|
||||
}: {
|
||||
children: ReactNode
|
||||
scrollableSelector?: string
|
||||
}): JSX.Element {
|
||||
const value = useScrollControllerContextValue()
|
||||
const value = useScrollControllerContextValue(scrollableSelector)
|
||||
return (
|
||||
<ScrollMonitorContext.Provider value={value}>
|
||||
{children}
|
||||
@@ -165,6 +176,7 @@ type UseScrollPositionSaver = {
|
||||
}
|
||||
|
||||
function useScrollPositionSaver(): UseScrollPositionSaver {
|
||||
const { getScrollableElement } = useScrollController()
|
||||
const lastElementRef = useRef<{ elem: HTMLElement | null; top: number }>({
|
||||
elem: null,
|
||||
top: 0,
|
||||
@@ -187,7 +199,7 @@ function useScrollPositionSaver(): UseScrollPositionSaver {
|
||||
const newTop = elem.getBoundingClientRect().top
|
||||
const heightDiff = newTop - top
|
||||
if (heightDiff) {
|
||||
window.scrollBy({ left: 0, top: heightDiff })
|
||||
getScrollableElement().scrollBy({ left: 0, top: heightDiff })
|
||||
}
|
||||
lastElementRef.current = { elem: null, top: 0 }
|
||||
|
||||
@@ -258,74 +270,3 @@ export function useScrollPositionBlocker(): {
|
||||
blockElementScrollPositionUntilNextRender,
|
||||
}
|
||||
}
|
||||
|
||||
type CancelScrollTop = () => void
|
||||
|
||||
function smoothScrollNative(top: number): CancelScrollTop {
|
||||
window.scrollTo({ top, behavior: "smooth" })
|
||||
return () => {
|
||||
// Nothing to cancel, it's natively cancelled if user tries to scroll down
|
||||
}
|
||||
}
|
||||
|
||||
function smoothScrollPolyfill(top: number): CancelScrollTop {
|
||||
let raf: number | null = null
|
||||
const isUpScroll = document.documentElement.scrollTop > top
|
||||
function rafRecursion() {
|
||||
const currentScroll = document.documentElement.scrollTop
|
||||
if (
|
||||
(isUpScroll && currentScroll > top) ||
|
||||
(!isUpScroll && currentScroll < top)
|
||||
) {
|
||||
raf = requestAnimationFrame(rafRecursion)
|
||||
window.scrollTo(0, Math.floor((currentScroll - top) * 0.85) + top)
|
||||
}
|
||||
}
|
||||
rafRecursion()
|
||||
|
||||
// Break the recursion. Prevents the user from "fighting" against that
|
||||
// recursion producing a weird UX
|
||||
return () => raf && cancelAnimationFrame(raf)
|
||||
}
|
||||
|
||||
/**
|
||||
* A "smart polyfill" of `window.scrollTo({ top, behavior: "smooth" })`.
|
||||
* This currently always uses a polyfilled implementation unless
|
||||
* `scroll-behavior: smooth` has been set in CSS, because native support
|
||||
* detection for scroll behavior seems unreliable.
|
||||
*
|
||||
* This hook does not do anything by itself: it returns a start and a stop
|
||||
* handle. You can execute either handle at any time.
|
||||
*/
|
||||
export function useSmoothScrollTo(): {
|
||||
/**
|
||||
* Start the scroll.
|
||||
*
|
||||
* @param top The final scroll top position.
|
||||
*/
|
||||
startScroll: (top: number) => void
|
||||
/**
|
||||
* A cancel function, because the non-native smooth scroll-top
|
||||
* implementation must be interrupted if user scrolls down. If there's no
|
||||
* existing animation or the scroll is using native behavior, this is a no-op.
|
||||
*/
|
||||
cancelScroll: CancelScrollTop
|
||||
} {
|
||||
const cancelRef = useRef<CancelScrollTop | null>(null)
|
||||
// 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
|
||||
// For now, we only use native scroll behavior if smooth is already set,
|
||||
// because otherwise the polyfill produces a weird UX when both CSS and JS try
|
||||
// to scroll a page, and they cancel each other.
|
||||
const supportsNativeSmoothScrolling =
|
||||
getComputedStyle(document.documentElement).scrollBehavior === "smooth"
|
||||
return {
|
||||
startScroll: (top: number) => {
|
||||
cancelRef.current = supportsNativeSmoothScrolling
|
||||
? smoothScrollNative(top)
|
||||
: smoothScrollPolyfill(top)
|
||||
},
|
||||
cancelScroll: () => cancelRef.current?.(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user