Skip to content

Commit 0fd64ea

Browse files
committed
fix(homepage): better animations
1 parent 418def7 commit 0fd64ea

2 files changed

Lines changed: 74 additions & 64 deletions

File tree

src/components/Homepage.js

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,54 @@ import Link from "@docusaurus/Link";
55
import useBaseUrl from "@docusaurus/useBaseUrl";
66

77
// --- REVEAL HOOK ---
8+
// Elements already in or above the viewport on mount (e.g., page refreshed
9+
// while scrolled down) are shown instantly without animation.
10+
// We wait a frame for the browser to restore scroll position, then check
11+
// whether the element is in or above the viewport. If so, show instantly.
12+
// Otherwise, use IntersectionObserver for the scroll-triggered reveal.
813
function useReveal(threshold = 0.15) {
914
const ref = useRef(null);
1015
const [visible, setVisible] = useState(false);
16+
const [skipAnimation, setSkipAnimation] = useState(false);
1117

1218
useEffect(() => {
1319
const el = ref.current;
1420
if (!el) return;
15-
const observer = new IntersectionObserver(
16-
([entry]) => {
17-
if (entry.isIntersecting) {
18-
setVisible(true);
19-
observer.disconnect();
20-
}
21-
},
22-
{ threshold }
23-
);
24-
observer.observe(el);
25-
return () => observer.disconnect();
21+
22+
// Wait one rAF so the browser has restored scroll position after refresh.
23+
const raf = requestAnimationFrame(() => {
24+
const rect = el.getBoundingClientRect();
25+
// Element is above the viewport (scrolled past) or currently visible
26+
if (rect.bottom <= 0 || rect.top < window.innerHeight) {
27+
setSkipAnimation(true);
28+
setVisible(true);
29+
return;
30+
}
31+
32+
// Element is below the viewport — observe for scroll-triggered reveal
33+
const observer = new IntersectionObserver(
34+
([entry]) => {
35+
if (entry.isIntersecting) {
36+
setVisible(true);
37+
observer.disconnect();
38+
}
39+
},
40+
{ threshold }
41+
);
42+
observer.observe(el);
43+
// Store for cleanup
44+
el._revealObserver = observer;
45+
});
46+
47+
return () => {
48+
cancelAnimationFrame(raf);
49+
if (el._revealObserver) {
50+
el._revealObserver.disconnect();
51+
}
52+
};
2653
}, [threshold]);
2754

28-
return [ref, visible];
55+
return [ref, visible, skipAnimation];
2956
}
3057

3158
// --- COUNT UP HOOK ---
@@ -34,6 +61,10 @@ function useCountUp(target, duration, start) {
3461

3562
useEffect(() => {
3663
if (!start) return;
64+
if (duration <= 0) {
65+
setValue(target);
66+
return;
67+
}
3768
let startTime = null;
3869
let raf;
3970

@@ -160,18 +191,22 @@ export default function Homepage() {
160191
--header 'Gotenberg-Webhook-Events-Url: https://my-api.com/events'
161192
`;
162193

163-
const [sponsorRowRef, sponsorRowVisible] = useReveal(0.1);
164-
const [poweredByRowRef, poweredByRowVisible] = useReveal(0.1);
165-
const [sponsorBtnRef, sponsorBtnVisible] = useReveal(0.1);
166-
const [sectionHeaderRef, sectionHeaderVisible] = useReveal(0.2);
167-
const [feature1Ref, feature1Visible] = useReveal(0.15);
168-
const [feature2Ref, feature2Visible] = useReveal(0.15);
169-
const [feature3Ref, feature3Visible] = useReveal(0.15);
170-
const [feature4Ref, feature4Visible] = useReveal(0.15);
171-
const [ctaRef, ctaVisible] = useReveal(0.2);
194+
const [sectionHeaderRef, sectionHeaderVisible, sectionHeaderSkip] =
195+
useReveal(0.2);
196+
const [feature1Ref, feature1Visible, feature1Skip] = useReveal(0.15);
197+
const [feature2Ref, feature2Visible, feature2Skip] = useReveal(0.15);
198+
const [feature3Ref, feature3Visible, feature3Skip] = useReveal(0.15);
199+
const [feature4Ref, feature4Visible, feature4Skip] = useReveal(0.15);
200+
const [ctaRef, ctaVisible, ctaSkip] = useReveal(0.2);
201+
202+
// Helper: returns the right CSS classes for reveal animations
203+
const revealClasses = (visible, skip) =>
204+
skip
205+
? styles.revealInstant
206+
: clsx(styles.reveal, visible && styles.revealVisible);
172207

173-
const dockerCount = useCountUp(55, 1800, ctaVisible);
174-
const starsCount = useCountUp(11, 1800, ctaVisible);
208+
const dockerCount = useCountUp(55, ctaSkip ? 0 : 1800, ctaVisible);
209+
const starsCount = useCountUp(11, ctaSkip ? 0 : 1800, ctaVisible);
175210

176211
return (
177212
<main className={styles.mainContainer}>
@@ -280,15 +315,7 @@ export default function Homepage() {
280315
<section className={styles.communitySection}>
281316
<div className="container">
282317
<div className={styles.sponsorsStrip}>
283-
<div
284-
ref={sponsorRowRef}
285-
className={clsx(
286-
styles.sponsorRow,
287-
styles.reveal,
288-
sponsorRowVisible && styles.revealVisible
289-
)}
290-
style={{ animationDelay: "0.4s" }}
291-
>
318+
<div className={styles.sponsorRow}>
292319
<span className={styles.sponsorStripLabel}>Sponsors</span>
293320
<div className={styles.sponsorLogos}>
294321
<a href="https://thecodingmachine.com" target="_blank">
@@ -315,15 +342,7 @@ export default function Homepage() {
315342
</div>
316343
</div>
317344

318-
<div
319-
ref={poweredByRowRef}
320-
className={clsx(
321-
styles.sponsorRow,
322-
styles.reveal,
323-
poweredByRowVisible && styles.revealVisible
324-
)}
325-
style={{ animationDelay: "0.7s" }}
326-
>
345+
<div className={styles.sponsorRow}>
327346
<span className={styles.sponsorStripLabel}>Powered By</span>
328347
<div className={styles.sponsorLogos}>
329348
<a
@@ -346,14 +365,7 @@ export default function Homepage() {
346365
</div>
347366
</div>
348367

349-
<div
350-
ref={sponsorBtnRef}
351-
className={clsx(
352-
styles.reveal,
353-
sponsorBtnVisible && styles.revealVisible
354-
)}
355-
style={{ animationDelay: "1s" }}
356-
>
368+
<div>
357369
<Link
358370
to="https://github.com/sponsors/gulien"
359371
className={styles.heartBtnStrip}
@@ -381,8 +393,7 @@ export default function Homepage() {
381393
ref={sectionHeaderRef}
382394
className={clsx(
383395
styles.sectionHeader,
384-
styles.reveal,
385-
sectionHeaderVisible && styles.revealVisible
396+
revealClasses(sectionHeaderVisible, sectionHeaderSkip)
386397
)}
387398
>
388399
<h2>
@@ -397,8 +408,7 @@ export default function Homepage() {
397408
ref={feature1Ref}
398409
className={clsx(
399410
styles.splitRow,
400-
styles.reveal,
401-
feature1Visible && styles.revealVisible
411+
revealClasses(feature1Visible, feature1Skip)
402412
)}
403413
>
404414
<div className={styles.splitContent}>
@@ -439,8 +449,7 @@ export default function Homepage() {
439449
className={clsx(
440450
styles.splitRow,
441451
styles.splitRowReverse,
442-
styles.reveal,
443-
feature2Visible && styles.revealVisible
452+
revealClasses(feature2Visible, feature2Skip)
444453
)}
445454
>
446455
<div className={styles.splitContent}>
@@ -478,8 +487,7 @@ export default function Homepage() {
478487
ref={feature3Ref}
479488
className={clsx(
480489
styles.splitRow,
481-
styles.reveal,
482-
feature3Visible && styles.revealVisible
490+
revealClasses(feature3Visible, feature3Skip)
483491
)}
484492
>
485493
<div className={styles.splitContent}>
@@ -519,8 +527,7 @@ export default function Homepage() {
519527
className={clsx(
520528
styles.splitRow,
521529
styles.splitRowReverse,
522-
styles.reveal,
523-
feature4Visible && styles.revealVisible
530+
revealClasses(feature4Visible, feature4Skip)
524531
)}
525532
>
526533
<div className={styles.splitContent}>
@@ -586,11 +593,7 @@ export default function Homepage() {
586593
<div className={styles.ctaGlow} />
587594
<div
588595
ref={ctaRef}
589-
className={clsx(
590-
"container",
591-
styles.reveal,
592-
ctaVisible && styles.revealVisible
593-
)}
596+
className={clsx("container", revealClasses(ctaVisible, ctaSkip))}
594597
>
595598
<h2>
596599
Ready to transform your{" "}

src/components/Homepage.module.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
animation: revealIn 1.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
1111
}
1212

13+
.revealInstant {
14+
opacity: 1;
15+
filter: none;
16+
transform: none;
17+
animation: none;
18+
}
19+
1320
@keyframes revealIn {
1421
to {
1522
opacity: 1;

0 commit comments

Comments
 (0)