From c1ea57d3a18548b4a7db0c15fcb7a1f695d13ccf Mon Sep 17 00:00:00 2001 From: 071yoon Date: Mon, 29 Jan 2024 21:44:20 +0900 Subject: [PATCH] feat: add 3d motion page template --- src/app/3d/gsapOptions.ts | 72 +++++++++ src/app/3d/page.tsx | 171 ++++++++++++++++++++ src/components/main-page/ThreeDimension.tsx | 32 ++-- 3 files changed, 266 insertions(+), 9 deletions(-) create mode 100644 src/app/3d/gsapOptions.ts create mode 100644 src/app/3d/page.tsx diff --git a/src/app/3d/gsapOptions.ts b/src/app/3d/gsapOptions.ts new file mode 100644 index 0000000..859d8d7 --- /dev/null +++ b/src/app/3d/gsapOptions.ts @@ -0,0 +1,72 @@ +import { easingOptions, gsapOptionsTypeObj } from "../../types/gsapOption"; + +export const gsapOptions: gsapOptionsTypeObj[] = [ + { + type: "xFrom", + min: -100, + max: 100, + componentType: "slider", + step: 1, + default: -100, + }, + { + type: "xTo", + min: -100, + max: 100, + componentType: "none", + default: 0, + }, + { + type: "yFrom", + min: -100, + max: 100, + componentType: "slider", + step: 1, + default: 0, + }, + { + type: "yTo", + min: -100, + max: 100, + componentType: "none", + default: 0, + }, + { + type: "opacityFrom", + min: 0, + max: 1, + componentType: "none", + step: 0.1, + default: 0, + }, + { + type: "opacityTo", + min: 0, + max: 1, + componentType: "slider", + step: 0.1, + default: 1, + }, + { + type: "duration", + min: 0, + max: 2, + componentType: "slider", + step: 0.1, + default: 0.5, + }, + { + type: "ease", + componentType: "select", + options: easingOptions, + default: "power2", + }, + { + type: "stagger", + min: 0, + max: 1, + componentType: "slider", + step: 0.1, + default: 0.1, + }, +]; diff --git a/src/app/3d/page.tsx b/src/app/3d/page.tsx new file mode 100644 index 0000000..94c1cba --- /dev/null +++ b/src/app/3d/page.tsx @@ -0,0 +1,171 @@ +"use client"; +import styled from "@emotion/styled"; +import { gsap } from "gsap"; +import { Button, Divider } from "@mui/joy"; +import { useEffect, useRef, useState } from "react"; +import { useDebounce } from "@toss/react"; +import { gsapOptions } from "./gsapOptions"; +import { + GsapSlider, + GsapSelect, + PlaygroundContainer, + IPhoneX, + InsideIPhone, +} from "@/components/playground-page"; +import useTransitionToCode from "@hooks/useTransitionToCode"; +import { getGsapData } from "@utils/getGsapData"; + +export default function Block() { + const [counter, setCounter] = useState(0); + const [isCode, setIsCode] = useState(false); + const animationRef = useRef(null); + const iPhoneCodeRef = useRef(null); + const [gsapStates, setGsapStates] = useState( + gsapOptions.reduce((acc, cur) => { + acc[cur.type] = cur.default; + return acc; + //! FIXME + }, {} as any) + ); + + useTransitionToCode({ + isCode, + mainRef: animationRef, + codeRef: iPhoneCodeRef, + }); + + const onChangeSlider = + (type: string) => (_event: Event, newValue: number | number[]) => { + if (typeof newValue === "number") + setGsapStates({ ...gsapStates, [type]: newValue }); + }; + + const handleGsapAnimation = useDebounce((timeline) => { + timeline.play(); + }, 300); + + useEffect(() => { + const gsapData = getGsapData(gsapStates); + const animationChildren = animationRef.current?.children; + const ctx = gsap.context(() => { + if (animationChildren) { + const timeline = gsap.fromTo( + animationChildren, + { + ...gsapData.from, + }, + { + stagger: 0.1, + ...gsapData.to, + ...gsapData.rest, + } + ); + timeline.pause(); + handleGsapAnimation(timeline); + } + }); + return () => { + ctx.revert(); + }; + }, [counter, gsapStates, handleGsapAnimation]); + + return ( + + + + {gsapOptions.map((item) => { + if (item.componentType === "select") { + return ( + + ); + } + })} + + {gsapOptions.map((item) => { + if (item.componentType === "slider") + return ( + + ); + })} + + + +
+
+
+
+
+ + + + ); +} + +const OptionsContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 1rem; +`; diff --git a/src/components/main-page/ThreeDimension.tsx b/src/components/main-page/ThreeDimension.tsx index 7d19f8e..0b45875 100644 --- a/src/components/main-page/ThreeDimension.tsx +++ b/src/components/main-page/ThreeDimension.tsx @@ -14,7 +14,7 @@ export default function ThreeDimension() { isMountRef.current = true; gsap.set(cube2Ref.current, { rotateY: "-90deg", - x: "100px", + x: "80px", opacity: 1, }); gsap.set(cube1Ref.current, { opacity: 1 }); @@ -24,14 +24,14 @@ export default function ThreeDimension() { if (isHover) { gsap.to(cube1Ref.current, { rotateY: "90deg", - x: "-100px", + x: "-80px", duration: 0.5, }); gsap.fromTo( cube2Ref.current, { rotateY: "-90deg", - x: "100px", + x: "80px", duration: 0, }, { @@ -43,14 +43,14 @@ export default function ThreeDimension() { } else { gsap.to(cube2Ref.current, { rotateY: "-90deg", - x: "100px", + x: "80px", duration: 0.5, }); gsap.fromTo( cube1Ref.current, { rotateY: "90deg", - x: "-100px", + x: "-80px", duration: 0, }, { @@ -69,8 +69,12 @@ export default function ThreeDimension() { navigateTo="3d" > - - + + 3D + + + Motion + ); @@ -88,12 +92,22 @@ const Cube1 = styled.div` position: absolute; width: 10rem; height: 10rem; - background-color: #cccccc; + background-color: #fc9bff; + display: flex; + justify-content: center; + align-items: center; + font-size: 2rem; + font-weight: bold; `; const Cube2 = styled.div<{ isHover?: boolean }>` position: absolute; width: 10rem; height: 10rem; - background-color: #777777; + background-color: #96d8ff; + display: flex; + justify-content: center; + align-items: center; + font-size: 2rem; + font-weight: bold; `;