React, Next, Redux/⚛ React.JS

useSpring을 이용한 3d-card 만들기

DarrenKwonDev 2021. 2. 2. 13:44

앞서 react-spring은 5개의 훅을 가지고 있다고 안내하였다. 

darrengwon.tistory.com/1238

 

react-spring 소개 및 간단한 이용법

이번 프로젝트에서는 다루는 asset들이 패션 모델들의 이미지였기 때문에 프론트엔드에서 애니메이션, 트랜지션 등 화려한 뷰를 보여줄 필요가 있었다. 그래서 이번 기회에 react-spring을 써보기로

darrengwon.tistory.com

 

  • 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];

 

https://gist.github.com/Palisanka/b9c03d21cdbb5e6e89d92dba6880297b

 

마우스를 떼면 제자리로 돌아오고요.

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 떨어져서 대상을 바라보니 작은 움직임에도 크게 반응하겠죠.

좌측이 perspective를 10px 준것, 우측이 1600px 준 것.