Skip to content

Commit bdb7d3a

Browse files
committed
changes
1 parent 341606a commit bdb7d3a

6 files changed

+105
-57
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ yarn-debug.log*
2020
yarn-error.log*
2121

2222
_crea
23+
tsconfig.tsbuildinfo

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"build": "rm -rf ./dist/* && bun build src/index.ts --outdir ./dist",
1212
"lint": "bunx eslint src -- --fix",
1313
"pretty": "bunx prettier --write 'src/**/*.(ts|tsx)'",
14+
"test:types": "bunx tsc",
1415
"upgrade:deps": "bunx ncu -u && bun install"
1516
},
1617
"peerDependencies": {

src/FPContainer.tsx

+63-39
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import {
2-
useState,
3-
useEffect,
4-
useRef,
5-
useContext,
6-
useMemo,
72
type CSSProperties,
3+
type FC,
84
type KeyboardEvent,
95
type ReactNode,
10-
type FC,
6+
useContext,
7+
useEffect,
8+
useLayoutEffect,
9+
useMemo,
10+
useRef,
11+
useState,
12+
useTransition,
1113
} from "react";
1214
import { motion, type MotionProps } from "framer-motion";
1315

@@ -25,6 +27,7 @@ export interface FPContainerInterface {
2527
onHide?: Function;
2628
onShow?: Function;
2729
outerStyle?: CSSProperties;
30+
scrollDebounceMs?: number;
2831
style?: CSSProperties;
2932
transitionTiming?: number;
3033
}
@@ -38,12 +41,17 @@ export const FPContainer: FC<FPContainerInterface> = ({
3841
onHide,
3942
onShow,
4043
outerStyle = {},
44+
scrollDebounceMs = 125,
4145
style = {},
4246
transitionTiming = 700,
4347
}) => {
44-
const throttled = useRef(false);
45-
const ticking = useRef(false);
46-
const scrollY = useRef(isSsr ? 0 : window.scrollY);
48+
const FPContainerInnerRef = useRef<HTMLDivElement>(null);
49+
const scrollTimer = useRef<Timer>(null);
50+
// @see https://developer.mozilla.org/en-US/docs/Web/API/Window/pageYOffset
51+
// ^ IE users dont deserve our support
52+
const scrollY = useRef<number>(isSsr ? 0 : window.scrollY);
53+
const throttled = useRef<boolean>(false);
54+
const ticking = useRef<boolean>(false);
4755

4856
const useOuterStyle = useMemo(
4957
() => ({
@@ -66,9 +74,9 @@ export const FPContainer: FC<FPContainerInterface> = ({
6674
[style]
6775
);
6876

69-
const FPContainerInnerRef = useRef<HTMLDivElement>(null);
77+
const { ReactFPRef, slides, isFullscreen } = useContext(FPContext);
7078

71-
const { ReactFPRef, slides } = useContext(FPContext);
79+
const [, startTransition] = useTransition();
7280

7381
const [pageState, setPageState] = useState({
7482
fullpageHeight: 0,
@@ -81,39 +89,49 @@ export const FPContainer: FC<FPContainerInterface> = ({
8189
});
8290

8391
// @see https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
84-
const handleScroll = (e: UIEvent) => {
92+
const handleScroll = () => {
8593
if (throttled.current || isSsr) return;
8694
throttled.current = true;
8795

88-
e.stopPropagation();
89-
90-
// scroll first, then enable throttling
91-
setTimeout(() => {
92-
throttled.current = false;
93-
}, transitionTiming);
94-
9596
if (!ticking.current) {
9697
window.requestAnimationFrame(() => {
9798
const newScrollY = window.scrollY;
9899
const prevScrollY = scrollY.current;
99100

100-
if (prevScrollY < newScrollY) forward();
101+
if (newScrollY === 0) first();
102+
else if (
103+
window.innerHeight + Math.round(newScrollY) >=
104+
document.body.offsetHeight
105+
)
106+
last();
107+
else if (prevScrollY < newScrollY) forward();
101108
else if (prevScrollY > newScrollY) back();
102109

103110
if (
104111
pageState.resetScroll ||
105112
transitionTiming !== pageState.transitionTiming
106113
)
107-
setPageState((prevState) => ({
108-
...prevState,
109-
resetScroll: false,
110-
transitionTiming,
111-
}));
114+
startTransition(() => {
115+
setPageState((prevState) => ({
116+
...prevState,
117+
resetScroll: false,
118+
transitionTiming,
119+
}));
120+
});
112121

113122
ticking.current = false;
114123
});
115124
ticking.current = true;
116125
}
126+
127+
setTimeout(() => {
128+
throttled.current = false;
129+
}, transitionTiming);
130+
};
131+
132+
const bouncedHandleScroll = () => {
133+
clearTimeout(scrollTimer.current);
134+
scrollTimer.current = setTimeout(() => handleScroll(), scrollDebounceMs);
117135
};
118136

119137
const handleResize = () => {
@@ -134,15 +152,17 @@ export const FPContainer: FC<FPContainerInterface> = ({
134152
if (!ticking.current) {
135153
requestAnimationFrame(() => {
136154
const fullpageHeight = FPContainerInnerRef.current!.clientHeight;
137-
// update count
138-
setPageState((prevState) => ({
139-
...prevState,
140-
fullpageHeight,
141-
viewportHeight: Math.max(
142-
document.documentElement.clientHeight,
143-
window.innerHeight
144-
),
145-
}));
155+
156+
startTransition(() => {
157+
setPageState((prevState) => ({
158+
...prevState,
159+
fullpageHeight,
160+
viewportHeight: Math.max(
161+
document.documentElement.clientHeight,
162+
window.innerHeight
163+
),
164+
}));
165+
});
146166
ReactFPRef.current!.style.height = `${fullpageHeight}px`;
147167
ticking.current = false;
148168
});
@@ -203,7 +223,10 @@ export const FPContainer: FC<FPContainerInterface> = ({
203223
slideIndex,
204224
translateY,
205225
};
206-
setPageState((prevState) => ({ ...prevState, ...newPageState }));
226+
227+
startTransition(() => {
228+
setPageState((prevState) => ({ ...prevState, ...newPageState }));
229+
});
207230

208231
setTimeout(() => {
209232
throttled.current = false;
@@ -256,20 +279,21 @@ export const FPContainer: FC<FPContainerInterface> = ({
256279
useEffect(() => {
257280
if (isSsr) return;
258281

259-
window.addEventListener("scroll", handleScroll, { passive: true });
282+
window.addEventListener("scroll", bouncedHandleScroll, { passive: true });
260283
window.addEventListener("resize", handleResize, { passive: true });
261284
document.addEventListener("keydown", handleKeys, { passive: true });
262285

263286
return () => {
264-
window.removeEventListener("scroll", handleScroll);
287+
window.removeEventListener("scroll", bouncedHandleScroll);
265288
window.removeEventListener("resize", handleResize);
266289
document.removeEventListener("keydown", handleKeys);
267290
};
268291
});
269292

270-
useEffect(() => {
293+
useLayoutEffect(() => {
271294
handleResize();
272-
});
295+
handleScroll();
296+
}, [isFullscreen]);
273297

274298
return (
275299
<div style={useOuterStyle}>

src/FPContext.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const observeFn = (el) => el;
55

66
export const FPContext = createContext({
77
getIndex: (el: any) => 0 as number,
8+
isFullscreen: false,
89
ReactFPRef: null as FPItemRef,
910
slides: [] as FPItemRef[],
1011
subscribe: observeFn,

src/FPItem.tsx

+16-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import {
2-
useContext,
3-
useRef,
4-
useEffect,
52
type CSSProperties,
6-
type ReactNode,
73
type FC,
4+
type ReactNode,
5+
useContext,
6+
useEffect,
7+
useRef,
8+
useMemo,
89
} from "react";
910
import { motion, type MotionProps } from "framer-motion";
1011

@@ -26,7 +27,15 @@ export const FPItem: FC<FPItemInterface> = ({
2627
style = {},
2728
motionProps = {},
2829
}) => {
29-
const { subscribe, unsubscribe, getIndex } = useContext(FPContext);
30+
const useStyle = useMemo(
31+
() => ({
32+
height,
33+
...style,
34+
}),
35+
[style]
36+
);
37+
38+
const { subscribe, unsubscribe } = useContext(FPContext);
3039
const FPItemRef: FPItemRef = useRef(null);
3140

3241
useEffect(() => {
@@ -36,13 +45,11 @@ export const FPItem: FC<FPItemInterface> = ({
3645
unsubscribe(FPItemRef);
3746
};
3847
}, []);
48+
3949
return (
4050
<motion.div
4151
className={className}
42-
style={{
43-
height,
44-
...style,
45-
}}
52+
style={useStyle}
4653
ref={FPItemRef}
4754
{...motionProps}
4855
>

src/ReactFP.tsx

+23-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {
22
useState,
33
useRef,
44
useMemo,
5+
useDeferredValue,
6+
Suspense,
57
type CSSProperties,
68
type ReactNode,
79
type ElementType,
@@ -12,12 +14,17 @@ import { motion, type MotionProps } from "framer-motion";
1214

1315
import { FPContext, FSButton, type FPItemRef } from ".";
1416

17+
const ImLoading = () => (
18+
<div style={{ backgroundColor: "black", height: "100vh" }}>Loading</div>
19+
);
20+
1521
export interface ReactFPInterface {
1622
children: ReactNode;
1723
//
1824
Button?: ElementType;
1925
buttonStyle?: CSSProperties;
2026
className?: string;
27+
Fallback?: ElementType;
2128
motionProps?: MotionProps;
2229
style?: CSSProperties;
2330
}
@@ -28,6 +35,7 @@ export const ReactFP: FC<ReactFPInterface> = ({
2835
Button = FSButton,
2936
buttonStyle = {},
3037
className = "",
38+
Fallback = ImLoading,
3139
motionProps = {},
3240
style = {},
3341
}) => {
@@ -38,8 +46,19 @@ export const ReactFP: FC<ReactFPInterface> = ({
3846
}),
3947
[style]
4048
);
49+
const useButtonStyle = useMemo(
50+
() => ({
51+
position: "fixed",
52+
left: 10,
53+
top: 10,
54+
zIndex: 9999,
55+
...buttonStyle,
56+
}),
57+
[buttonStyle]
58+
);
4159

4260
const [slides, setSlides] = useState<FPItemRef[]>([]);
61+
const deferredSlides = useDeferredValue(slides);
4362

4463
const fullscreen = useRef(false);
4564
const ReactFPRef = useRef<HTMLDivElement>(null);
@@ -72,19 +91,14 @@ export const ReactFP: FC<ReactFPInterface> = ({
7291
}
7392
return void (fullscreen.current = !fullscreen.current);
7493
}}
75-
style={{
76-
position: "fixed",
77-
left: 10,
78-
top: 10,
79-
zIndex: 9999,
80-
...buttonStyle,
81-
}}
94+
style={useButtonStyle}
8295
/>
8396
<FPContext.Provider
8497
value={{
8598
getIndex,
99+
isFullscreen: !!fullscreen.current,
86100
ReactFPRef,
87-
slides,
101+
slides: deferredSlides,
88102
subscribe,
89103
unsubscribe,
90104
}}
@@ -95,7 +109,7 @@ export const ReactFP: FC<ReactFPInterface> = ({
95109
className={className}
96110
{...motionProps}
97111
>
98-
{children}
112+
<Suspense fallback={<Fallback />}>{children}</Suspense>
99113
</motion.div>
100114
</FPContext.Provider>
101115
</FullScreen>

0 commit comments

Comments
 (0)