react-spring 소개 및 간단한 이용법
이번 프로젝트에서는 다루는 asset들이 패션 모델들의 이미지였기 때문에 프론트엔드에서 애니메이션, 트랜지션 등 화려한 뷰를 보여줄 필요가 있었다. 그래서 이번 기회에 react-spring을 써보기로 했다. 이번 아니면 언제 쓰겠는가.
자연스러운 애니메이션을 위해 스프링의 원리를 기반으로 한 애니메이션을 설계했다고 한다.
React, React-Native, React-Native-Web 에서 모두 사용 가능하다.
설치
yarn add react-spring
용량과 트리 쉐이킹
Everything included you end up with currently 10.7KB (web), 9.7KB (react-native) or 6.9KB (/universal export). The size will ultimately depend on your build-chain and can decrease with tree-shaking. For instance, just importing useSpring without color-interpolations (using the /universal export) you will get 4.7KB.
용량이 큰 것은 아니지만, 트리쉐이킹을 할 수 있으니해서 나쁠 것은 없습니다.
Basic
react-spring은 5개의 훅으로 이루어져있다. 이 훅 5개를 사용할 줄 알면 react-spring은 박.살.
각 훅에 공식 문서로 가는 경로를 달아놨으니 필요할 때 찾아가자.
- useSpring : data를 a에서 b로 이동시키는 spring이다.
- useSprings : multiple spring이다.
- useTrail : 단일 dataset을 위한 multiple spring이다. 하나의 spring을 다른 것들이 따라간다. 그래서 "Trail"이다.
- useTransition : 트랜지션의 mount/unmount을 위해 사용된다.
- useChain : 여러 애니메이션을 중첩하여 적용하거나, 순차적으로 적용하기 위해 사용한다. 그래서 "Chain"이다.
공식 문서에서는 가장 간단한 useSpring을 통해서 spring에 대한 개념을 이해시키고 있는데 이걸 그대로 따라가보자.
괜히 verbose해져서 코드에 주석으로 다 달았다.
요약하자면, animated로 컴포넌트나 html 태그를 확장하고, useSpring으로 spring 애니메이션을 적용하면 된다.
* 여담으로, 공식문서에 있는 예시 코드가 잘못되었다. 사라지려면 1에서 시작해서 0으로 없어져야 한다. 아래 코드에선 고쳐놨다.
// animated는 react 외부에서 구현된 애니메이션 factory이다. react의 도구들, 예컨대 hook으로 애니메이션을 작성하면 성능이 좋지 않기 때문
import { useSpring, animated } from "react-spring";
function App() {
// useSpring을 이용하여 spring을 정의함.
// spring은 a에서 b로 움직이는 스프링 애니메이션.
// self-updating을 하는 동적인 값이며(타입으론 AnimatedValue로 되어있음), static value가 아님
const props = useSpring({ opacity: 0, from: { opacity: 1 } });
// native tag를 extends 한 것으로, animated.tag 꼴로 사용해줌. ex - animated.div, animated.span, animated.svg ...
// 만약 React Component를 animated하게 확장하고 싶다면 animated(App), animated(Login) 과 같이 단순히 ()로 감싸면 된다.
// css-in-js인 styled-components나 emotion 등을 확장하려면 const AnimatedHeader = styled(animated.h1) 꼴로 사용하면 된다.
return <animated.div style={props}>I will fade in</animated.div>;
}
export default App;
spring이 적용되는 것을 수치적으로 보고 싶다면 0부터 1까지 이동시켜 확인해보자.
import { useSpring, animated } from "react-spring";
function App() {
const props = useSpring({ number: 1, from: { number: 0 } });
return <animated.span>{props.number}</animated.span>;
}
export default App;
어쨌거나 아래와 같이 useSpring에 다양한 값을 전달하여 spring 애니메이션을 사용할 수 있다.
const props = useSpring({
vector: [0, 10, 30],
display: 'block',
padding: 20,
background: 'linear-gradient(to right, #009fff, #ec2f4b)',
transform: 'translate3d(0px,0,0) scale(1) rotateX(0deg)',
boxShadow: '0px 10px 20px 0px rgba(0,0,0,0.4)',
borderBottom: '10px solid #2D3747',
shape: 'M20,20 L20,380 L380,380 L380,20 L20,20 Z',
textShadow: '0px 5px 15px rgba(255,255,255,0.5)'
})
- interpolation
interpolation('보간법')하면 점과 점 사이를 직선으로 연결하지 않고 부드럽게 곡선으로 연결하는, 근사적인 함수이 떠오른다. 그런데 react-spring의 내부적인 수학적 원리를 차치하고, 그냥 a에서 b로 이동하는 애니메이션의 값을 이용하여 변수처럼 사용할 수 있게 해주는 메서드라고 이해하는 게 쉽다.
왜, 모든 속성을 spring으로 작성할 수 없지는 않은가? border with를 위한 prop 하나 만들고, margin을 위한 prop 하나 만들고, 이렇게 특정 속성을 컨트롤하기 위해 prop를 계속 만드는 건 가독성이 좋지 않다.
이 녀석은 다른 걸 구현해보면서 직접 이해해보는게 직접 와닿는다.
import { useSpring, animated, interpolate } from "react-spring";
function App() {
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: "red" },
o: 1,
xyz: [10, 20, 5],
color: "green",
});
return (
<animated.div
style={{
color, // 일반 spring 애니메이션이 적용됨
// 가장 기본적인 interpolate 사용
background: o.interpolate((o) => `rgba(210, 57, 77, ${o})`),
// array로 된 값을 각각 뿌리는데 interpolate를 사용함.
// 참고로, useSpring을 통해 이동하는 값은 자체적으로 interpolate 메서드를 지니고 있음. 굳이 import 안해도 됨
transform: xyz.interpolate(
(x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`
),
// 여러 spring prop을 섞어 쓰고 싶은 경우 아래처럼 하면 됨.
border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
// interpolate는 체인처럼 사용할 수 있음. type 이름도 InterpolationChain임
padding: o
.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] })
.interpolate((o) => `${o}%`),
}}
>
{o.interpolate((n) => n.toFixed(2)) /* innerText interpolation ... */}
</animated.div>
);
}
export default App;