useSpring을 이용한 3d-card 만들기
앞서 react-spring은 5개의 훅을 가지고 있다고 안내하였다.
- useSpring : data를 a에서 b로 이동시키는 spring이다.
- useSprings : multiple spring이다.
- useTrail : 단일 dataset을 위한 multiple spring이다. 하나의 spring을 다른 것들이 따라간다. 그래서 "Trail"이다.
- useTransition : 트랜지션의 mount/unmount을 위해 사용된다.
- useChain : 여러 애니메이션을 중첩하여 적용하거나, 순차적으로 적용하기 위해 사용한다. 그래서 "Chain"이다.
여기서는 useSpring을 이용하여 3d card를 만들어 볼 것이다.
참고할만한 공식 문서는 아래와 같다.
common api : www.react-spring.io/docs/hooks/api
usespring : www.react-spring.io/docs/hooks/use-spring
소스코드 : codesandbox.io/embed/rj998k4vmm (공식 홈페이지에서 제공된 예시임)
소스코드에서 CSS를 가급적 내부로 다 옮겨와서 한 눈에 보기 쉽게 만들어놨다. 하나씩 짚어보자.
import { useSpring, animated } from "react-spring";
import styled from "styled-components";
const S = {
Card: styled(animated.div)`
width: 500px;
height: 500px;
background: grey;
border-radius: 5px;
background-image: url("어쨌거나 이미지 경로");
background-size: cover;
background-position: center center;
box-shadow: 0px 10px 30px -5px rgba(0, 0, 0, 0.3);
transition: box-shadow 0.5s;
will-change: transform; // 미리 변화할 것을 알려주어 브라우저가 최적화할 수 있게 함. https://developer.mozilla.org/ko/docs/Web/CSS/will-change
border: 15px solid white;
`,
};
const calc = (x, y) => [-(y - window.innerHeight / 2) / 20, (x - window.innerWidth / 2) / 20, 1.1];
const trans = (x, y, s) => `perspective(600px) rotateX(${x}deg) rotateY(${y}deg) scale(${s})`;
function App() {
const [props, set] = useSpring(() => ({
xys: [0, 0, 1],
config: { mass: 5, tension: 350, friction: 40 },
}));
return (
<S.Card
onMouseMove={({ clientX: x, clientY: y }) => set({ xys: calc(x, y) })}
onMouseLeave={() => set({ xys: [0, 0, 1] })}
style={{ transform: props.xys.interpolate(trans) }}
/>
);
}
export default App;
useSpring : config
아래처럼 useSpring이 반환하는 값을 사용할 수 있다. 넘겨준 값들은 props에 저장되어 있다.
set은 값을 변경하는 메서드이고, stop은 값을 변경하는 걸 멈추는 메서드이다.
const [props, set, stop] = useSpring(() => ({opacity: 1}))
기초적인 spring이 필요한 경우 아래처럼 사용할 수도 있다.
const props = useSpring({ number: 1, from: { number: 0 } });
실제 카드에서 적용된 코드를 살펴보자.
stop은 따로 추출하지 않았고, props와 set만 따왔다.
const [props, set] = useSpring(() => ({
xys: [0, 0, 1],
config: { mass: 5, tension: 350, friction: 40 },
}));
props에 xys가 들어있다. (참고로 x, y, scale의 약자이다)
config 부분은 www.react-spring.io/docs/hooks/api 에 다 나와있다. common api인데
mass : spring mass 쉽게 말해 크기다. (물리학도들은 이 번역에 대해 진정하고 그냥 받아들이자.)
tension : spring energetic load (강직도. 정도로 이해하자.)
friction : spring resistence (저항도. 작성해보면 tension을 억누르는 효과가 있음을 알 수 있다.)
이건 계산하기 보다 직접 값을 넣어서 원하는 느낌이 나는지 확인하는게 빠르다.
useSpring : set
단순히 값을 변화시키는 메서드입니다.
<S.Card
onMouseMove={({ clientX: x, clientY: y }) => set({ xys: calc(x, y) })}
onMouseLeave={() => set({ xys: [0, 0, 1] })}
style={{
transform: props.xys.interpolate(
(x, y, s) =>
`perspective(1200px) rotateX(${x}deg) rotateY(${y}deg) scale(${s})`
),
}}
/>
마우스를 올리면, calc 함수의 값까지 spring 애니메이션으로 움직이게 됩니다.
잘못 설정하면 카드가 flip 되는 등 의도한 대로 작동하지 않습니다.
const calc = (x, y) => [-(y - window.innerHeight / 2) / 20, (x - window.innerWidth / 2) / 20, 1.1];
마우스를 떼면 제자리로 돌아오고요.
onMouseLeave={() => set({ xys: [0, 0, 1] })}
적용되는 style에 있어서는 interpolate에서 설정한대로 움직입니다.
마웃를 올리면 scale은 1.1까지 커지고, x는 -(y - window.innerHeight / 2) / 20 만큼 rotate하게 되죠.
style={{
transform: props.xys.interpolate(
(x, y, s) =>
`perspective(1600px) rotateX(${x}deg) rotateY(${y}deg) scale(${s})`
),
}}
여기서 perspective는 3d 대상을 얼마나 멀리 떨어져서 보는가에 대한 값입니다.'
MDN에서 제공해주는 예시를 보면 직관적으로 이해할 수 있습니다.
developer.mozilla.org/en-US/docs/Web/CSS/perspective
perspective-origin: 50% 50%;은 기본값이니 그대로 두면 되고, perspective만 조정해서 볼 경우 아래 같이 됩니다.
당연히 10px 떨어져서 대상을 바라보니 작은 움직임에도 크게 반응하겠죠.