endpild-laman/components/body/left-panel.tsx

276 lines
11 KiB
TypeScript
Raw Normal View History

2024-01-22 20:52:38 +09:00
import { FC, useEffect, useState } from "react";
import { AnimatePresence, motion, useAnimation, usePresence } from "framer-motion";
import { emptyFunc, joinClasses, waitAsync } from "@utils/common";
import { Forceful, SlowDown } from "@utils/anims";
import bodyStyles from "@components/body/Body.module.scss";
interface IIndex {
initIndex?: number;
onIndexAnimStart?: (from: number, to: number) => void;
onIndexAnimEnd?: (from: number, to: number) => void;
onIndexChange?: (index: number) => void;
}
export const LeftPanel: FC<IIndex> = ({
onIndexAnimStart= emptyFunc,
onIndexAnimEnd= emptyFunc,
onIndexChange= emptyFunc,
initIndex = 1
}) => {
const [isPresent, safeToRemove] = usePresence();
const [init, setInit] = useState(true);
const [indexSubU, setIndexSubU] = useState(initIndex - 1);
const [indexMain, setIndexMain] = useState(initIndex);
const [indexSubL, setIndexSubL] = useState(initIndex + 1);
const [indexPolygon, setIndexPolygon] = useState(0);
const [isAnimating, setIsAnimating] = useState(false);
const indexMainController = useAnimation();
const indexSubLController = useAnimation();
const indexSubUController = useAnimation();
const InnerPolygonVariants = {
0: { d: "M0,0V928H0L0,0Z" },
1: { d: "M0,28V928H175L425,0Z" },
2: { d: "M0,28V928H425L175,0Z" },
3: { d: "M0,28V928H280L280,0Z" }
};
const OuterPolygonVariants = {
0: { d: "M0,0V928H0L0,0Z" },
1: { d: "M0,28V928H240L490,0" },
2: { d: "M0,28V928H490L240,0" },
3: { d: "M0,28V928H310L310,0Z" }
};
const IndexTextVariants = {
0: { transform: "translate(220px, 600px) rotate(-74deg)" },
1: { transform: "translate(220px, 600px) rotate(-74deg)" },
2: { transform: "translate(250px, 600px) rotate(-105deg)" },
3: { transform: "translate(220px, 600px) rotate(-90deg)" },
};
const transition = {
duration: init ? 1 : 2,
ease: Forceful,
};
useEffect(() => {
setIndexPolygon(indexMain);
onIndexChange(indexMain);
indexMainController.set({ y: 830, x: 200 , transition: { duration: 1.2, ease: SlowDown }});
void indexMainController.start({ x: 0 });
waitAsync(1000).then(
() => {
indexMainController.set({ transition });
setInit(false);
}
);
indexSubLController.set({ y: 1670 });
indexSubUController.set({ y: -10 });
return () => {
indexMainController.stop();
indexSubLController.stop();
indexSubUController.stop();
};
}, [isPresent]);
const indexTextConfig = {
fontFamily: "Jetbrains Mono",
fontStyle: "bold",
fontSize: 62,
fill: "#fff",
transition,
};
const indexNumConfig = {
className: "no-pointer",
stroke: "#1D1D1D",
fill: "#1D1D1D",
fontFamily: "Jetbrains Mono",
fontStyle: "italic",
fontSize: 1000,
x: -180,
};
const HandleScroll = async (event: WheelEvent) => {
if (isAnimating) return;
const down = event.deltaY > 0;
// LOWER_INDEX_BOUND UPPER_INDEX_BOUND
await shiftNumberSequence(down, (indexMain < 2 && !down) || (indexMain > 2 && down));
};
const shiftNumberSequence = async (increment: boolean, wrap = false) => {
setIsAnimating(true);
const oldValue = indexMain;
const newValue = wrap ? (increment ? 1 : 3) : (oldValue + (increment ? 1 : -1));
onIndexAnimStart(oldValue, newValue);
if (wrap) {
increment ? setIndexSubL(newValue) : setIndexSubU(newValue);
}
setIndexPolygon(newValue);
await Promise.all(
increment ?
[
indexSubLController.start({ y: 830 }),
indexMainController.start({ y: -10 }),
] :
[
indexSubUController.start({ y: 830 }),
indexMainController.start({ y: 1670 })
]
);
await waitAsync(20);
setIndexMain(newValue);
onIndexChange(newValue);
indexMainController.set({ y: 830 });
increment ? indexSubLController.set({ y: 1670 }) : indexSubUController.set({ y: -10 });
setIndexSubL(newValue + 1);
setIndexSubU(newValue - 1);
setIsAnimating(false);
onIndexAnimEnd(oldValue, newValue);
return Promise.resolve();
};
const commonPolygonProps = {
animate: indexPolygon.toString(),
transition,
initial: "0",
};
return (
<motion.svg
className={joinClasses("fh z2", bodyStyles["index-panel"])}
layout
viewBox="0 29 520 880"
fill="none"
xmlns="http://www.w3.org/2000/svg"
pointerEvents={"none"}
>
<motion.path
filter="url(#filter0_bd_140_3)"
fill="white"
fillOpacity="0.47"
variants={OuterPolygonVariants}
{...commonPolygonProps}
/>
<motion.path
filter="url(#filter0_d_116_13)"
onWheel={(e) => HandleScroll(e as unknown as WheelEvent)}
pointerEvents={"all"}
fill="black"
variants={InnerPolygonVariants}
{...commonPolygonProps}
/>
<g clipPath={"url(#index__inner-poly)"}>
<motion.text
{...indexNumConfig}
initial={{ y: -10 }}
animate={indexSubUController}
transition={transition}
>
{indexSubU}
</motion.text>
<motion.text
{...indexNumConfig}
animate={indexMainController}
transition={transition}
>
{indexMain}
</motion.text>
<motion.text
{...indexNumConfig}
animate={indexSubLController}
transition={transition}
>
{indexSubL}
</motion.text>
<motion.g
id={"label"}
variants={IndexTextVariants}
initial={"0"}
animate={indexPolygon.toString()}
transition={transition}
>
<AnimatePresence>
{
indexPolygon === 1 &&
<motion.text
initial={{ x: -700 }}
animate={{ x: 0 }}
exit={{ x: 1300 }}
{...indexTextConfig}
>
OVERVIEW
</motion.text>
}
</AnimatePresence>
<AnimatePresence>
{
indexPolygon === 2 &&
<g transform={"rotate(180)"}>
<motion.text
initial={{ x: 400 }}
animate={{ x: -500 }}
exit={{ x: -1800 }}
{...indexTextConfig}
>
<tspan x={188}>PROTOCOL FIELD</tspan>
<tspan x={0} dy={60}>RECOVERY DEPARTMENT</tspan>
</motion.text>
</g>
}
</AnimatePresence>
<AnimatePresence>
{
indexPolygon === 3 &&
<motion.text
initial={{ x: -1050 }}
animate={{ x: -150 }}
exit={{ x: 900 }}
{...indexTextConfig}
>
COORDINATE RECORDS
</motion.text>
}
</AnimatePresence>
</motion.g>
</g>
<text
transform="rotate(90) translate(370 0)"
{...Object.assign(indexTextConfig, { fontSize: 20 })}
>
{"<-SCROLL->"}
</text>
<defs>
<clipPath id={"index__inner-poly"}>
<motion.path
variants={InnerPolygonVariants}
{...commonPolygonProps}
/>
</clipPath>
<filter id="filter0_bd_140_3" x="-30" y="0" width="554" height="964.347" filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB">
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImage" stdDeviation="2" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_140_3" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha" />
<feOffset />
<feGaussianBlur stdDeviation="14.5" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.48 0" />
<feBlend mode="normal" in2="effect1_backgroundBlur_140_3" result="effect2_dropShadow_140_3" />
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_140_3" result="shape" />
</filter>
<filter id="filter0_d_116_13" x="-1" y="0" width="427.424" height="914.694" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
<feFlood floodOpacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dx="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_116_13"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_116_13" result="shape"/>
</filter>
</defs>
</motion.svg>
);
};