Framer Motion

Framer Motion

Date: #2021-11-17

  1. MotionValue
  2. SVG Animation
  3. Slider

3. Motion Value

#7.7 #7.8 #7.9

https://www.framer.com/docs/motionvalue/

By manually creating MotionValues you can

  • Set and get their state.
  • Pass to multiple components to synchronise motion across them.
  • Chain MotionValues via the useTransform hook.
  • Update visual properties without triggering React’s render cycle.

useMotionValue

useMotion hook을 이용하면 motion에 따라 바뀌는 값들을 알 수 있음. set, get method를 통하여 읽기 쓰기를 할 수 있다. 중요한 것은 react state가 아니라 리렌더링을 하지 않는다. console.log를 통해서 로그를 찍어봐도 값이 변하지 않는다.

    const x = useMotionValue(0);
...

<Box
    drag={'x'}
    style={{ x }}
/>
<Box style={{ x }} />

이렇게 해두면 위의 박스를 드래그할 때 밑의 박스가 따라 움직이게 된다. x 값이 변하는 것을 굳이 봐야겠다하면, useEffect를 통해 이벤트 리스너를 통해 찍어 볼 수 있다.

    useEffect(() => {
    x.onChange(() => console.log(x.get()));
}, [])

useTransform

useTransform hook을 이용하면, React Native의 Animate.interpolate처럼 inputRange를 통해 output을 얻을 수 있다.

    const scaleValue = useTransform(x, [-800, 0, 800], [2, 1, 0]);

input range와 output range는 같은 길이를 가져야 한다(다르면 에러 발생) 숫자 뿐만 아니라 문자로 지정된 색도 interpolate 가능.

useViewportScroll

4가지 정보를 알려줌. scrollX, scrollY, scrollXProgress, scrollYProgress. 각각 horizontal, vertical로 scroll 한 거리를 pixel로 알려주고, 얼마자 progress 되었는지 0~1 사이 소수로 알려준다. 여기 주어진 값들도 MotionValue이기 때문에 useTransform을 이용해서, 다른 interpolated된 값들을 얻을 수 있다.

4. SVG Animation

#7.10

<svg role="img"
     xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
    <path fill="currentColor"
          d="M224 373.12c-25.24-31.67-40.08-59.43-45-83.18-22.55-88 112.61-88 90.06 0-5.45 24.25-20.29 52-45 83.18zm138.15 73.23c-42.06 18.31-83.67-10.88-119.3-50.47 103.9-130.07 46.11-200-18.85-200-54.92 0-85.16 46.51-73.28 100.5 6.93 29.19 25.23 62.39 54.43 99.5-32.53 36.05-60.55 52.69-85.15 54.92-50 7.43-89.11-41.06-71.3-91.09 15.1-39.16 111.72-231.18 115.87-241.56 15.75-30.07 25.56-57.4 59.38-57.4 32.34 0 43.4 25.94 60.37 59.87 36 70.62 89.35 177.48 114.84 239.09 13.17 33.07-1.37 71.29-37.01 86.64zm47-136.12C280.27 35.93 273.13 32 224 32c-45.52 0-64.87 31.67-84.66 72.79C33.18 317.1 22.89 347.19 22 349.81-3.22 419.14 48.74 480 111.63 480c21.71 0 60.61-6.06 112.37-62.4 58.68 63.78 101.26 62.4 112.37 62.4 62.89.05 114.85-60.86 89.61-130.19.02-3.89-16.82-38.9-16.82-39.58z"
          class=""></path>
</svg>

일단 fontawesome에서 airbnb 아이콘 svg를 따온다. path의 속성에는 stroke, strokeWidth가 있어 svg의 겉라인을 꾸며줄 수 있다.

    <motion.path
    initial={{
        pathLength: 0.1,
        fill: 'rgba(255, 255, 255, 0)',
    }}
    animate={{
        pathLength: 1,
        fill: 'rgba(255, 255, 255, 1)',
        transition: {
            duration: 2,
        },
    }}
    stroke={'white'}
    strokeWidth={3}
    fill="currentColor"
    d="M224 373.12c-25.24-31.67-40.08-59.43-45-83.18-22.55-88 112.61-88 90.06 0-5.45 24.25-20.29 52-45 83.18zm138.15 73.23c-42.06 18.31-83.67-10.88-119.3-50.47 103.9-130.07 46.11-200-18.85-200-54.92 0-85.16 46.51-73.28 100.5 6.93 29.19 25.23 62.39 54.43 99.5-32.53 36.05-60.55 52.69-85.15 54.92-50 7.43-89.11-41.06-71.3-91.09 15.1-39.16 111.72-231.18 115.87-241.56 15.75-30.07 25.56-57.4 59.38-57.4 32.34 0 43.4 25.94 60.37 59.87 36 70.62 89.35 177.48 114.84 239.09 13.17 33.07-1.37 71.29-37.01 86.64zm47-136.12C280.27 35.93 273.13 32 224 32c-45.52 0-64.87 31.67-84.66 72.79C33.18 317.1 22.89 347.19 22 349.81-3.22 419.14 48.74 480 111.63 480c21.71 0 60.61-6.06 112.37-62.4 58.68 63.78 101.26 62.4 112.37 62.4 62.89.05 114.85-60.86 89.61-130.19.02-3.89-16.82-38.9-16.82-39.58z"
></motion.path>

path 컴포넌트는 motion.path 컴포넌트로 대체하고, 나머지 애니메이션을 꾸며주면 되는데.. pathLength는 path를 얼마나 그릴지라서.. 위의 값을 주면 애니메이션되는 듯이 멋있게 렌더링이 완성이 된다.

속성마다 다른 transition을 줄 수 있다?

여태까지 배운 내용으로는 애니메이션에 transition을 줘서 모든 속성이 같은 transition을 갖도록 했는데, motion 컴포넌트의 transition 속성을 직접 주면 css 속성마다 따로 transition을 줄 수도 있다.

위의 코드에서..

   <motion.path
        initial={{
            pathLength: 0.1,
            fill: 'rgba(255, 255, 255, 0)',
            stroke: 'rgb(40, 100, 214)',
        }}
        animate={{
            pathLength: 1,
            fill: 'rgba(255, 255, 255, 1)',
            stroke: 'rgb(120, 53, 123)',
        }}
        transition={{
            default: {
                duration: 5,
            },
            fill: {
                delay: 5,
                duration: 2,
            },
        }} />

애니메이션 속성을 위처럼 바꿔볼 수 있는데..transition 속성에서 볼 수 있듯.. fill 속성만 따로 transition을 주는 것도 가능하다.

5. AnimatePresence

# 7.11

https://www.framer.com/docs/animate-presence/

Animate components when they’re removed from the React tree. It’s required to enable exit animations because React lacks a lifecycle method that: Notifies components when they’re going to be unmounted and Allows them to defer that unmounting until after an operation is complete (for instance an animation).

보통의 리액트 상황에서는

   { open ? <ShowSomething /> : null}

이런식으로 작성을 하기 때문에 사라질 때 애니메이션은 없는 편이다. 그치만 framer motion 컴포넌트가 AnimatePresence 컴포넌트로 감싸진다면.. exit 라는 속성이 생기게 된다.

예제코드

<AnimatePresence>
    {open && (
        <Box
            variants={boxVars}
            initial={'start'}
            animate={'end'}
            exit={'exit'}
        />
    )}
</AnimatePresence>
<button onClick={toggle}>Toggle</button>

exit 속성으로 사라질 때 애니메이션을 줄 수 있다.

Note: Child motion components must each have a unique key prop so AnimatePresence can track their presence in the tree. 문서에 나와있듯.. key를 꼭 줘야 한다. 그래야 AnimatePresence가 DOM tree에서 제거 되는 것을 추적할 수 있다.

In React, changing a component’s key makes React treat it as an entirely new component. So the old one is unmounted before the new one is mounted. So by changing the key of a single child of AnimatePresence, we can easily make components like slideshows 약간의 트릭인 것 같은데, 키 값이 바뀌면 새로운 컴포넌트로 인식하기 때문에 슬라이드처럼 보일 수 있다고 한다.

https://www.framer.com/docs/animate-presence/#usage 예제를 확인함이 좋을 것 같다.

5. Slider part

위의 노트와 예제에 해당하는 부분.. #7.12 #7.13

const boxVars = {
    start: {
        x: 500,
        opacity: 0,
        scale: 0,
    },
    end: {
        x: 0,
        opacity: 1,
        scale: 1,
        transition: {
            duration: 1,
        }
    },
    exit: {
        opacity: 0,
        scale: 0,
        x: -500,
    },
};
const [page, setPage] = useState(0);

const next = () => setPage((prev) => (prev === 9 ? 1 : prev + 1));
<div
    style={{
        display: 'flex',
        overflow: 'hidden',
        backgroundColor: 'transparent',
    }}
>
    <AnimatePresence>
        {Array.from(new Array(10)).map((_, index) =>
            page === index ? (
                <Box
                    key={index}
                    variants={boxVars}
                    initial={'start'}
                    animate={'end'}
                    exit={'exit'}
                >
                    {index + 1}
                </Box>
            ) : null,
        )}
    </AnimatePresence>
</div>
<button onClick={next}>Next</button>

대략 위와 같은 코드로 만들어졌는데, AnimatePresence를 이용하여 슬라이더를 쉽게 구현할 수 있었다. 보통 강의 듣기 강의 시작 전에 이번엔 ~~을 할 것이다… 하면 강의를 듣지 않고, 먼저 구현해 보는데, 이번에는 구현할 때 문제가 있었다. 그… position: aboluste 관련된 것이었는데.. 애니메이션이 사라지기는 동안 + 다음 박스가 오는 애니메이션이 같은 div 안에 존재 해버리니까.. 이상한 형태로 애니메이션이 진행 되었는데, 강의를 듣고 position: aboluste를 추가했었어야 되는구나..알게 되었음.

위의 코드는 Array를 이용하여 여러개의 박스를 렌더링 하였지만, 사실상 위에 Note에 해당하는 내용에서 알 수 있듯.. key가 바뀌면 새로운 컴포넌트로 인식을하고 그 이전의 키값을 가지고 있는 컴포넌트는 사라지는 것으로 리액트는 인식을 하기 때문에 위의 코드는

    <AnimatePresence>
        <Box
            key={page}
            variants={boxVars(isNext)}
            initial={'start'}
            animate={'end'}
            exit={'exit'}
        >
            {page}
        </Box>
    </AnimatePresence>

이와 같이 바뀔 수 있다. 작동은 똑같이 한다.

custom

https://www.framer.com/docs/component/###custom

variants에 일종의 변수가 들어간 함수처럼 작동할 수 있도록 해준다.

const boxVars: Variants = {
        start: (isNext: boolean) => ({
            x: isNext ? 500 : -500,
            opacity: 0,
            scale: 0,
        }),
        end: {
            x: 0,
            opacity: 1,
            scale: 1,
            transition: {
                duration: 1,
            },
        },
        exit: (isNext: boolean) => ({
            opacity: 0,
            scale: 0,
            x: isNext ? -500 : 500,
            transition: {
                duration: 1,
            },
        }),
    };

    // ...(중략)...
    
    <AnimatePresence exitBeforeEnter custom={isNext}>
        <Box
            key={page}
            custom={isNext}
            variants={boxVars}
            initial={'start'}
            animate={'end'}
            exit={'exit'}
                >
                {page}
            </Box>
    </AnimatePresence>

이상하게 AnimatePresence에도 custom 값을 줬어야 했다.

exitBeforeEnter

이 속성이 없으면. 없어지는 박스와 새로나오는 박스가 겹쳐 벌인다. 이 속성이 있으면, 없어지는 박스가 완전히 없어져야 새로운 박스가 시작된다.

6. layout animation

layout

https://www.framer.com/docs/component/##layout-animation

css에 따라 component의 위치가 바뀔 수 있는데.. 위치가 바뀔 때 애니메이션을 하도록.. 해주는 역할을 한다. 공식홈페이지에는 스위치 버튼이 예시로 되어 있는데, 그냥 단순하게 css는 position 바뀌도록만 하고.. layout 속성만 주면 알아서 애니메이션이 된다는..것..

<Box
    style={{
        justifyContent: clicked ? 'center' : 'flex-start',
        alignItems: clicked ? 'center' : 'flex-start',
    }}
    onClick={() => setClicked((prev) => !prev)}
>
    <Circle layout />
</Box>

위의 예시에서 보면 Box가 클릭되면 단순히 위치가 center냐 start냐가 갈리지만, Circle에 layout 속성 하나만 줬을 뿐인데, 애니메이션이 되어버린다..

layoutId

https://www.framer.com/docs/component/###layoutid

layoutId 있는 것이 react tree에서 사라졌다가 다른 곳에서 나타나면, 이전 위치에서부터 애니메이션을 시켜준다. 이전 컴포넌트가 남아있으면, 새로운 컴포넌트와 동시에 fade된다고(crossfade)..한다..

<Box>{!clicked && <Circle layoutId={'circle'} />}</Box>
<Box>{clicked && <Circle layoutId={'circle'} />}</Box>

클릭하면 박스에 있는 Circle이 왔다갔가 나타나게 하는 코드이지만, layoutId 하나를 추가함으로 엄청난 애니메이션이 만들어진다. 한쪽 박스에 있는 것이 다른 쪽 박스로 이동하는 듯한.. 마술같은 애니메이션이 단 layoutId라는 한 단어*2로 나타난다. 후덜덜..


Written by@pleed0215
records.length > recalls.length

InstagramGitHubFacebook