feat(Navbar): custom 800ms ease-in-out smooth scroll via rAF

Replaces browser scroll-behavior:smooth (no duration control) with a
requestAnimationFrame loop using cubic ease-in-out over 800ms. Offset
accounts for navbar height (100px) via getBoundingClientRect + scrollY.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-06-30 21:47:09 +00:00
parent 4aa5b924fc
commit 2ed1acf997
2 changed files with 27 additions and 2 deletions
+27 -1
View File
@@ -4,6 +4,28 @@ import { useEffect, useState } from "react";
import Link from "next/link";
import Image from "next/image";
const NAVBAR_HEIGHT = 100; // px — matches h-[6.25rem]
const SCROLL_DURATION = 800; // ms
function smoothScrollTo(targetY: number) {
const startY = window.scrollY;
const distance = targetY - startY;
const startTime = performance.now();
function step(now: number) {
const elapsed = now - startTime;
const progress = Math.min(elapsed / SCROLL_DURATION, 1);
// ease-in-out cubic
const ease = progress < 0.5
? 4 * progress ** 3
: 1 - (-2 * progress + 2) ** 3 / 2;
window.scrollTo(0, startY + distance * ease);
if (progress < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
const navLinks = [
{ label: "Werkzeuge", href: "#werkzeuge" },
{ label: "Blog", href: "#blog" },
@@ -50,7 +72,11 @@ export function Navbar() {
href={link.href}
onClick={(e) => {
e.preventDefault();
document.getElementById(link.href.slice(1))?.scrollIntoView({ behavior: "smooth" });
const el = document.getElementById(link.href.slice(1));
if (el) {
const top = el.getBoundingClientRect().top + window.scrollY - NAVBAR_HEIGHT;
smoothScrollTo(top);
}
history.replaceState(null, "", link.href);
}}
className="text-[1.125rem] font-semibold text-[#222221] tracking-[0.01125rem] hover:text-[#f6a701] transition-colors cursor-pointer"
-1
View File
@@ -13,7 +13,6 @@
}
html {
scroll-behavior: smooth;
scroll-padding-top: 6.25rem; /* navbar height */
}