fix(docs): anchor link scrolling with conflict prevention (#5186)

This commit is contained in:
KinfeMichael Tariku
2025-10-09 19:14:46 +03:00
committed by GitHub
parent 72d63b6e0f
commit 91778b5bbd
4 changed files with 122 additions and 4 deletions

View File

@@ -7,6 +7,8 @@
:root {
--fd-nav-height: 56px;
--fd-banner-height: 0px;
--fd-tocnav-height: 0px;
--background: oklch(1 0 0);
@@ -255,15 +257,19 @@
}
html {
scroll-behavior: smooth;
scroll-behavior: auto;
scroll-padding-top: calc(
var(--fd-nav-height) +
var(--fd-nav-height, 56px) +
var(--fd-banner-height, 0px) +
var(--fd-tocnav-height, 0px) +
16px
24px
);
}
html:not([data-anchor-scrolling]) {
scroll-behavior: smooth;
}
/* Global, accessible custom scrollbars */
* {
scrollbar-width: thin; /* Firefox */

View File

@@ -10,6 +10,7 @@ import { Analytics } from "@vercel/analytics/react";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "@/components/ui/sonner";
import { CustomSearchDialog } from "@/components/search-dialog";
import { AnchorScroll } from "@/components/anchor-scroll-fix";
export const metadata = createMetadata({
title: {
@@ -58,6 +59,7 @@ export default function Layout({ children }: { children: ReactNode }) {
: undefined,
}}
>
<AnchorScroll />
<NavbarProvider>
<Navbar />
{children}

View File

@@ -0,0 +1,107 @@
"use client";
import { useEffect, useRef } from "react";
export function AnchorScroll() {
const scrollTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const isScrollingRef = useRef(false);
useEffect(() => {
function calculateScrollOffset() {
const root = document.documentElement;
const navHeight = parseInt(
getComputedStyle(root).getPropertyValue("--fd-nav-height") || "56",
);
const bannerHeight = parseInt(
getComputedStyle(root).getPropertyValue("--fd-banner-height") || "0",
);
const tocnavHeight = parseInt(
getComputedStyle(root).getPropertyValue("--fd-tocnav-height") || "0",
);
return navHeight + bannerHeight + tocnavHeight + 24;
}
function smoothScrollToElement(element: HTMLElement) {
if (isScrollingRef.current) return;
isScrollingRef.current = true;
document.documentElement.setAttribute("data-anchor-scrolling", "true");
const elementRect = element.getBoundingClientRect();
const scrollOffset = calculateScrollOffset();
const targetPosition =
window.pageYOffset + elementRect.top - scrollOffset;
// Simple smooth scroll animation
const startPosition = window.pageYOffset;
const distance = targetPosition - startPosition;
const duration = Math.min(500, Math.abs(distance) * 0.3);
const startTime = performance.now();
function animateScroll(currentTime: number) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
const currentPosition =
startPosition + distance * easeOutCubic(progress);
window.scrollTo(0, currentPosition);
if (progress < 1) {
requestAnimationFrame(animateScroll);
} else {
document.documentElement.removeAttribute("data-anchor-scrolling");
isScrollingRef.current = false;
}
}
requestAnimationFrame(animateScroll);
}
function handleAnchorScroll() {
if (window.location.hash) {
const element = document.getElementById(window.location.hash.slice(1));
if (element) {
scrollTimeoutRef.current = setTimeout(
() => smoothScrollToElement(element),
100,
);
}
}
}
function handleHashChange() {
const element = document.getElementById(window.location.hash.slice(1));
if (element) smoothScrollToElement(element);
}
function handleAnchorClick(event: Event) {
const link = (event.target as HTMLElement).closest(
'a[href^="#"]',
) as HTMLAnchorElement;
if (link?.hash) {
event.preventDefault();
const element = document.getElementById(link.hash.slice(1));
if (element) {
history.pushState(null, "", link.hash);
smoothScrollToElement(element);
}
}
}
handleAnchorScroll();
window.addEventListener("hashchange", handleHashChange);
document.addEventListener("click", handleAnchorClick);
return () => {
if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
window.removeEventListener("hashchange", handleHashChange);
document.removeEventListener("click", handleAnchorClick);
};
}, []);
return null;
}

View File

@@ -48,11 +48,14 @@ export function NavProvider({
if (transparentMode !== "top") return;
const listener = () => {
if (document.documentElement.hasAttribute("data-anchor-scrolling")) {
return;
}
setTransparent(window.scrollY < 10);
};
listener();
window.addEventListener("scroll", listener);
window.addEventListener("scroll", listener, { passive: true });
return () => {
window.removeEventListener("scroll", listener);
};