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