
효과는 양쪽에서 다르지만 같은 논리로 슬라이더를 구현해야 했습니다.
원래는 페이지별로 코드를 작성했는데 반복되는 영역이므로 catch로 처리하도록 하겠습니다.
const useSlider = <T>(
data: T(),
duration?: number,
moveScale?: number,
opacity?: boolean,
type?: 'spring' | 'linear' | 'tween' | 'inertia',
): (index: number, direction: number, increase: () => void, decrease: () => void, animationVariant: Variants)=>{
...
}
나중에 슬라이더의 애니메이션을 사용자 정의하기 위해 Duration(지속 시간), moveScale(움직이는 정도), Opacity(투명도 변경) 및 Type(애니메이션 효과 유형)을 입력했습니다.
반환되는 결과는 슬라이더의 인덱스, 애니메이션이 진행되는 방향, 증가 함수, 감소 함수, 애니메이션 효과 개체입니다.
useState, 증가, 감소
const ((index, direction), setIndex) = useState((0, 0));
function increase() {
setIndex((prev) => (prev(0) > data.length - 2 ? (0, +1) : (index + 1, +1)));
}
function decrease() {
setIndex((prev) => (prev(0) < 1 ? (data.length - 1, -1) : (index - 1, -1)));
}
인덱스와 디렉션은 같은 상태로 관리되었습니다. 이는 setState가 비동기적으로 작동하고 애니메이션이 별도의 상태로 관리되어 제대로 작동하지 않았기 때문입니다.
단일 상태 및 콜백 함수에 대해 증가 및 감소 함수를 만들었습니다.
애니메이션 개체
const animationVariant = {
initial: (direction: number) => {
return {
x: moveScale ? (direction > 0 ? moveScale : -moveScale) : direction > 0 ? 1000 : -1000,
opacity: opacity && 0,
transition: {
duration: duration ? duration : 0.5,
type: type && type,
},
};
},
visible: {
x: 0,
opacity: opacity && 1,
transition: {
duration: duration ? duration : 0.5,
type: type && type,
},
},
exit: (direction: number) => {
return {
opacity: opacity && 0,
x: moveScale ? (direction < 0 ? moveScale : -moveScale) : direction < 0 ? 1000 : -1000,
transition: {
duration: duration ? duration : 0.5,
type: type && type,
},
};
},
};
애니메이션은 방향에 따라 결정되기 때문에 실제 움직임이 있어야 하는 initial과 exit를 함수와 반환 객체로 생성했습니다.
전체 코드
import { Variants } from 'framer-motion';
import { useState } from 'react';
const useSlider = <T>(
data: T(),
duration?: number,
moveScale?: number,
opacity?: boolean,
type?: 'spring' | 'linear' | 'tween' | 'inertia',
): (index: number, direction: number, increase: () => void, decrease: () => void, animationVariant: Variants) => {
const ((index, direction), setIndex) = useState((0, 0));
function increase() {
setIndex((prev) => (prev(0) > data.length - 2 ? (0, +1) : (index + 1, +1)));
}
function decrease() {
setIndex((prev) => (prev(0) < 1 ? (data.length - 1, -1) : (index - 1, -1)));
}
// 애니메이션 효과 관련 코드 initial은 초기(등장시 초기값), visible은 initial에서 랜더링될시까지 보여줄 애니메이션, exit은 visible에서 컴포넌트가 지워질때 보여줄 애니메이션
const animationVariant = {
initial: (direction: number) => {
return {
x: moveScale ? (direction > 0 ? moveScale : -moveScale) : direction > 0 ? 1000 : -1000,
opacity: opacity && 0,
transition: {
duration: duration ? duration : 0.5,
type: type && type,
},
};
},
visible: {
x: 0,
opacity: opacity && 1,
transition: {
duration: duration ? duration : 0.5,
type: type && type,
},
},
exit: (direction: number) => {
return {
opacity: opacity && 0,
x: moveScale ? (direction < 0 ? moveScale : -moveScale) : direction < 0 ? 1000 : -1000,
transition: {
duration: duration ? duration : 0.5,
type: type && type,
},
};
},
};
return (index, direction, increase, decrease, animationVariant as Variants);
};
export default useSlider;
적용된 코드
부모 구성 요소
const ProjectSlider = () => {
const (index, direction, increase, decrease, animationVariants) = useSlider<IProjectInner>(
ProjectData,
0.5,
1000,
true,
'spring',
);
return (
<Wrapper>
<Left size={30} onClick={increase} />
<RelativeWrapper>
<Project ProjectData={ProjectData(index)} direction={direction} animationVaraints={animationVariants} />
</RelativeWrapper>
<Right size={30} onClick={decrease} />
</Wrapper>
);
};
하위 구성 요소
const Project = ({
ProjectData,
direction,
animationVaraints,
}: {
ProjectData: IProjectInner;
direction: number;
animationVaraints: Variants;
}) => {
return (
<AnimatePresence initial={false} custom={direction}>
<ProjectWrapper
variants={animationVaraints}
initial="initial"
animate="visible"
exit="exit"
key={ProjectData.introduce}
custom={direction}
>
...
</ProjectWrapper>
</AnimatePresence>
);
};

