Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions apps/website/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ import { motion } from 'framer-motion'
import { Link } from 'react-router-dom'
import { useI18n } from '../i18n/context'

// Smooth scrolling is animation-driven, so it silently does nothing when
// animations are not running — a hidden or backgrounded tab, or a reader who has
// asked their system for reduced motion. Getting there instantly is always better
// than not getting there at all.
function scrollBehaviour(): ScrollBehavior {
const reduced = typeof window.matchMedia === 'function'
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches
return reduced || document.hidden ? 'auto' : 'smooth'
}

// Under HashRouter a bare `#section` is read as a route, so these links used to
// dump the reader on the homepage without scrolling to what they clicked. This
// sends them home when needed and then finds the section once it exists.
Expand All @@ -11,13 +21,15 @@ function goToSection(e: React.MouseEvent, id: string) {
const hash = window.location.hash
const onHome = hash === '' || hash === '#' || hash === '#/'
if (!onHome) window.location.hash = '#/'
// Timer, not requestAnimationFrame: rAF does not fire in a hidden tab, and
// sections further down the homepage mount lazily.
let tries = 0
const findIt = () => {
const tick = () => {
const el = document.getElementById(id)
if (el) { el.scrollIntoView({ behavior: 'smooth' }); return }
if (++tries < 40) requestAnimationFrame(findIt)
if (el) { el.scrollIntoView({ behavior: scrollBehaviour() }); return }
if (++tries < 50) setTimeout(tick, 80)
}
requestAnimationFrame(findIt)
tick()
}

export default function Footer() {
Expand Down
44 changes: 32 additions & 12 deletions apps/website/src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,36 @@ const PAGES: PageLink[] = [
{ href: DOCS_URL, en: 'Docs', ru: 'Документация', note: 'Full documentation', noteRu: 'Полная документация', external: true },
]

// Smooth scrolling is animation-driven, so it silently does nothing when
// animations are not running — a hidden or backgrounded tab, or a reader who has
// asked their system for reduced motion. Getting there instantly is always better
// than not getting there at all.
function scrollBehaviour(): ScrollBehavior {
const reduced = typeof window.matchMedia === 'function'
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches
return reduced || document.hidden ? 'auto' : 'smooth'
}

// Find a section that may not have mounted yet, then scroll to it.
//
// Uses a timer rather than requestAnimationFrame on purpose: rAF is throttled to
// zero in a background or hidden tab, so a correctness path built on it simply
// never runs there. Sections further down the homepage are also lazily rendered,
// so the window has to be generous — 4 seconds of 80ms polls, which stops as soon
// as the element appears.
function scrollToSectionWhenReady(id: string) {
let tries = 0
const tick = () => {
const el = document.getElementById(id)
if (el) {
el.scrollIntoView({ behavior: scrollBehaviour() })
return
}
if (++tries < 50) setTimeout(tick, 80)
}
tick()
}

export default memo(function Navigation() {
const { t, lang } = useI18n()
const [active, setActive] = useState('hero')
Expand Down Expand Up @@ -74,20 +104,10 @@ export default memo(function Navigation() {
const onHome = hash === '' || hash === '#' || hash === '#/'
if (!onHome) {
window.location.hash = '#/'
// One frame is not enough: the homepage has to mount before the target
// element exists. Poll briefly instead of guessing a delay.
let tries = 0
const findIt = () => {
const el = document.getElementById(id)
if (el) { el.scrollIntoView({ behavior: 'smooth' }); return }
if (++tries < 40) requestAnimationFrame(findIt)
}
requestAnimationFrame(findIt)
scrollToSectionWhenReady(id)
return
}
setTimeout(() => {
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' })
}, 100)
scrollToSectionWhenReady(id)
}, [])

// Handle escape key to close menu
Expand Down
Loading