Skip to content

Commit 5b79465

Browse files
committed
ScrollToTop works, observe each element independently at same time
1 parent 29fe94e commit 5b79465

File tree

1 file changed

+27
-5
lines changed

1 file changed

+27
-5
lines changed

src/components/react/ScrollToTop.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import React, { useEffect, useRef, useState } from 'react';
22

3+
import { cn } from '@/utils/styles';
4+
35
import type { ReactNode } from 'react';
46

57
interface Props {
68
children: ReactNode;
79
}
810

911
const fixedClasses = ['opacity-1', 'translate-y-0'];
10-
const hiddenClasses = ['opacity-0', 'translate-y-full'];
12+
const hiddenClasses = ['opacity-0', 'translate-y-20'];
1113

1214
const showLink = (linkRef: React.RefObject<HTMLAnchorElement>): void => {
1315
linkRef.current?.classList.add(...fixedClasses);
@@ -29,13 +31,25 @@ const ScrollToTop: React.FC<Props> = ({ children }) => {
2931
const [height, setHeight] = useState(getHalfViewportHeight(window));
3032

3133
useEffect(() => {
34+
// ! track them both independently at same time
35+
// ! important: must be in this scope, outside of callback()
36+
let isAtTop = false;
37+
let isAtBottom = false;
38+
3239
const callback: IntersectionObserverCallback = (entries) => {
33-
const isAtTopOrBottom = entries.some((entry) => entry.isIntersecting);
40+
// entries.length === 1 || 2, count changes when exits viewport
41+
entries.forEach((entry) => {
42+
if (entry.target === topRef.current) {
43+
isAtTop = entry.isIntersecting;
44+
}
3445

35-
console.log('isAtTopOrBottom', isAtTopOrBottom);
46+
if (entry.target === bottomRef.current) {
47+
isAtBottom = entry.isIntersecting;
48+
}
49+
});
3650

3751
if (linkRef.current) {
38-
isAtTopOrBottom ? hideLink(linkRef) : showLink(linkRef);
52+
isAtTop || isAtBottom ? hideLink(linkRef) : showLink(linkRef);
3953
}
4054
};
4155

@@ -69,6 +83,7 @@ const ScrollToTop: React.FC<Props> = ({ children }) => {
6983
className="pointer-events-none absolute top-0 w-10 bg-red-500"
7084
style={{ height: `${height}px` }}
7185
/>
86+
{/* mounted in <body /> in Base layout */}
7287
<div
7388
ref={bottomRef}
7489
className="pointer-events-none absolute bottom-0 w-10 bg-blue-500"
@@ -78,7 +93,14 @@ const ScrollToTop: React.FC<Props> = ({ children }) => {
7893
ref={linkRef}
7994
id="to-top"
8095
href="#top"
81-
className="z-10 fixed bottom-6 right-6 rounded bg-base-200 border border-base-300"
96+
className={cn(
97+
// default styles
98+
'z-10 fixed bottom-6 right-6 rounded bg-base-200 border border-base-300',
99+
// initial state
100+
hiddenClasses,
101+
// transition classes
102+
'transition-all duration-300'
103+
)}
82104
aria-label="Scroll to top"
83105
>
84106
{/* astro-icon must be passed as slot */}

0 commit comments

Comments
 (0)