Progressive-Animated Typography

Sharlotte ·2022년 10월 7일
0

react 포트폴리오를 개발하던 중 framer-motion과 react로 Typography, 텍스트에 입력하는듯한 애니메이션을 부여하고 싶어졌다.

첫번째 시도는 단순히 제공받은 텍스트 문자열을 특정 주기로 자르도록 setTimeout 체인을 만드는 것이였다.

const ProgressiveTypograpy: React.FC<{ 
  label: string, 
  speed?: number 
} & Omit<TypographyProps, 'children'>> = ({ 
  label, 
  speed = 500, 
  ...props 
}) => {
  const [prog, setProg] = useState(0);

  useEffect(() => {
    for(let i = 0; i < label.length; i++) {
     setTimeout(() => setProg(prev => prev+1), speed);
    }
  }, []);

  return <Typography {...props}>{label.slice(0, prog)}</Typography>
}

하지만 아래와 같이 문자들이 연속적으로 등장하지 않아 실패했다.

setState의 비동기적 상태관리가 내 생각보다 더 lazy한 것이였다. 재귀적 방법 또한 시도해봤으나 근본적으로 setState에 대한 문제라 역시 실패했다. 500ms가 이러하니 더 빨리해도 소용이 없고 좋지 않은 방법이라 판단해 포기했다.

잠시 고민을 하고 구글링을 하려던 참에, 비생산적이겠지만 차라리 문자열을 문자 엘리먼트로 쪼개어 framer-motion의 animate속성의 keyframe 특징을 이용하여 단계별 등장을 유도하면 어떨까 라는 생각이 들어 바로 시도해보았다.

//...윗부분 생략
}) => {
  const variants = {
    visible: (i: number) => ({
      opacity: 1,
      transition: {
        delay: i * 0.3,
      },
    }),
    hidden: { opacity: 0 },
  }
  //mui의 Typography가 내 생각보다 무겁지 않기를 빌며...
  return (
    <div style={{ display: 'flex', justifyContent: 'center' }}>
    {label.split('').map((char, i) => 
    <motion.div
      key={i}  
      custom={i}
      initial='hidden'
      animate="visible"
      variants={variants}
      whileInView='visible'
    >
      <Typography {...props}> {char} </Typography>
    </motion.div>)}
    </div>
  );
}


역시 framer-motion는 최고다.
속도를 빠르게 해서 각 철자간 애니메이션 간극이 사라질 수 있나 싶었는데 잘도 사라진다.

import { Typography, type TypographyProps } from '@mui/material';
import { motion } from 'framer-motion';
import React from 'react';

const ProgressiveTypography: React.FC<{ 
    label: string, 
    speed?: number 
} & TypographyProps> = ({ 
    label,
    speed = 0.1, 
    ...props 
}) => {
    const variants = {
        visible: (i: number) => ({
	        opacity: 1,
        	transition: {
        	    delay: i * speed,
        	},
        }),
        hidden: { opacity: 0 },
    }
    //mui의 Typography가 내 생각보다 무겁지 않기를 빌며...
    return (
        <div style={{ display: 'flex', justifyContent: 'center' }}>
          {label.split('').map((char, i) => 
            <motion.div
                key={i}  
                custom={i}
                initial='hidden'
                animate="visible"
                variants={variants}
                whileInView='visible'
            >
                <Typography {...props}>{char}</Typography>
            </motion.div>
          )}
        </div>
    );
}
export default ProgressiveTypography;

아무튼 완성!

profile
샤르르르

0개의 댓글