티스토리 뷰
[React.js 3D 에니메이션 만들기(1)] generator(제네레이터) 를 활용해 에니메이션 시퀸스 만들기! with react three fiber/ framer-motion / react three rapier
실전압축코딩 2024. 11. 19. 14:39
포트폴리오 제작중, 3d text mesh의 역동적인 움직임을 중심으로 나를 표현 하는 3d 에니메이션을 만들어 보고 싶다는 생각이 들었다.
그래서 three.js 관련된 라이브러리를 사용해 구현해 보기로 하였다.
설치 라이브러리
npm i three three-stdlib @react-three/fiber @react-three/drei @react-three/rapier framer-motion framer-motion-3d
1. three
- 설명: WebGL 기반 3D 그래픽을 쉽게 구현할 수 있는 JavaScript 라이브러리.
- 용도: 3D 모델링, 애니메이션, 조명, 텍스처 맵핑 등 3D 그래픽의 기본적인 빌딩 블록 제공.
2. three-stdlib
- 설명: Three.js의 추가 유틸리티 모음.
- 용도: OrbitControls, GLTFLoader 같은 자주 쓰이는 툴과 확장을 제공.
- 참고: 이 패키지는 Three.js의 확장 기능이 점점 자체 패키지로 분리되는 경향이 있어, 필요한 유틸리티를 제공함.
3. @react-three/fiber
- 설명: React에서 Three.js를 효율적으로 사용할 수 있도록 돕는 React Renderer.
- 용도: React 컴포넌트 기반으로 Three.js 오브젝트와 씬을 선언적으로 생성하고 관리.
4. @react-three/drei
- 설명: React Three Fiber를 위한 확장 컴포넌트 모음.
- 용도: OrbitControls, Text, Sky, Environment 등 추가적인 유용한 3D 구성 요소를 쉽게 사용할 수 있음.
5. @react-three/rapier
- 설명: Rapier(빠르고 경량의 물리 엔진)를 React Three Fiber와 통합하기 위한 라이브러리.
- 용도: 충돌 감지, 중력, 강체(Rigid Body) 등 물리 엔진 기능을 구현.
6. framer-motion
- 설명: React 애니메이션 라이브러리로 간단하면서 강력한 애니메이션 구현 가능.
- 용도: 모션 컴포넌트 및 키프레임 기반 애니메이션을 활용해 자연스러운 UI 동작 구현.
7. framer-motion-3d
- 설명: Framer Motion의 기능을 Three.js 오브젝트에도 확장한 라이브러리.
- 용도: React Three Fiber 환경에서 3D 오브젝트의 애니메이션을 간단히 작성.
제너레이터(generator)를 활용해 시퀀스를 전환하기
React를 활용해 애니메이션을 구현하는 과정에서, 각 시퀀스를 개별적으로 생성하고 시퀀스별로 상태(state)를 설정하는 방식을 선택 했다. 그리고 각 시퀸스를 전환하기 위해 제네레이터를 사용하였다.
제네레이터는 yield 키워드를 사용하여 특정 시점에 상태를 중단하고, 이후에 상태를 다시 업데이트할 수 있는 기능을 제공 한다. 이 방식은 애니메이션 또는 시퀀스를 순차적으로 제어하고, 각 시퀀스가 끝날 때마다 상태를 명시적으로 업데이트할 수 있다.
이 방법은 각 애니메이션 단계를 독립적으로 제어할 수 있어 유연하며, 코드의 확장성과 유지 보수 측면에서도 장점이 많다!
또한 각 에니메이션 시퀸스를 일괄적으로 관리 할수 있다는 점이 굉장한 장점 이다!
애니메이션 시퀀스를 일괄적으로 관리하는 장점
중앙 집중식 상태 관리
제네레이터를 사용하여 애니메이션 시퀀스를 일괄적으로 관리하면, 모든 애니메이션 상태를 중앙에서 관리할 수 있다. 이는 각 컴포넌트가 개별적으로 상태를 관리하는 것보다 훨씬 효율적이다. 모든 시퀀스의 상태를 하나의 컨텍스트에서 처리하면, 상태 간 의존성을 쉽게 관리할 수 있습니다.
예를 들어, 애니메이션의 흐름이나 상태 변경을 하나의 시퀀스로 묶어서 관리하면, 각 컴포넌트에서 개별적으로 state를 조작할 필요 없이 중앙에서 상태를 제어할 수 있다.
상태 동기화가 용이
여러 컴포넌트에서 개별적으로 상태를 관리하면, 각 컴포넌트가 다른 상태를 참조하거나 변경할 때 상태 불일치 문제가 발생할 수 있다. 예를 들어, 하나의 컴포넌트에서 배경색을 변경하고, 다른 컴포넌트에서 텍스트를 업데이트하는 경우, 이 둘이 동기화되지 않으면 애니메이션의 일관성이 깨질 수 있다.
그러나 제네레이터를 사용하면, 애니메이션 상태가 하나의 흐름으로 연결되므로 상태가 일관되게 유지된다. 시퀀스 내에서 각 상태가 일관되게 변화하며, 이를 하나의 컴포넌트나 컨텍스트에서 처리할 수 있어 동기화 문제가 최소화 된다.
애니메이션 관리의 효율성
각 컴포넌트가 자체적으로 애니메이션을 관리하는 경우, 각 시퀀스마다 상태 업데이트가 필요하고, 여러 상태를 독립적으로 추적해야 하기 때문에 복잡성이 증가 한다. 시퀀스가 늘어날수록 관리할 상태가 많아지고, 상태 간의 관계를 추적하는 것이 어려워 진다.
반면, 제네레이터를 활용하면 하나의 흐름으로 상태를 관리할 수 있어 애니메이션 시퀀스가 보다 깔끔하고 효율적으로 처리된다. 각 상태 변경이 자연스럽게 이어지며, 시퀀스가 끝날 때까지 흐름을 일관되게 유지할 수 있다.
유지보수와 확장성
여러 컴포넌트가 각각 애니메이션 상태를 관리하는 경우, 시퀀스를 변경하거나 추가할 때마다 각 컴포넌트의 상태를 수정해야 한다.. 이 경우, 새로운 시퀀스를 추가할 때마다 코드 수정 범위가 커지고, 유지보수가 어려워질 수 있다.
하지만 제네레이터로 시퀀스를 일괄 관리하면, 새로운 애니메이션을 추가할 때 단 한 곳만 수정하면 된다. 이는 확장성과 유지보수성 측면에서 큰 장점이다.
애니메이션 흐름 추적 용이
제네레이터를 사용하여 애니메이션을 일괄 관리하면, 각 시퀀스의 흐름을 명확하게 추적할 수 다. yield는 각 시퀀스를 한 번에 반환하므로, 애니메이션 진행 상황을 쉽게 디버깅하거나 로깅할 수 있다. 각 상태 변경이 명확하게 구분되어 있기 때문에 애니메이션이 어떻게 진행되고 있는지, 어느 시점에서 상태가 변경되는지를 쉽게 알 수 있다.
접근 방식 및 구현
1. Context와 상태 정의
먼저, SequenceContext와 상태(sequenceInfo)를 정의한다. 이 상태는 애니메이션에서 필요한 다양한 속성들을 포함하고 있으며, 각 시퀀스에서 보여줄 텍스트, 색상, 애니메이션 효과 등을 설정한다.
const sequenceInfo = {
backgroundColor: "#FFA500",
color: "#FF6D00",
text: "불가능",
showImpossible: false,
showChallenge: false,
showGrowth: false,
showLeejaeyeop: false,
showCuboidCollider: true,
shakeX: false,
shakeY: false,
delayTime: 0,
h1Animation: null,
showH2Text: false,
changeLightPos: false,
isDone: false,
};
2. GenerateSequence 제너레이터 함수
GenerateSequence 함수는 제너레이터 함수로, 각 시퀀스를 단계별로 실행하고 상태를 변화시킨다. 각 단계에서는 상태를 변경하고, yield를 사용해 변경된 상태를 반환한다.
function* GenerateSequence() {
// 일회성 이벤트는 제거
// 첫번째 시퀀스-> impossible geo 생성 delay 1초
sequenceInfo.showImpossible = true;
yield { ...sequenceInfo, delayTime: 800 };
// 두번째 시퀀스 -> challenge geo 생성
sequenceInfo.showChallenge = true;
yield { ...sequenceInfo };
// 세번째 시퀀스 -> color change , text change , camera shake
sequenceInfo.backgroundColor = "#caf0f8";
sequenceInfo.color = "#005AFF";
sequenceInfo.text = "도전";
yield { ...sequenceInfo, shakeX: true, shakeY: true, delayTime: 1200 };
// 네번째 시퀸스 -> growth text geo 생성
sequenceInfo.showImpossible = false;
sequenceInfo.showGrowth = true;
yield { ...sequenceInfo };
// 다섯번째 시퀸스 -> color change , text change , camera shake
sequenceInfo.backgroundColor = "#c9ffed";
sequenceInfo.color = "#08bd53";
sequenceInfo.text = "성장";
yield { ...sequenceInfo, shakeX: true, shakeY: true, delayTime: 1000 };
// 여섯번째 시퀸스 -> h1 transform , sink growth geo
sequenceInfo.showCuboidCollider = false;
sequenceInfo.showChallenge = false;
(sequenceInfo.h1Animation = {
top: 0,
left: 0,
transform: "rotate3d(0, 0, 0, 0deg)",
justifyContent: "left",
}),
yield {
...sequenceInfo,
delayTime: 1100,
};
// 7번쩨 시퀸스 -> show h2
sequenceInfo.showH2Text = true;
yield {
...sequenceInfo,
};
// 8 시퀸스 -> Leejaeyeop
sequenceInfo.showCuboidCollider = true;
sequenceInfo.showChallenge = false;
sequenceInfo.showLeejaeyeop = true;
yield { ...sequenceInfo };
// 9 light 위치 변경
sequenceInfo.changeLightPos = true;
sequenceInfo.showGrowth = false;
yield { ...sequenceInfo, shakeX: true, shakeY: true, delayTime: 2000 };
// fade out 효과
return { ...sequenceInfo, isDone: true };
}
- 각 시퀀스는 상태를 변경하고, yield를 사용하여 새로운 상태 객체를 반환한다.
- 여기서, 일시적인 state(단발성 에니메이션 등)는 새로운 상태 객체에 값을 바인딩 하고, 지속이 필요한 state(background color)등은 sequnceInfo 객체의 값을 변경해 지속성을 부여 하였다.
- 상태가 변경된 후, 고정된 시퀀스 간의 시간 간격이 필요하면 delayTime을 설정해 시간 간격을 조정할 수 있다.
- 각 시퀀스는 다음 상태로 넘어가기 전에 yield로 제어를 잠시 멈추고, 상태를 반환하여 애니메이션을 시퀀스대로 처리할 수 있게 한다.
3. useSequence 훅을 통한 상태 관리
useSequence 훅은 상태를 관리하고, moveNextSequence 함수로 각 시퀀스를 순차적으로 변경할 수 있게 한다.
function useSequence() {
const [generateSequence] = useState(() => GenerateSequence());
const [sequence, setSequence] = useState(() => generateSequence.next().value);
const moveNextSequence = () => {
const nextSequence = generateSequence.next();
if (nextSequence.value) {
setSequence(nextSequence.value);
}
};
return { sequence, setSequence, moveNextSequence };
}
- generateSequence는 GenerateSequence 제너레이터 함수로 초기화된다.
- sequence 는 현재 시퀀스의 상태를 저장하며, 처음에는 첫 번째 yield에서 반환된 상태가 설정된다.
- moveNextSequence는 generateSequence.next()를 호출하여 제너레이터를 한 단계씩 진행시키고, 새로운 상태를 setSequence를 통해 업데이트 한다.
4. 시퀀스 상태 사용
- useSequence 훅 사용하기
useSequence 훅을 사용하여 시퀀스를 관리하고, 각 시퀀스에 맞게 상태를 업데이트 한다. 이 훅은 sequence 상태와 moveNextSequence 함수를 반환한다. sequence는 현재 시퀀스에 해당하는 상태 객체이며, moveNextSequence는 시퀀스를 한 단계씩 진행시키는 함수이다.
const Intro = () => {
const { sequence, moveNextSequence } = useSequence();
// sequnce state 구조분해할당
const {
backgroundColor,
color,
isDone,
} = sequence;
}
- sequence 상태 활용하기
sequence 상태에는 여러 애니메이션과 UI 변경에 필요한 속성들이 포함 된다. 각 시퀀스에 맞게 해당 속성들을 이용해 화면을 렌더링한다.
- 배경색과 텍스트 색상: sequence.backgroundColor와 sequence.color를 사용하여 동적으로 스타일을 적용할 수 있다.
- 애니메이션에 필요한 요소들: 각 시퀀스에 맞춰 보여줄 텍스트나 요소들을 조건부 렌더링으로 처리할 수 있다.
const Intro = () => {
const { sequence, moveNextSequence } = useSequence();
// sequnce state 구조분해할당
const {
backgroundColor,
color,
isDone,
} = sequence;
return (
// state 사용
<motion.div
animate={{
backgroundColor,
color,
opacity: isDone ? 0 : 1,
}}
>
...
</motion.div>
)
}
- 시퀀스 상태 전환
moveNextSequence 함수는 버튼 클릭이나 다른 이벤트에서 호출되어 각 시퀀스를 차례대로 전환 한다. 예를 들어, 사용자가 "다음" 버튼을 클릭할 때마다 상태가 업데이트되고, 새로운 애니메이션 시퀀스가 화면에 나타 난다.
<button onClick={moveNextSequence}>다음</button>
// delayTime이 지나고 다음 시퀸스로 이동.
useEffect(() => {
if (delayTime > 0) {
setTimeout(() => moveNextSequence(), delayTime);
}
return;
}, [delayTime]);
5. createContext 를 활용해 SequenceContext 생성.
createContext를 활용하여 SequenceContext를 생성하고, 이를 통해 sequence 상태와 moveNextSequence 함수 등의 값을 컴포넌트 트리 내에서 공유할 수 있게 된다. SequenceContext를 사용하면, 여러 컴포넌트에서 동일한 시퀀스 상태를 손쉽게 접근하고 업데이트할 수 있다.
use-sequence.tsx
const SequenceContext = createContext<SequenceInfo | null>(null);
intro.tsx
const Intro = () => {
...
return (
<SequenceContext.Provider value={sequence}>
</SequenceContext.Provider>
)
}
children.tsx
import { SequenceContext } from "./hooks/use-sequence";
const child = () => {
const {
showImpossible,
showChallenge,
showGrowth,
showCuboidCollider,
showLeejaeyeop,
changeLightPos,
} = useContext<SequenceInfo>(SequenceContext);
...
}
최종 흐름도
시작
|
|-> useSequence 훅 사용
| - useSequence 훅이 `GenerateSequence` 제너레이터를 초기화
| - 초기 상태를 생성하여 `sequence` 상태에 저장
| - sequence 정보: { backgroundColor, color, text, showImpossible, ... }
|
|-> SequenceContext 제공
| - `SequenceContext.Provider`로 `sequence` 상태와 `moveNextSequence` 함수 전달
| - Context를 구독하는 컴포넌트가 `sequence` 상태에 접근하고, 이를 렌더링
|
|-> 각 컴포넌트에서 Context 사용
| - Context에서 `sequence` 값을 가져와 렌더링
| - 예: 텍스트, 색상, 애니메이션 등 업데이트
|
|-> moveNextSequence 호출
| - 버튼 클릭이나 다른 이벤트로 `moveNextSequence` 호출
| - `moveNextSequence`가 제너레이터의 `next()` 메서드 호출
| - `next()`가 반환하는 새로운 시퀀스를 `sequence` 상태에 설정
|
|-> 새로운 시퀀스 상태가 적용됨
| - `sequence` 상태가 변경되면 Context를 통해 이를 구독한 모든 컴포넌트가 업데이트
| - 예: 텍스트 변경, 색상 변경, 애니메이션 적용
|
|-> 반복
| - 시퀀스가 진행됨에 따라 `moveNextSequence`를 계속 호출하여 상태 변경
| - 각 컴포넌트는 최신 `sequence` 상태를 사용하여 렌더링
|
|-> 종료
| - `isDone: true` 상태에 도달하면 시퀀스가 종료
다음에는 구체적으로 3d 컴포넌트를 제작하는 과정을 다뤄보도록 하겠다!
다음글
https://ljy1011.tistory.com/225
[React.js 3D 에니메이션 만들기(2)] 3D text geometry 생성 및 조명 설정 with react three fiber
https://ljy1011.tistory.com/224 [React.js] generator(제네레이터) 를 활용해 3D 에니메이션 만들기! with react three fiber/ framer-motion / react포트폴리오 제작중, 3d text mesh의 역동적인 움직임을 중심으로 나를 표현
ljy1011.tistory.com
'프론트엔드' 카테고리의 다른 글
- Total
- Today
- Yesterday
- react three fiber leva
- react 3d 에니메이션
- 394 decode string
- ts glsl
- eslint
- 394. decode string javascript
- 394. decode string js
- typescript gsls
- react leva
- vue3
- react 3d text
- three.js leva
- rollup typescript
- react fiber 3d
- rollup typescript react
- react 3d animation
- rollup ts react npm
- attempted import error: bvh_struct_definitions' is not exported from './gpu/bvhshaderglsl.js' (imported as 'bvhshaderglsl').
- next.js glsl
- react glsl
- leva
- react three fiber
- [leetcode] 394. decode string
- next.js import glsl
- vue
- react 3d
- rollup react.js npm
- webpack glsl
- Vue.js
- [leetcode] 394. decode string js
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |