Archived
ScrollToTop springt bei /#services nach dem Laden der Schriften zum Abschnitt (vorher landete man oben auf der Startseite bzw. 220 px zu weit unten). Dazu Quelltext-Test gegen 99,9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.1 KiB
TypeScript
68 lines
2.1 KiB
TypeScript
import { Toaster } from "@/components/ui/toaster";
|
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { useEffect } from "react";
|
|
import { BrowserRouter, useLocation } from "react-router-dom";
|
|
import { ThemeProvider } from "@/components/ThemeProvider";
|
|
import { AnalyticsProvider } from "@/components/AnalyticsProvider";
|
|
import { AppRoutes } from "./routes";
|
|
|
|
const queryClient = new QueryClient();
|
|
|
|
/**
|
|
* Scrollt bei einem Seitenwechsel nach oben. Mit Anker (URL mit #) springt es zum Abschnitt,
|
|
* sobald er gerendert ist, z. B. beim Footer-Link „/#services“ von einer Unterseite aus.
|
|
*/
|
|
export const ScrollToTop = () => {
|
|
const { pathname, hash } = useLocation();
|
|
useEffect(() => {
|
|
if (!hash) {
|
|
window.scrollTo(0, 0);
|
|
return;
|
|
}
|
|
const id = decodeURIComponent(hash.slice(1));
|
|
let versuche = 0;
|
|
let frame = 0;
|
|
let abgebrochen = false;
|
|
const springe = () => {
|
|
const ziel = document.getElementById(id);
|
|
if (!ziel) {
|
|
if (versuche++ < 60) frame = requestAnimationFrame(springe); // höchstens ca. 1 s warten
|
|
return;
|
|
}
|
|
// Erst springen, wenn die Schriften geladen sind: Vorher ist der Text darüber höher,
|
|
// und der Abschnitt landet nach dem Nachladen ein Stück zu weit oben.
|
|
const schriften = document.fonts?.ready ?? Promise.resolve();
|
|
schriften.then(() => {
|
|
if (!abgebrochen) ziel.scrollIntoView();
|
|
});
|
|
};
|
|
springe();
|
|
return () => {
|
|
abgebrochen = true;
|
|
cancelAnimationFrame(frame);
|
|
};
|
|
}, [pathname, hash]);
|
|
return null;
|
|
};
|
|
|
|
const App = () => (
|
|
<ThemeProvider>
|
|
<QueryClientProvider client={queryClient}>
|
|
<TooltipProvider>
|
|
<Toaster />
|
|
<Sonner />
|
|
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
|
<ScrollToTop />
|
|
<AnalyticsProvider>
|
|
<AppRoutes />
|
|
</AnalyticsProvider>
|
|
</BrowserRouter>
|
|
</TooltipProvider>
|
|
</QueryClientProvider>
|
|
</ThemeProvider>
|
|
);
|
|
|
|
export default App;
|