마우스 오버 애니메이션은 응용 프로그램이 동적이고 반응적으로 느껴지도록 하는 좋은 방법입니다.
작은 적용으로 전체 제품을 업그레이드 할 수 있는 디테일입니다.
마우스 오버를 사용하지 않고 키보드로 웹을 사용하는 사람들은 Enter를 눌러서 마우스 오버 효과를 활성화 할 수 있습니다.
const Boop = ({ rotation = 0, timing = 150, children }) => {
const [isBooped, setIsBooped] = React.useState(false);
const style = {
display: 'inline-block',
backfaceVisibility: 'hidden',
transform: isBooped
? `rotate(${rotation}deg)`
: `rotate(0deg)`,
transition: `transform ${timing}ms`,
};
React.useEffect(() => {
if (!isBooped) {
return;
}
const timeoutId = window.setTimeout(() => {
setIsBooped(false);
}, timing);
return () => {
window.clearTimeout(timeoutId);
};
}, [isBooped, timing]);
const trigger = () => {
setIsBooped(true);
};
return (
<span onMouseEnter={trigger} style={style}>
{children}
</span>
);
};
위 코드의 특이점은 타이밍으로 타이머가 있어서 마우스 효과로 애니메이션이 일어난 후에 몇 초 있다가 본래의 상태로 자연스럽게 돌아간다는 것입니다.
짧은 시간 후 저절로 꺼지는 "쓸모없는 기계"와 비슷합니다.
꽤 괜찮아보이지만, 더 나아질 수 있습니다.
위의 효과는 꽤 인공적으로 느껴집니다.
물리엔진으로 사용할 수 있는 React Spring 패키지도 있습니다.
https://www.npmjs.com/package/react-spring
import { animated, useSpring } from "react-spring";
import { useEffect, useState } from "react";
export const BoopSpring = ({ rotation = 0, timing = 150, children }) => {
const [isBooped, setIsBooped] = useState(false);
const style = useSpring({
display: "inline-block",
backfaceVisibility: "hidden",
transform: isBooped ? `rotate(${rotation}deg)` : `rotate(0deg)`
});
useEffect(() => {
if (!isBooped) {
return;
}
const timeoutId = window.setTimeout(() => {
setIsBooped(false);
}, timing);
return () => {
window.clearTimeout(timeoutId);
};
}, [isBooped, timing]);
const trigger = () => {
setIsBooped(true);
};
return (
<animated.span onMouseEnter={trigger} style={style}>
{children}
</animated.span>
);
};
패키지를 설치한 후에, 직접 style을 useSpring hook을 통해서 animated 컴포넌트로 전달을 해주었습니다.
조금은 더 자연스러워졌죠?
느린 느낌이 있어서 조금 더 수정합시다.
const style = useSpring({
display: 'inline-block',
backfaceVisibility: 'hidden',
transform: isBooped
? `rotate(${rotation}deg)`
: `rotate(0deg)`,
config: {
tension: 300,
friction: 10,
},
});
혹자는 setTimeout 대신에 React Spring API에서 onReset Callback을 사용하는 것이 어떤가에 대해서 말하곤 합니다.
하지만, React Spring의 물리학적 장점을 위해서 Timeout을 사용하는 것이 더 낫다고 생각합니다.
1. 마우스 오버가 연속으로 일어났을 때 완전히 멈추기 전에 기본 위치로 다시 잡아당겨야하기 때문에 timeout을 clear해서 잡아당기고 싶습니다.
2. onReset의 일반적인 문제는 애니메이션이 끝날 때 까지 대기한다는 점입니다.
아래 출처를 클릭해서 trigger함수에 대해서 useCallback을 사용해서 최적화도 해보세요 : )
PrefersReducedMotion을 통해서 a11y도 대응해보세요.
해당 작업들을 추가로 한 뒤의 UseSpring 완성 Hooks
import React from 'react';
import { useSpring } from 'react-spring';
// UPDATE this path to your copy of the hook!
// Source here: https://joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion
import usePrefersReducedMotion from '@hooks/use-prefers-reduced-motion.hook';
function useBoop({
x = 0,
y = 0,
rotation = 0,
scale = 1,
timing = 150,
springConfig = {
tension: 300,
friction: 10,
},
}) {
const prefersReducedMotion = usePrefersReducedMotion();
const [isBooped, setIsBooped] = React.useState(false);
const style = useSpring({
transform: isBooped
? `translate(${x}px, ${y}px)
rotate(${rotation}deg)
scale(${scale})`
: `translate(0px, 0px)
rotate(0deg)
scale(1)`,
config: springConfig,
});
React.useEffect(() => {
if (!isBooped) {
return;
}
const timeoutId = window.setTimeout(() => {
setIsBooped(false);
}, timing);
return () => {
window.clearTimeout(timeoutId);
};
}, [isBooped]);
const trigger = React.useCallback(() => {
setIsBooped(true);
}, []);
let appliedStyle = prefersReducedMotion ? {} : style;
return [appliedStyle, trigger];
}
export default useBoop;
'SPA > REACT' 카테고리의 다른 글
React memo를 사용하기 전에 (0) | 2022.11.13 |
---|---|
다크모드를 위해서 Context API보다 CSS 변수를 활용하세요 (0) | 2022.10.30 |
리플로우가 일어나는 환경에서 React Suspense 사용해보기 (0) | 2022.01.18 |
[React] useState의 초깃값 지연 (0) | 2021.01.01 |
[React] React를 위한 렌더링 캐시 (0) | 2019.01.27 |
댓글