drei 에서 제공해주는 스크롤 컨트롤
pages: 꽉차는 슬라이드의 페이지 갯수
damping: 스무스한 이동 효과 정도
<ScrollControls pages={8} damping={0.25}>
<Dancer />
</ScrollControls>
감싸진 Dancer 컴포넌트 안에서 useScroll() 선언하여 스크롤 정보를 불러올 수 있다
glb 모델을 불러오는동안의 로딩 작업
drei 에서 제공해주는 useProgress 또는 Loader 컴포넌트 사용
1️⃣ useProgress
비동기로 모델을 불러오니 Suspense 의 fallback 에서 로딩처리
useProgress 통해서 현재 진행 상태 확인
drei 에서 제공해주는 Html 컴포넌트를 사용하면 canvas 안에서도 html 을 불러올 수 있다
2️⃣ Loader
Canvas 컴포넌트 밖에서 선언되어야 하며
glb 모델을 불러올 컴포넌트는 Suspense fallback={null} 로 감싸져야 한다
<Canvas
...
>
...
<Suspense fallback={null}>
<TestMesh />
</Suspense>
</Canvas>
<Loader />
애니메이션 라이브러리
간단한 사용방법
// 처음 위치에서 ~ 의 위치로 이동하라
gsap.to(".box",{ rotation: 27, x: 100, duration: 1})
// ~ 에서 처음 위치로 이동하라
gsap.from(".class",{ opacity: 0, y: 100, duration: 1})
간단한 카메라 무빙 애니메이션
const three = useThree();
// 2.5 초 동안 [-5,5,5] 에서 [0,6,12] 로 카메라 이동
gsap.fromTo(
three.camera.position,
{
x: -5,
y: 5,
z: 5,
},
{
duration: 2.5,
x: 0,
y: 6,
z: 12,
}
);
// 카메라가 180도에서 0도로 회전
// camera Controls 컴포넌트가 없어야함 e.g. OrbitControls
gsap.fromTo(
three.camera.rotation,
{
z: Math.PI,
},
{
duration: 2.5,
z: 0,
}
);
timeline: 애니메이션을 시간 순서에 따라 제어하고싶을때 사용
timeline = gsap.timeline();
timeline.from(
dancerRef.current.rotation,
{
duration: 4,
y: -4 * Math.PI,
},
// delay (number or string)
// 선언되어 있지 않다면 앞선 애니메이션 끝나고 난 후 실행
// "<" 는 앞선 애니메이션 delay 와 동일하게 실행해라 의미
1
);
위 코드는 1초 후 4초동안 -720도를 도는 애니메이션이다.
timeline.seek(★시간) 은 timeline 의 애니메이션의 총 시간인 duration + delay 를 합친 시간 사이의 (★시간) 으로 위치한다.
즉, ★시간이 2.5 초라면 총 5초의 애니메이션에서 2.5 초에 해당하는 애니메이션 프레임 위치로 이동한다
이 방법을 이용해서 스크롤 시 glb 모델이 도는 동작을 할 수 있다.
useFrame(() => {
// 만약 스크롤을 절반까지 한 경우
// 0.5 * 5초 인 2.5초의 애니메이션 프레임 위치로 이동
timeline.seek(scroll.offset * timeline.duration());
});
※ 하나의 timeline 에 .from .from 계속 이어나가면서 선언할 수 있고, 이어진 duration 과 delay 를 모두 합한 값이 timeline.duration()
const { positions } = useMemo(() => {
const count = 500;
// new Float32Array([1,2,3]) => 3 * 4 = 12 Bytes
const positions = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i++) {
// -12.5 ~ 12.5 범위
positions[i] = (Math.random() - 0.5) * 25;
}
return { positions };
}, []);
<Points positions={positions.slice(0, positions.length / 3)}>
<pointsMaterial
size={0.5}
color={new THREE.Color("#DC4F00")}
// 원근에 따라 크기를 조정하고 싶을 때
sizeAttenuation
// 앞에 있는 별이 뒤에 별을 가리고 싶을 때
depthWrite
alphaMap={texture}
transparent
alphaTest={0.001}
/>
</Points>
※ 검정색은 transparent 선언 시 항상 투명하게 보임.
alphaTest 를 선언하면 투명하게 보일 알파의 범위를 설정할 수 있음.
e.g. 분홍색은 alphaTest={0.7} 이상에서 투명하게 보임.
지구가 태양 주위를 도는것처럼 댄서를 중심으로 공전하는 카메라 효과를 주고 싶을 때 사용
댄서 position을 copy해서 group 에 넣어주고 그 group 을 중심으로 카메라가 도는것
공전이 끝나면 메인 카메라는 자연스럽게 종료된 지점에 위치
const pivot = new THREE.Group();
pivot.position.copy(dancerRef.current.position);
pivot.add(three.camera);
three.scene.add(pivot);
...
timeline.to(pivot.rotation, {
duration: 10,
y: Math.PI,
onUpdate: () => {
// 자기 애니메이션 프레임에 도달했을 때
console.log("doing");
},
})
// 종료 시 항상 remove 해주어야함
return ()=>{
three.scene.remove(pivot)
}
useFrame(()=>{
// 총 8 페이지
// scroll.range ( 0 ~ 1 )
// 두번째 매개변수는 distance. 이동하는 거리의 양
article01Ref.current.style.opacity = `${1 - scroll.range(0, 1 / 8)}`;
// scroll.curve ( 0 - 1 - 0 )
// 처음과 끝 0 리턴 중간은 1 리턴
// 2/8 지점에서 1/8 만큼 이동하겠다 의미
article03Ref.current.style.opacity = `${scroll.curve(2 / 8, 1 / 8)}`;
// 현재 viewport 가 4/8 에서 + 3/8 까지의 범위 내에 있는지
if (scroll.visible(4 / 8, 3 / 8)) {
...
}
})
// 기본적으로 슬라이드 애니메이션 효과 적용되어 있음
<Scroll html>
...
</Scroll>
<PositionalAudio
position={[-24, 0, 0]}
autoplay
url="/audio/bgm.mp3"
distance={50}
loop
/>