하루하루 꾸준히, 인생은 되는대로

리액트

리액트로 스크롤 애니메이션을 넣어보자!

긤효중 2023. 4. 9. 23:01

정적인 사이트를 만들때, 스크롤 애니메이션은 정말 멋진 것 같다.

특히 애플 공식 홈페이지의 애니메이션은 너무너무 이쁜데, 이를 보고 내가 만드는 것에 도입해보고 싶었다.

 

스크롤 애니메이션

브라우저는 사용자가 화면을 스크롤 할떄마다, 스크롤 이벤트를 발생시킨다.

리액트로 스크롤을 구현하는 라이브러리는 많지만, 직접 구현해 보고 싶었다.

 

화면을 스크롤 할떄, 특정 화면이 보이기 시작한다면 애니메이션이 적용되면서 등장하는 방식이다.

 

Intersection Observer

방금 말한 '특정 화면이 보인다면'을 Intersection Observer를 통해 해결 가능하다.

기본적으로 뷰포트와 설정한 교차점을 관찰해, 요소가 뷰포트에 포함되는지 ,사용자에게 지금 보이는 화면인지를 쉽게 판단가능하게 해준다.

 

비동기적으로 동작한다는 특징도 존재한다.

const observer = new IntersectionObserver(callback,option); //초기화
observer.observe(DOM) //요소 등록

 

observe()메서드로 특정 DOM이 뷰포트에 들어왔는지, 안들어왔는지 확인 가능하다.

위의 코드에서 콜백은 관찰할 대상으로 등록되거나 가시성의 변화가 생기면 발생한다.

 

콜백은 2개의 인수를 받는다. entries와 observe를 받는데, 하나씩 살펴보자.

entries와 observer를 인수로 받는다.

entries는 IntersectionObserverEntry의 배열이라는 타입을 갖고 있다.

IntersectionObserverEntry의 타입을 그대로 쭉 따라가보면, 다음과 같은 타입이 보인다.

이 코드만 봐서 이해가 안되서 IntersectionObserverEntry타입이 뭔지 더 찾아보았다.

IntersectionObserverEntry는 요소와 뷰포트 간, 교차 영역에 대한 다양한 정보를 제공하는 객체이다.

  • target: 관찰 대상 요소(Element)를 나타냅니다.
  • boundingClientRect: 관찰 대상 요소의 사각형 영역(ClientRect) 정보를 나타낸다.
  • intersectionRect: 관찰 대상 요소와 뷰포트(또는 다른 부모 요소)의 교차 영역의 사각형 영역(ClientRect) 정보를 나타낸다
  • intersectionRatio: 관찰 대상 요소와 뷰포트(또는 다른 부모 요소)의 교차 영역의 비율을 나타낸다. 0에서 1 사이의 값이며, 0은 교차 영역이 없음을, 1은 관찰 대상 요소가 완전히 교차 영역에 있음을 나타낸다
  • isIntersecting: 관찰 대상 요소가 교차 영역에 있는지 여부를 나타냅니다. true는 교차 영역에 있음을, false는 교차 영역에 없음을 나타낸다
  • rootBounds: 뷰포트(또는 다른 부모 요소)의 사각형 영역(ClientRect) 정보를 나타낸다
  • time: 교차 영역의 변경 시간을 나타낸다

 

정말 많은 정보를 알 수 있는데, isIntersection이 필요하다. 교차 여부에 있으면 애니메이션이 적용되면서,

화면이 나와야하기 때문에 이 외의 정보는 지금은 딱히..?필요하지 않을 것 같다.


options은 다음의 옵션이 존재한다.

let options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}

root : 기본 값은 브라우저 뷰포트이고, 정해지지 않았거나 null인 경우 기본값이다.

rootMargin : root가 가진 여백인데, 기본값은 0이며, 바깥 여백으로 Root의 범위를 조절 가능하다

밑의 그림처럼 rootMargin을 주면 Root로부터 마진이 생기는 것을 볼 수 있다.

threshold: 0부터 1까지의 숫자 값이 들어간다.

root와 observer 요소가 얼마나 교차되었는지를 결정하며 0이 기본 값이다. 0은 전혀 교차하지 않았음을, 1은 전체가 교차함을 의미한다.

밑의 그림으로 보면 더 잘 이해가 된다.

thereshold가 커질수록 루트와 observer요소의 교차가 더 많이 커지는 것을 볼 수 있다.


먼저 콜백과 옵션을 각각 지정해준다. 콜백은 IntersectionObserverEntry의 배열을 받아서, 

배열을 돌면서 IntersectionObserverEntry가 isIntersecting 로 설정되어 있다면, setState를 실행할 것이다.

 

이 콜백을 분리해보면 다음의 코드와 같다.

 옵션 객체의 threshold를 0.5로 지정해 주고 나머지는 기본 옵션을 사용했다. 그 후,새로운 intersection observer를 생성하였다. 
이제 새로 만든 intersection observer의 observe 메서드를 사용해 DOM이 뷰포트에 들어왔는지? 검사할 것이다.useRef를 사용해서 DOM에 접근 할 수 있었고, 한번 정한 ref가 바뀔 가능성이 없다고 생각해서,

 

useEffect안에 의존성 배열로 빈 배열을 할당했다. 최종적인 코드는 다음과 같다.

이렇게 하고 ref에는 setState인 setInviewPort가 실행되어서 true로 바꾸게 된다면 새로운 클래스를 추가해 주었다.

그리고 새로운 클래스에 애니메이션을 지정해 주면 잘 작동하는 것을 볼 수 있다.

 

전체 코드 

 

다만 개선이 필요하다. 아무래도 useEffect안에 로직을 커스텀 훅으로 분리해서 재사용 가능하게 만드는게 좋기 떄문에,

커스텀 훅으로 분리를 이어서 해 볼 생각이다.

 

신기..