컴포넌트 수준에서의 애니메이션 연구
이전 포스트에서는 페이지 전체를 대상으로 어떤 아이템(네모난 상자, 나비 등)이 마우스를 따라오도록 했고, 이를 구현하기 위해 전역 객체에 이벤트 리스너를 등록하였다. 하지만 컴포넌트 수준에서는 각각의 이벤트 리스너를 ReactElement에 직접 연결할 수 있다. 아래의 커스텀 훅은 이전 포스트에서 작성했던 useFollowMouse 훅과 구조적으로 동일하나, 이벤트 리스너를 ReactElement에 연결할 수 있도록 리턴하는 부분이 조금 다르다.
이벤트 리스너인 handleMouseMove를 잠시 살펴보면 e.terget이 아닌 e.currentTarget을 사용하고 있다는 점을 알 수 있다. 이는 이벤트 버블링으로 인한 오작동을 막기 위한 것이다. 사족이지만 나는 이벤트 객체의 이 두 프로퍼티가 이름이 바뀌어야 하는 것 아닐까 하는 생각을 종종 한다.
export const useFollowMouseX = () => {
const [targetX, setTargetX] = useState(0)
const [pointX, setPointX] = useState(0)
const speed = useRef(0.01)
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
const target = e.currentTarget as HTMLElement
setPointX(e.clientX - (target.offsetWidth / 2))
}
useEffect(() => {
const update = () => {
const newX = targetX + (pointX - targetX) * speed.current
setTargetX(Number(newX.toFixed(2)))
}
const a = requestAnimationFrame(update)
return () => {
cancelAnimationFrame(a)
}
}, [targetX, pointX])
return {targetX, handleMouseMove}
}
특정 아이템이 마우스의 움직임에 반대되는 방향으로 이동하도록 하고 싶다. 이러한 애니메이션을 구현하는 방법에는 크게 두 가지가 있을 것 같은데, 하나는 Image 태그의 style 프로퍼티가 targetX 값을 참조하도록 하는 것이고, 다른 하나는 ref를 사용해 스타일 객체를 가져와 이를 수정하는 것이다. 나는 후자의 방식을 택했는데 이는 ref 값 조작을 랜더링 중에 처리해서는 안된다는 개인적인 철학에 의한 것일 뿐 각 방식의 우열에 따른 것은 아니다.
export default function B () {
const {targetX, handleMouseMove} = useFollowMouseX()
const imageRef = useRef<HTMLImageElement>(null)
useEffect(() => {
if (imageRef.current) {
imageRef.current.style.transform = `translateX(${-50 - (targetX / 10)}%)`
}
}, [targetX])
return (
<div className={styles.imageWrapper} onMouseMove={handleMouseMove}>
<Image ref={imageRef} src={butterfly} alt="" width={150} height={150} />
</div>
)
}
참고로 css는 아래와 같았다.
.imageWrapper {
position: relative;
margin: 0 auto;
height: 100vh;
& > img {
position: absolute;
left: 50%;
width: 150px;
}
}
블로그의 정보
Ayden's journal
Beard Weard Ayden