본문 바로가기
SPA/REACT

transition에 대한 물리엔진 패키지 - react-spring

by F.E.D 2022. 8. 21.

마우스 오버 애니메이션은 응용 프로그램이 동적이고 반응적으로 느껴지도록 하는 좋은 방법입니다.

작은 적용으로 전체 제품을 업그레이드 할 수 있는 디테일입니다.

마우스 오버를 사용하지 않고 키보드로 웹을 사용하는 사람들은 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>
  );
};

위 코드의 특이점은 타이밍으로 타이머가 있어서 마우스 효과로 애니메이션이 일어난 후에 몇 초 있다가 본래의 상태로 자연스럽게 돌아간다는 것입니다.

짧은 시간 후 저절로 꺼지는 "쓸모없는 기계"와 비슷합니다.

Edit react-animation-hover

꽤 괜찮아보이지만, 더 나아질 수 있습니다.

위의 효과는 꽤 인공적으로 느껴집니다.

물리엔진으로 사용할 수 있는 React Spring 패키지도 있습니다.

https://www.npmjs.com/package/react-spring 

 

react-spring

<p align="center"> <img src="https://i.imgur.com/QZownhg.png" width="240" /> </p>. Latest version: 9.5.2, last published: a month ago. Start using react-spring in your project by running `npm i react-spring`. There are 1552 other projects in the npm regist

www.npmjs.com

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;

 

 

 

 

 

출처 : https://www.joshwcomeau.com/react/boop/

댓글