본문 바로가기

react

리엑트, state로 컴포넌트를 넣었다 뺄 때 부드러운 렌더링을 하는 법(framer-motion)

1. 문제

리엑트에서는 state를 이용해서 컴포넌트를 렌더링하거나 없애는 경우가 흔하다. 이 과정에 컴포넌트의 생성과 제거 애니메이션을 넣어주면  유저와의 상호작용이 더 매끄럽게 이뤄진다. 이는 framer-motion 같은 라이브러리를 이용하면 쉽게 해결되지만, 문제는 아래처럼 "주변의 다른 컴포넌트들이 레이아웃에 영향을 받는 경우"다. 

 

 

 

 

 

이런 경우, 주변 컴포넌트는 매끄럽지 않게 레이아웃이 변경된다. 이런 문제는 어떻게 해결할 수 있을까.

아래처럼 3가지 방법이 있다.

 

 

2. height animation

제일 처음 문제 삼은 예시는 animation으로 transform: scaleY()를 조작했다. 따라서, 컴포넌트 마운트 시, 요소의 정상적인 height만큼 레이아웃이 즉시 변하고, scale에 따라 컴포넌트가 서서히 렌더링되었다. 이 문제는 height를 animation의 대상으로 삼으면 쉽게 해결된다.

문제는 이런 방식이 성능 저하를 일으킬 수 있다.  기본적으로 애니메이션을 넣을 때는 애니메이션 대상 이외 요소들의 레이아웃을 건드리지 않는 게 좋다. 이를 레이아웃을 건드렸을 때 이를 재계산하는 과정을 "리플로우"라고 하는데, transform은 리플로우를 발생시키지 않아서 연산 비용이 작은 반면, 실제 레이아웃을 바꾸는 height는 리플로우를 발생시켜 큰 연산비용이 발생한다. 전체 레이아웃이 크게 바뀐다면 상황은 더 악화된다.

 

3. position

두 번째 방법은 애니메이션 대상 컴포넌트에 position을 설정하는 것이다. position은 대상을 전체 레이아웃에서 독립된 요소로 배치한다. 따라서, 컴포넌트가 새로 생성되어도 전체 레이아웃에 영향을 끼치지 않고, 그 위에 배치된다. 이 방식은 일반적으로 가장 많이 사용되고 특히 모달창을 띄울 때 아주 유용하고 쉽다.

하지만 모달창을 띄울 경우가 아니라면 이 방법을 쓸 수가 없다. 전체 레이아웃에서 벗어나기 때문에, 반응형 페이지에서는 위치를 계산하기도 어렵다.

 

4. pseudo element: before

 

세 번째 방법은 약간의 트릭을 이용한다.  먼저 state로 컨트롤할 박스를 감싸준다. 이를 wrap box라고 할 때, wrap box에 의사 요소로 ::before을 설정하고 크기는 animate box와 동일하게, 그리고 background-color: transparent로 설정한다. 마지막으로 animate box는 position:absolute; top:0로 설정해준다. 이러면, 컴포넌트가 존재하든지 없든지 상관없이 항상 해당 자리가 레이아웃에 마련되어 있다.
이 방법은 요소를 전체 레이아웃 내에 배치하면서도 2번 방법처럼 다른 요소에게 영향을 주지 않는다. 다만, 요소가 없을 때에도 해당 자리를 비워놔야 해서 공간이 낭비된다.