Skip to content

Commit 0576ba4

Browse files
committed
Fix intersection observer on Firefox
1 parent 47829d0 commit 0576ba4

File tree

2 files changed

+94
-71
lines changed

2 files changed

+94
-71
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"build": "yarn wsrun -m --stages -c script build",
1616
"watch": "yarn wsrun -m -c script watch",
1717
"version": "yarn wsrun -m --serial -c script version",
18-
"watch-package": "yarn wsrun -p '@code-hike/mini-editor' -r script watch ",
18+
"watch-package": "yarn wsrun -p '@code-hike/scroller' -r script watch ",
1919
"publish-canary": "yarn wsrun -p '@code-hike/*' --exclude '@code-hike/build' --serial -c publish-canary",
2020
"build-site": "yarn build && yarn workspace site export",
2121
"storybook-watch": "yarn watch & yarn storybook start"

packages/scroller/src/index.tsx

Lines changed: 93 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,133 @@
1-
import React from "react";
1+
import React from "react"
22

3-
export { Scroller, Step };
3+
export { Scroller, Step }
44

5-
const ObserverContext = React.createContext<IntersectionObserver | undefined>(
6-
undefined
7-
);
5+
const ObserverContext = React.createContext<
6+
IntersectionObserver | undefined
7+
>(undefined)
8+
9+
const useLayoutEffect =
10+
typeof window !== "undefined"
11+
? React.useLayoutEffect
12+
: React.useEffect
813

914
type ScrollerProps = {
10-
onStepChange: (stepIndex: number) => void;
11-
children: React.ReactNode;
12-
getRootMargin?: (vh: number) => string;
13-
};
15+
onStepChange: (stepIndex: number) => void
16+
children: React.ReactNode
17+
getRootMargin?: (vh: number) => string
18+
}
1419

1520
type StepElement = {
16-
stepIndex: any;
17-
};
21+
stepIndex: any
22+
}
1823

1924
function defaultRootMargin(vh: number) {
20-
return `-${vh / 2 - 2}px 0px`;
25+
return `-${vh / 2 - 2}px 0px`
2126
}
2227

2328
function Scroller({
2429
onStepChange,
2530
children,
2631
getRootMargin = defaultRootMargin,
2732
}: ScrollerProps) {
28-
const [observer, setObserver] = React.useState<IntersectionObserver>();
29-
const vh = useWindowHeight();
33+
const [observer, setObserver] = React.useState<
34+
IntersectionObserver
35+
>()
36+
const vh = useWindowHeight()
3037

31-
React.useLayoutEffect(() => {
32-
const windowHeight = vh || 0;
33-
const handleIntersect: IntersectionObserverCallback = (entries) => {
34-
entries.forEach((entry) => {
38+
useLayoutEffect(() => {
39+
const windowHeight = vh || 0
40+
const handleIntersect: IntersectionObserverCallback = entries => {
41+
entries.forEach(entry => {
3542
if (entry.intersectionRatio > 0) {
36-
// ref.current = entry.target.stepInfo
37-
const stepElement = (entry.target as unknown) as StepElement;
38-
onStepChange(+stepElement.stepIndex);
43+
const stepElement = (entry.target as unknown) as StepElement
44+
onStepChange(+stepElement.stepIndex)
3945
}
40-
});
41-
// console.log(entries[0].rootBounds, entries);
42-
// const rrect = document.getElementById("root-rect")!;
43-
// const bounds = entries[0].rootBounds;
44-
// rrect.style.top = bounds?.top + "px";
45-
// rrect.style.left = bounds?.left + "px";
46-
// rrect.style.height = bounds?.height + "px";
47-
// rrect.style.width = bounds?.width + "px";
48-
49-
// const irect = document.getElementById("entry-rect")!;
50-
// const ibounds = entries[0].intersectionRect;
51-
// irect.style.top = ibounds?.top + "px";
52-
// irect.style.left = ibounds?.left + "px";
53-
// irect.style.height = ibounds?.height + "px";
54-
// irect.style.width = ibounds?.width + "px";
55-
};
56-
const observer = new IntersectionObserver(handleIntersect, {
57-
rootMargin: getRootMargin(windowHeight),
58-
threshold: 0.000001,
59-
root: document as any,
60-
});
61-
setObserver(observer);
46+
})
47+
}
48+
const observer = newIntersectionObserver(
49+
handleIntersect,
50+
getRootMargin(windowHeight)
51+
)
52+
setObserver(observer)
6253

63-
return () => observer.disconnect();
64-
}, [vh]);
54+
return () => observer.disconnect()
55+
}, [vh])
6556

6657
return (
6758
<ObserverContext.Provider value={observer}>
6859
{children}
6960
</ObserverContext.Provider>
70-
);
61+
)
62+
}
63+
64+
function newIntersectionObserver(
65+
handleIntersect: IntersectionObserverCallback,
66+
rootMargin: string
67+
) {
68+
try {
69+
return new IntersectionObserver(handleIntersect, {
70+
rootMargin,
71+
threshold: 0.000001,
72+
root: document as any,
73+
})
74+
} catch {
75+
// firefox doesn't like passing `document` as the root
76+
// it's a shame because it break the scroller inside iframes
77+
return new IntersectionObserver(handleIntersect, {
78+
rootMargin,
79+
threshold: 0.000001,
80+
})
81+
}
7182
}
7283

73-
function Step({ as = "section", index, ...props }: { as: any; index: number }) {
74-
const ref = React.useRef<HTMLElement>(null!);
75-
const observer = React.useContext(ObserverContext);
84+
function Step({
85+
as = "section",
86+
index,
87+
...props
88+
}: {
89+
as: any
90+
index: number
91+
}) {
92+
const ref = React.useRef<HTMLElement>(null!)
93+
const observer = React.useContext(ObserverContext)
7694

77-
React.useLayoutEffect(() => {
95+
useLayoutEffect(() => {
7896
if (observer) {
79-
observer.observe(ref.current);
97+
observer.observe(ref.current)
8098
}
81-
return () => observer && observer.unobserve(ref.current);
82-
}, [observer]);
99+
return () => observer && observer.unobserve(ref.current)
100+
}, [observer])
83101

84-
React.useLayoutEffect(() => {
85-
const stepElement = (ref.current as unknown) as StepElement;
86-
stepElement.stepIndex = index;
87-
}, [index]);
102+
useLayoutEffect(() => {
103+
const stepElement = (ref.current as unknown) as StepElement
104+
stepElement.stepIndex = index
105+
}, [index])
88106

89-
return React.createElement(as, { ...props, ref });
107+
return React.createElement(as, { ...props, ref })
90108
}
91109

92110
function useWindowHeight() {
93-
const isClient = typeof window === "object";
111+
const isClient = typeof window === "object"
94112
function getHeight() {
95-
return isClient ? document.documentElement.clientHeight : undefined;
113+
return isClient
114+
? document.documentElement.clientHeight
115+
: undefined
96116
}
97-
const [windowHeight, setWindowHeight] = React.useState(getHeight);
117+
const [windowHeight, setWindowHeight] = React.useState(
118+
getHeight
119+
)
98120
React.useEffect(() => {
99121
function handleResize() {
100-
setWindowHeight(getHeight());
122+
setWindowHeight(getHeight())
101123
}
102-
window.addEventListener("resize", handleResize);
103-
return () => window.removeEventListener("resize", handleResize);
104-
}, []);
105-
React.useLayoutEffect(() => {
106-
// FIX when an horizontal scrollbar is added after the first layout
107-
setWindowHeight(getHeight());
108-
}, []);
109-
return windowHeight;
124+
window.addEventListener("resize", handleResize)
125+
return () =>
126+
window.removeEventListener("resize", handleResize)
127+
}, [])
128+
useLayoutEffect(() => {
129+
// FIX when a horizontal scrollbar is added after the first layout
130+
setWindowHeight(getHeight())
131+
}, [])
132+
return windowHeight
110133
}

0 commit comments

Comments
 (0)