mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Fix scroll jump and scroll reset (#3756)
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
||||
} from '@gitbook/api';
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
import { PageAside } from '@/components/PageAside';
|
||||
import { PageBody, PageCover } from '@/components/PageBody';
|
||||
@@ -75,9 +74,7 @@ export async function SitePage(props: SitePageProps) {
|
||||
insightsDisplayContext={SiteInsightsDisplayContext.Site}
|
||||
/>
|
||||
</div>
|
||||
<React.Suspense fallback={null}>
|
||||
<PageClientLayout />
|
||||
</React.Suspense>
|
||||
<PageClientLayout />
|
||||
</div>
|
||||
</PageContextProvider>
|
||||
);
|
||||
|
||||
@@ -7,9 +7,8 @@ import { useHash } from './useHash';
|
||||
import { usePrevious } from './usePrevious';
|
||||
|
||||
/**
|
||||
* Scroll the page to an anchor point or
|
||||
* to the top of the page when navigating between pages (pathname)
|
||||
* or sections of a page (hash).
|
||||
* Scroll the page to the hash or reset scroll to the top.
|
||||
* Only triggered while navigating in the app, not for initial load.
|
||||
*/
|
||||
export function useScrollPage() {
|
||||
const hash = useHash();
|
||||
@@ -17,6 +16,10 @@ export function useScrollPage() {
|
||||
const pathname = usePathname();
|
||||
const previousPathname = usePrevious(pathname);
|
||||
React.useLayoutEffect(() => {
|
||||
if (!previousHash && !previousPathname) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is no change in pathname or hash, do nothing
|
||||
if (previousHash === hash && previousPathname === pathname) {
|
||||
return;
|
||||
@@ -31,13 +34,10 @@ export function useScrollPage() {
|
||||
block: 'start',
|
||||
behavior: 'smooth',
|
||||
});
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was a hash but not anymore, scroll to top
|
||||
if (previousHash && !hash) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
window.scrollTo(0, 0);
|
||||
}, [hash, previousHash, pathname, previousPathname]);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import * as React from 'react';
|
||||
import { useScrollListener } from '../hooks/useScrollListener';
|
||||
import { Button } from './Button';
|
||||
|
||||
/**
|
||||
@@ -46,56 +47,53 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
|
||||
const language = useLanguage();
|
||||
|
||||
useScrollListener(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
setScrollPosition(
|
||||
orientation === 'horizontal' ? container.scrollLeft : container.scrollTop
|
||||
);
|
||||
}, containerRef);
|
||||
|
||||
React.useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update scroll position on scroll using requestAnimationFrame
|
||||
const scrollListener: EventListener = () => {
|
||||
requestAnimationFrame(() => {
|
||||
setScrollPosition(
|
||||
orientation === 'horizontal' ? container.scrollLeft : container.scrollTop
|
||||
);
|
||||
});
|
||||
};
|
||||
container.addEventListener('scroll', scrollListener);
|
||||
|
||||
// Update max scroll position using resize observer
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
const containerEntry = entries.find((i) => i.target === containerRef.current);
|
||||
if (containerEntry) {
|
||||
const ro = new ResizeObserver((entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry) {
|
||||
setScrollSize(
|
||||
orientation === 'horizontal'
|
||||
? containerEntry.target.scrollWidth - containerEntry.target.clientWidth - 1
|
||||
: containerEntry.target.scrollHeight -
|
||||
containerEntry.target.clientHeight -
|
||||
1
|
||||
? entry.target.scrollWidth - entry.target.clientWidth - 1
|
||||
: entry.target.scrollHeight - entry.target.clientHeight - 1
|
||||
);
|
||||
}
|
||||
});
|
||||
resizeObserver.observe(container);
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('scroll', scrollListener);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
ro.observe(container);
|
||||
|
||||
return () => ro.disconnect();
|
||||
}, [orientation]);
|
||||
|
||||
// Scroll to the active item
|
||||
React.useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container || !activeId) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
const activeItem = container.querySelector(`#${CSS.escape(activeId)}`);
|
||||
if (activeItem) {
|
||||
activeItem.scrollIntoView({
|
||||
inline: 'center',
|
||||
block: 'center',
|
||||
});
|
||||
if (!activeId) {
|
||||
return;
|
||||
}
|
||||
const activeItem = document.getElementById(activeId);
|
||||
if (!activeItem || !container.contains(activeItem)) {
|
||||
return;
|
||||
}
|
||||
scrollToElementInContainer(activeItem, container);
|
||||
}, [activeId]);
|
||||
|
||||
const scrollFurther = () => {
|
||||
@@ -103,6 +101,7 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.scrollTo({
|
||||
top: orientation === 'vertical' ? scrollPosition + container.clientHeight : undefined,
|
||||
left: orientation === 'horizontal' ? scrollPosition + container.clientWidth : undefined,
|
||||
@@ -115,6 +114,7 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.scrollTo({
|
||||
top: orientation === 'vertical' ? scrollPosition - container.clientHeight : undefined,
|
||||
left: orientation === 'horizontal' ? scrollPosition - container.clientWidth : undefined,
|
||||
@@ -194,3 +194,25 @@ export function ScrollContainer(props: ScrollContainerProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to an element in a container.
|
||||
*/
|
||||
function scrollToElementInContainer(element: HTMLElement, container: HTMLElement) {
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
return container.scrollTo({
|
||||
top:
|
||||
container.scrollTop +
|
||||
(rect.top - containerRect.top) -
|
||||
container.clientHeight / 2 +
|
||||
rect.height / 2,
|
||||
left:
|
||||
container.scrollLeft +
|
||||
(rect.left - containerRect.left) -
|
||||
container.clientWidth / 2 +
|
||||
rect.width / 2,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user