From 06ff6d32e83c1fdea85022da1191148cc643e294 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sun, 13 Apr 2025 19:00:56 +0200 Subject: [PATCH 1/4] look: fix scrolling to sections This fixes: 1. The inability to scroll down to 'contacts' and other lower sections. We just expand the second-to-last section to allow the browser to scroll. 2. Updating the currently active section - this now uses a global view of all visible sections that is updated by the observer diff, thereby fixing the logic. --- src/globals.css | 8 +++++++- src/hooks/color-sections.tsx | 35 +++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/globals.css b/src/globals.css index 670c714..7d3c4f1 100644 --- a/src/globals.css +++ b/src/globals.css @@ -156,6 +156,12 @@ z-index: 10000; } +/* Allow to scroll past the last section, needed for fragment-directed navigation. */ +section#contact { + min-height: 100vh; +} + +/* Fix scrolling to section by fragment, making sure it shows in the right spot and not behind the navbar. */ section { scroll-margin-top: calc(var(--spacing) * 16 + var(--spacing) * 4); -} +} \ No newline at end of file diff --git a/src/hooks/color-sections.tsx b/src/hooks/color-sections.tsx index beab322..0ce6d88 100644 --- a/src/hooks/color-sections.tsx +++ b/src/hooks/color-sections.tsx @@ -6,7 +6,7 @@ export const linksOrder: Array = [ "tickets", "cfp", "details", - "contact" + "contact", ] export function useColorSections(parent: React.RefObject) { @@ -27,6 +27,7 @@ export function useColorSections(parent: React.RefObject) acc[value] = parent.current!.querySelector('[data-sub="' + value + '"]')!; return acc; }, {} as Record); + console.log(subs); const links = linksOrder.reduce((acc, value) => { acc[value] = parent.current!.querySelector('[href="#' + value + '"]')!; return acc; @@ -34,22 +35,32 @@ export function useColorSections(parent: React.RefObject) console.log(links) - const observer = new IntersectionObserver((entries) => { + // Set of currently intersecting sections by ID. + let intersecting: Set = new Set; + const observer = new IntersectionObserver((entries) => { + // Update intersection set based on diff. + let startedIntersecting: Set = new Set; + let stoppedIntersecting: Set = new Set; for (const entry of entries) { - const target = entry.target.id as Sections if (entry.isIntersecting) { - // FIXME: This seems to be VERY broken on firefox. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1250972 - // It basically spikes up CPU usage to some enormous values just to update the hash, like WTF firefox. - // if (history.replaceState) { - // timeout = setTimeout(() => { - // history.replaceState(null, "", `#${target}`) - // }, 150) - // } + startedIntersecting.add(entry.target.id); + } else { + stoppedIntersecting.add(entry.target.id); + } + } + intersecting = intersecting.difference(stoppedIntersecting); + intersecting = intersecting.union(startedIntersecting); + console.log(intersecting); + + // Act upon intersection set to find the highest intersecting section - + // that's our 'active' section. + for (const id of linksOrder) { + if (intersecting.has(id)) { + console.log("best: " + id); subs[previous.current]?.classList.remove('scale-x-100'); links[previous.current]?.classList.remove('text-primary'); - previous.current = target; + previous.current = id; subs[previous.current]?.classList.add('scale-x-100'); links[previous.current]?.classList.add('text-primary'); -- 2.47.1 From 79feb340cda732c9b1612565b9acf18a7ba95d9d Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 13 Apr 2025 19:58:21 +0200 Subject: [PATCH 2/4] fix: improve upon coloring sections --- src/components/landing-page.tsx | 2 +- src/hooks/color-sections.tsx | 49 +++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/components/landing-page.tsx b/src/components/landing-page.tsx index 42edfd5..bc27275 100644 --- a/src/components/landing-page.tsx +++ b/src/components/landing-page.tsx @@ -51,7 +51,7 @@ function NewSection({ after?: ReactElement; }) { return (
-
+
{children} {after}
diff --git a/src/hooks/color-sections.tsx b/src/hooks/color-sections.tsx index 0ce6d88..f74e7b9 100644 --- a/src/hooks/color-sections.tsx +++ b/src/hooks/color-sections.tsx @@ -9,6 +9,13 @@ export const linksOrder: Array = [ "contact", ] +/** + * Those links need to be reverted to account for the smallest section at the bottom. + * This way the intersection still pops the event at correct time, but now + * we account for 'contact' too! + */ +const reversedLinks = linksOrder.toReversed(); + export function useColorSections(parent: React.RefObject) { const previous = useRef(linksOrder[0]) @@ -17,8 +24,8 @@ export function useColorSections(parent: React.RefObject) const options = { root: null, - rootMargin: "-10px", - threshold: 0.5, // Adjust the visibility threshold as needed + rootMargin: "80px 0px 0px 0px", // Top 60% of viewport should matter + threshold: 0.4, // Adjust the visibility threshold as needed }; @@ -33,15 +40,23 @@ export function useColorSections(parent: React.RefObject) return acc; }, {} as Record); - console.log(links) // Set of currently intersecting sections by ID. let intersecting: Set = new Set; + function setCurrentUnderline(id: typeof linksOrder[number]) { + subs[previous.current]?.classList.remove('scale-x-100'); + links[previous.current]?.classList.remove('text-primary'); + previous.current = id; + + subs[previous.current]?.classList.add('scale-x-100'); + links[previous.current]?.classList.add('text-primary'); + } + const observer = new IntersectionObserver((entries) => { // Update intersection set based on diff. - let startedIntersecting: Set = new Set; - let stoppedIntersecting: Set = new Set; + const startedIntersecting: Set = new Set; + const stoppedIntersecting: Set = new Set; for (const entry of entries) { if (entry.isIntersecting) { startedIntersecting.add(entry.target.id); @@ -51,19 +66,13 @@ export function useColorSections(parent: React.RefObject) } intersecting = intersecting.difference(stoppedIntersecting); intersecting = intersecting.union(startedIntersecting); - console.log(intersecting); - // Act upon intersection set to find the highest intersecting section - + // Act upon intersection set to find the lowest intersecting section - // that's our 'active' section. - for (const id of linksOrder) { + for (const id of reversedLinks) { if (intersecting.has(id)) { - console.log("best: " + id); - subs[previous.current]?.classList.remove('scale-x-100'); - links[previous.current]?.classList.remove('text-primary'); - previous.current = id; - - subs[previous.current]?.classList.add('scale-x-100'); - links[previous.current]?.classList.add('text-primary'); + console.log("intersecting", id); + setCurrentUnderline(id); break; } } @@ -80,4 +89,14 @@ export function useColorSections(parent: React.RefObject) }; }, [parent]); + // Initialize the colors once + useLayoutEffect(() => { + const sub = document.querySelector('[data-sub="' + linksOrder[0] + '"]'); + const link = document.querySelector('[href="#' + linksOrder[0] + '"]'); + if (sub && link) { + sub.classList.add('scale-x-100'); + link.classList.add('text-primary'); + } + }, []); + } -- 2.47.1 From 133c82790e1ea46e371d278a4c6664155d9f403c Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 13 Apr 2025 19:58:52 +0200 Subject: [PATCH 3/4] fix: revert sections height --- src/globals.css | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/globals.css b/src/globals.css index 7d3c4f1..0e08f62 100644 --- a/src/globals.css +++ b/src/globals.css @@ -156,12 +156,7 @@ z-index: 10000; } -/* Allow to scroll past the last section, needed for fragment-directed navigation. */ -section#contact { - min-height: 100vh; -} - /* Fix scrolling to section by fragment, making sure it shows in the right spot and not behind the navbar. */ section { scroll-margin-top: calc(var(--spacing) * 16 + var(--spacing) * 4); -} \ No newline at end of file +} -- 2.47.1 From fb3d783ebcb69b568fd1a6b01c580052144c8cfd Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 13 Apr 2025 20:48:58 +0200 Subject: [PATCH 4/4] fix: make it work with worse motions --- src/components/nav.tsx | 20 ++++++++++++++++++-- src/globals.css | 3 --- src/hooks/color-sections.tsx | 32 +++++++++++++++++++++++--------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/components/nav.tsx b/src/components/nav.tsx index e714728..51ced98 100644 --- a/src/components/nav.tsx +++ b/src/components/nav.tsx @@ -1,6 +1,6 @@ "use client" import { useColorSections } from "@/hooks/color-sections" -import { type translations } from "@/i18n/translations" +import { Sections, type translations } from "@/i18n/translations" import { cn } from "@/lib/utils" import { useRef } from "react" import { MobileNav } from "./mobile-nav" @@ -20,7 +20,19 @@ export function MainpageNav({ t: typeof translations.pl }) { const parent = useRef(null); - useColorSections(parent); + const previous = useRef(linksOrder[0]) + const forceIgnore = useRef(false); + useColorSections(parent, previous, forceIgnore); + + function setCurrentUnderline(id: typeof linksOrder[number]) { + forceIgnore.current = true; + previous.current = id; + + document.querySelectorAll('[data-sub]').forEach(x => x.classList.remove('scale-x-100')); + document.querySelectorAll('[data-link]').forEach(x => x.classList.remove('text-primary')); + document.querySelector(`[data-sub="${id}"]`)?.classList.add('scale-x-100'); + document.querySelector(`[data-link="${id}"]`)?.classList.add('text-primary'); + } return ( @@ -31,7 +43,11 @@ export function MainpageNav({ { + setCurrentUnderline(value) + }} className="text-sm md:text-md hover:text-primary transition-colors relative group will-change-[color]" + data-link={value} > {t.nav[value]} = [ "about", @@ -16,10 +16,13 @@ export const linksOrder: Array = [ */ const reversedLinks = linksOrder.toReversed(); -export function useColorSections(parent: React.RefObject) { - const previous = useRef(linksOrder[0]) +export function useColorSections(parent: React.RefObject, previous: React.RefObject, forceIgnore: React.MutableRefObject) { + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); useLayoutEffect(() => { + if (prefersReducedMotion.matches) { + return; + } if (parent.current === null) return; const options = { @@ -34,7 +37,6 @@ export function useColorSections(parent: React.RefObject) acc[value] = parent.current!.querySelector('[data-sub="' + value + '"]')!; return acc; }, {} as Record); - console.log(subs); const links = linksOrder.reduce((acc, value) => { acc[value] = parent.current!.querySelector('[href="#' + value + '"]')!; return acc; @@ -45,8 +47,12 @@ export function useColorSections(parent: React.RefObject) let intersecting: Set = new Set; function setCurrentUnderline(id: typeof linksOrder[number]) { - subs[previous.current]?.classList.remove('scale-x-100'); - links[previous.current]?.classList.remove('text-primary'); + for (const sub of Object.values(subs)) { + sub.classList.remove('scale-x-100'); + } + for (const link of Object.values(links)) { + link.classList.remove('text-primary'); + } previous.current = id; subs[previous.current]?.classList.add('scale-x-100'); @@ -54,6 +60,12 @@ export function useColorSections(parent: React.RefObject) } const observer = new IntersectionObserver((entries) => { + if (forceIgnore.current) { + window.onscrollend = () => { + forceIgnore.current = false + } + return; + } // Update intersection set based on diff. const startedIntersecting: Set = new Set; const stoppedIntersecting: Set = new Set; @@ -71,7 +83,6 @@ export function useColorSections(parent: React.RefObject) // that's our 'active' section. for (const id of reversedLinks) { if (intersecting.has(id)) { - console.log("intersecting", id); setCurrentUnderline(id); break; } @@ -87,16 +98,19 @@ export function useColorSections(parent: React.RefObject) return () => { observer.disconnect() }; - }, [parent]); + }, [forceIgnore, parent, prefersReducedMotion.matches, previous]); // Initialize the colors once useLayoutEffect(() => { + if (prefersReducedMotion.matches) { + return; + } const sub = document.querySelector('[data-sub="' + linksOrder[0] + '"]'); const link = document.querySelector('[href="#' + linksOrder[0] + '"]'); if (sub && link) { sub.classList.add('scale-x-100'); link.classList.add('text-primary'); } - }, []); + }, [prefersReducedMotion.matches]); } -- 2.47.1