Ayden's journal

requestAnimationFrame 없이 애니메이션 구현

requestAnimationFrame을 사용하는 근본적인 이유는 브라우저의 화면 갱신 주기에 맞춰 콜백을 실행하고, 이를 통해 애니메이션이 더 부드럽고 일관되게 실행되도록 하는 것에 그 목적이 있다. 그런데 이전의 두 포스트에서 다루었던 애니메이션의 경우와 같이 CSS를 수정하는 형태 ─ 첫 번째는 엘리먼트 스타일의 top과 left 값을 변경했으며, 두 번째는 translateX 값을 변경하였다 ─ 에서는 복잡한 자바스크립트 로직 대신 transition 하나로 대응이 가능하다고 생각한다.

 

우선 마우스 움직임을 처리하는 로직이다. requestAnimationFrame 관련 로직을 제거하고, ref를 커스텀 훅 내부에서 처리하여 외부로 전달하는 방식을 채택했다.

export const useFollowMouseX = () => {
  const [targetX, setTargetX] = useState(0)
  const imageRef = useRef<HTMLImageElement>(null)
  
  const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
    const target = e.currentTarget as HTMLElement
    setTargetX(e.clientX - (target.offsetWidth / 2))
  }
 
  useEffect(() => {
    if (imageRef.current) {
      imageRef.current.style.transform = `translateX(${-50 - (targetX / 10)}%)`
    }
  }, [targetX])
 
  return {imageRef, handleMouseMove}
}

 

컴포넌트 내부에 작성되어있던 애니메이션 관련 로직이 커스텀 훅 안에서 처리되고 있기에 컴포넌트가 아주 깔끔해진 것을 확인할 수 있다.

export default function B () {
  const {imageRef, handleMouseMove} = useFollowMouseX()

  return (
    <div className={styles.imageWrapper} onMouseMove={handleMouseMove}>
        <Image ref={imageRef} src={butterfly} alt="" width={150} height={150} />
    </div>
  )
}

 

CSS는 transition만 추가했다. 베지어 곡선을 활용하면 더 예쁜 애니메이션이 될 수도 있겠지만 그럴 여력까지는 없어서 빌트인 키워드를 사용하였다.

.imageWrapper {
  position: relative;
  margin: 0 auto;
  height: 100vh;
 
  & > img {
    position: absolute;
    left: 50%;
    width: 150px;
    transition: transform 0.2s ease-out;
  }
}

 

 

+ position: absolute 엘리먼트의 top left를 수정하는 애니메이션의 경우 top 혹은 left만 처리할 경우에는 괜찮지만, 2차원 움직임을 가져가야 할 경우 requestAnimationFrame을 사용하는 편이 더 부드러워지는 것 같다.

블로그의 정보

Ayden's journal

Beard Weard Ayden

활동하기