Last active
July 25, 2024 06:39
-
-
Save 1216892614/2fcf1df21866ce5870503672828eb78c to your computer and use it in GitHub Desktop.
carousel.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useSpringValue, animated } from "@react-spring/web"; | |
import React, { useCallback, useReducer, useRef, useEffect } from "react"; | |
import _ from "lodash/fp"; | |
import useWaitValue from "./useWaitValue"; | |
const DATA_LIST = _.range(0, 25).map((i) => ({ | |
title: `asdf${i}`, | |
})); | |
const useWaitValue = <T>(v: T | undefined | null, countMax: number = 5) => { | |
const [count, update] = useReducer((cur: number) => cur + 1, 0); | |
useEffect(() => { | |
if ((v !== undefined && v !== null) || count >= countMax) return; | |
setTimeout(update, 500); | |
}); | |
}; | |
const App: React.FC = () => { | |
const indexSpringed = useSpringValue(0); | |
const [index, setIndex] = useReducer((_cur: number, input: number) => { | |
if (input >= DATA_LIST.length) { | |
indexSpringed.set(-1); | |
indexSpringed.start(0); | |
return 0; | |
} | |
if (input < 0) { | |
indexSpringed.set(DATA_LIST.length); | |
indexSpringed.start(DATA_LIST.length - 1); | |
return DATA_LIST.length - 1; | |
} | |
indexSpringed.start(input); | |
return input; | |
}, 0); | |
const containerRef = useRef<HTMLDivElement>(null); | |
const splitOrdAndProportions = useCallback((i: number, v: number) => { | |
const round = Math.floor(v); | |
const prop = Math.abs(v - round) / 2; | |
if (round + 4 === i) return prop; | |
if (round + 3 === i) return prop + 0.5; | |
if (round + 2 === i) return 1 - prop; | |
if (round + 1 === i) return 0.5 - prop; | |
return 0; | |
}, []); | |
useWaitValue(containerRef.current); | |
const getTrans = useCallback( | |
(i: number) => | |
indexSpringed.to((v) => { | |
const delta = splitOrdAndProportions(i, v) ?? 0; | |
const divide = i - v <= 2 ? 1 : -1; | |
return `translateX(${(i - v - 1) * 50}%) rotate3d(0,1,0,${ | |
(1 - (delta ?? 0)) * divide * 100 | |
}deg) scale(${delta ?? 0})`; | |
}), | |
[indexSpringed, splitOrdAndProportions] | |
); | |
return ( | |
<div | |
className="h-1/2 w-1/2 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex justify-center items-center bg-slate-300" | |
style={{ perspective: "800px" }} | |
onWheel={(evt) => setIndex(evt.deltaY > 0 ? index + 1 : index - 1)} | |
ref={containerRef} | |
> | |
{[ | |
DATA_LIST.at(-2)!, | |
DATA_LIST.at(-1)!, | |
...DATA_LIST, | |
DATA_LIST.at(0)!, | |
DATA_LIST.at(1)!, | |
] | |
.map(({ title }, i) => | |
Math.abs(index - i + 2) <= 4 ? ( | |
<animated.div | |
key={i} | |
className="absolute right-1/2 bg-gray-500 w-1/2 h-1/2 shrink-0 overflow-hidden shadow-lg" | |
style={{ | |
display: indexSpringed.to((v) => | |
i - v <= 0 || i - v > 4 ? "none" : "unset" | |
), | |
zIndex: indexSpringed.to((v) => | |
i - v <= 2 ? 1 : 0 | |
), | |
opacity: indexSpringed.to( | |
(v) => | |
(splitOrdAndProportions(i, v) ?? 0) * 1 | |
), | |
transform: getTrans(i), | |
}} | |
> | |
{i}-{title} | |
</animated.div> | |
) : null | |
) | |
.filter(Boolean)} | |
</div> | |
); | |
}; | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Deps