fix: Fully optimize rendering of navigations
Fallback to vanillaJS due to React taking a significant amount of rendering time when we wanted to only update two classes on the navigation bar. In addition to that, turns out Firefox has a decade-old bug related to history.replaceState that causes it to leak memory. This is a known issue and it's not going to be fixed. Our solution is to just get rid of the history.replaceState and not update the URL when you're scrolling. Tough luck, but hey, not gonna fix a decade old bug for a simple throw-away website.
This commit is contained in:
parent
f1caef1058
commit
63fca6b5ad
|
|
@ -3,18 +3,15 @@
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
|
||||||
import type { Sections, translations } from "@/i18n/translations"
|
import type { Sections, translations } from "@/i18n/translations"
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Menu } from "lucide-react"
|
import { Menu } from "lucide-react"
|
||||||
import { LanguageSelector } from "./ui/language-selector"
|
import { LanguageSelector } from "./ui/language-selector"
|
||||||
|
|
||||||
export function MobileNav({
|
export function MobileNav({
|
||||||
t,
|
t,
|
||||||
linksOrder,
|
linksOrder,
|
||||||
activeSection
|
|
||||||
}: {
|
}: {
|
||||||
t: typeof translations.pl
|
t: typeof translations.pl
|
||||||
linksOrder: Array<Sections>
|
linksOrder: Array<Sections>
|
||||||
activeSection: Sections
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Sheet>
|
<Sheet>
|
||||||
|
|
@ -30,9 +27,7 @@ export function MobileNav({
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<nav className="flex flex-col gap-4 mt-8">
|
<nav className="flex flex-col gap-4 mt-8">
|
||||||
{linksOrder.map((value) => (
|
{linksOrder.map((value) => (
|
||||||
<a key={value} href={`#${value}`} className={cn("text-lg hover:text-primary transition-colors", {
|
<a key={value} href={`#${value}`} className="text-lg hover:text-primary transition-colors">
|
||||||
'text-primary': activeSection === value
|
|
||||||
})}>
|
|
||||||
{t.nav[value]}
|
{t.nav[value]}
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button"
|
||||||
import { Sections, type translations } from "@/i18n/translations"
|
import { Sections, type translations } from "@/i18n/translations"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { MoonIcon, SunIcon } from "lucide-react"
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
import { useEffect, useState } from "react"
|
import { useLayoutEffect, useRef } from "react"
|
||||||
import { MobileNav } from "./mobile-nav"
|
import { MobileNav } from "./mobile-nav"
|
||||||
import { useTheme } from "./providers"
|
import { useTheme } from "./providers"
|
||||||
import { LanguageSelector } from "./ui/language-selector"
|
import { LanguageSelector } from "./ui/language-selector"
|
||||||
|
|
@ -25,10 +25,10 @@ export function Nav({
|
||||||
t: typeof translations.pl
|
t: typeof translations.pl
|
||||||
}) {
|
}) {
|
||||||
const { theme, setTheme } = useTheme()
|
const { theme, setTheme } = useTheme()
|
||||||
const [activeSection, setActiveSection] = useState<Sections>("about")
|
const previous = useRef<Sections>(linksOrder[0])
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const options = {
|
const options = {
|
||||||
root: null,
|
root: null,
|
||||||
rootMargin: "-10px",
|
rootMargin: "-10px",
|
||||||
|
|
@ -36,25 +36,43 @@ export function Nav({
|
||||||
};
|
};
|
||||||
let timeout: NodeJS.Timeout | null = null;
|
let timeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
const sections = linksOrder.map(value => document.getElementById(value));
|
||||||
|
const subs = linksOrder.reduce((acc, value) => {
|
||||||
|
acc[value] = document.querySelector('[data-sub="' + value + '"]')!;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<Sections, HTMLAnchorElement>);
|
||||||
|
const links = linksOrder.reduce((acc, value) => {
|
||||||
|
acc[value] = document.querySelector('[href="#' + value + '"]')!;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<Sections, HTMLAnchorElement>);
|
||||||
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = new IntersectionObserver((entries) => {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
entries.forEach(entry => {
|
for (const entry of entries) {
|
||||||
const target = entry.target.id as keyof (typeof translations.pl)["nav"]
|
const target = entry.target.id as keyof (typeof translations.pl)["nav"]
|
||||||
if (entry.isIntersecting) {
|
if (entry.intersectionRatio > 0) {
|
||||||
setActiveSection(target);
|
// FIXME: This seems to be VERY broken on firefox.
|
||||||
if (window.location.hash !== `#${target}` && history.replaceState) {
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1250972
|
||||||
timeout = setTimeout(() => {
|
// It basically spikes up CPU usage to some enormous values just to update the hash, like WTF firefox.
|
||||||
history.replaceState(null, "", `#${target}`)
|
// if (history.replaceState) {
|
||||||
}, 150)
|
// timeout = setTimeout(() => {
|
||||||
|
// history.replaceState(null, "", `#${target}`)
|
||||||
|
// }, 150)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
subs[previous.current]?.classList.remove('scale-x-100');
|
||||||
|
links[previous.current]?.classList.remove('text-primary');
|
||||||
|
previous.current = target;
|
||||||
|
|
||||||
|
subs[previous.current]?.classList.add('scale-x-100');
|
||||||
|
links[previous.current]?.classList.add('text-primary');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const sections = linksOrder.map(value => document.getElementById(value));
|
|
||||||
sections.forEach(section => {
|
sections.forEach(section => {
|
||||||
if (section) {
|
if (section) {
|
||||||
observer.observe(section);
|
observer.observe(section);
|
||||||
|
|
@ -62,11 +80,7 @@ export function Nav({
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
sections.forEach(section => {
|
observer.disconnect()
|
||||||
if (section) {
|
|
||||||
observer.unobserve(section);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -88,13 +102,10 @@ export function Nav({
|
||||||
<a
|
<a
|
||||||
key={value}
|
key={value}
|
||||||
href={`#${value}`}
|
href={`#${value}`}
|
||||||
className={cn("text-sm md:text-md hover:text-primary transition-colors relative group", {
|
className="text-sm md:text-md hover:text-primary transition-colors relative group will-change-[color]"
|
||||||
'text-primary': activeSection === value
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{t.nav[value]}
|
{t.nav[value]}
|
||||||
<span className={cn("absolute inset-x-0 -bottom-1 h-0.5 bg-primary transform scale-x-0 group-hover:scale-x-100 transition-transform", {
|
<span data-sub={value} className={cn("absolute inset-x-0 -bottom-1 h-0.5 bg-primary transform scale-x-0 group-hover:scale-x-100 transition-transform will-change-transform", {
|
||||||
'scale-x-100': activeSection === value
|
|
||||||
})} />
|
})} />
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
|
@ -113,7 +124,7 @@ export function Nav({
|
||||||
|
|
||||||
{/* Mobile Navigation */}
|
{/* Mobile Navigation */}
|
||||||
<div className="md:hidden ml-2">
|
<div className="md:hidden ml-2">
|
||||||
<MobileNav t={t} linksOrder={linksOrder} activeSection={activeSection} />
|
<MobileNav t={t} linksOrder={linksOrder} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue