• Home
  • About
  • Posts
  • 1. 목표

    • 1.1. react-type-animation
    • 1.2. props 설정
  • 2. 전체 코드

  • 3. 사용 예시

타이핑 애니메이션 (React 컴포넌트)

📅 2023-06-22
🖋️ Byongho96
  • CSS
  • Animation
  • JavaScript
  • 타이핑

1. 목표

1.1. react-type-animation

구글링하면 react-type-animation라는 리액트용 타이핑 애니메이션 라이브러리가 바로 나온다. 이 라이브러리와 유사하게 동작하는 리액트 컴포넌트를 만드는 것이 오늘의 목표다.

my-type-animation.gif

1.2. props 설정

오늘 만들 Type 컴포넌트는 다음과 같은 prop을 받는다.

type Props = {
  phrases: string[] // 순서대로 타이핑 할 문구 리스트
  speed?: number // 타이핑 속도
  style?: object // 인라인 스타일 객체
  pause?: number // 한 개의 텍스트가 완성되고, 다음 텍스트가 타이핑 되기 전 대기시간
  isInfinite?: boolean // 타이핑 무한 반복 여부
}

2. 전체 코드

Github 소스 코드

타이핑 애니메이션을 구현하기 위해서 JS와 CSS를 모두 사용했다. 특별하게 짚고 싶은 부분이 2개 있다.

  • element.textContext

    • useState를 사용하는 것은 추천하지 않는다. setState자체가 비동기적이어서 그런지, 속도가 아주 빨라지면 철자 오류가 발생했다.
    • element.innerText가 공백을 인식하지 못해서 textContext를 사용했다.
  • CSS 가상선택자

    • YouTube강의를 보면 border-right요소로 커서를 구현하는 경우가 있다. 하지만 그럴 경우, 코드가 2줄 이상이 되면 요소 전체의 오른쪽에 커서 크게 생기는 오류가 발생했다. 또한 font와 border설정을 맞춰줘야하는 번거로움이 있다.
    • 가상선택자로 '|'라는 문자를 표시하면 위의 문제를 모두 해결할 수 있다.
import React, { useRef, useEffect } from 'react'
import './style.scss'

type Props = {
  phrases: string[] // 순서대로 타이핑 할 문구 리스트
  speed?: number // 타이핑 속도
  style?: object // 인라인 스타일 객체
  pause?: number // 한 개의 텍스트가 완성되고, 다음 텍스트가 타이핑 되기 전 대기시간
  isInfinite?: boolean // 타이핑 무한 반복 여부
}

const TypeAnimation: React.FC<Props> = ({
  phrases,
  speed = 5,
  style = {},
  pause = 2000, // 기본 대기 시간 2초
  isInfinite = false,
}) => {
  const textElementRef = useRef<HTMLParagraphElement>(null)

  useEffect(() => {
    const textElement = textElementRef.current
    if (!(textElement instanceof HTMLParagraphElement)) return

    textElement.textContent = ''

    let phraseIdx = 0 // 현재 타이핑하는 문구의 인덱스
    let charIdx = 0 // 현재 타이핑하는 글자 인덱스
    let interval: number = null // setInterval을 담을 변수

    // 타이핑 메인 함수
    const typeText = function () {
      textElement.style.setProperty('--cursor-opacity', 1) // 타이핑 하는 동안은 커서 깜빡이지 않도록
      const currentPhrase = phrases[phraseIdx] // 현재 타이핑하는 문구

      // Case1: 현재 문구 타이핑이 안 끝났을 경우
      if (charIdx < currentPhrase.length) {
        textElement.textContent += currentPhrase[charIdx++] // innerText 대신 textContent 사용해야 공백을 담을 수 있음
        return
      }
      // Case2: 현재 문구가 마지막 문구가 아닐 경우
      if (phraseIdx < phrases.length - 1) {
        phraseIdx++ // 다음 문구로 이동
        charIdx = 0 // 첫 번째 글자로 이동
        retypeAfterPause()
        return
      }
      // Case3: 무한 반복이 설정되어 있을 경우
      if (isInfinite) {
        phraseIdx = 0 // 처음 문구로 이동
        charIdx = 0 // 첫 번재 글자로 이동
        retypeAfterPause()
        return
      }
      // Case4: 타이핑 종료
      textElement.style.setProperty('--cursor-opacity', 0) // 타이핑 종료 후 커서 깜빡이도록
      clearInterval(interval)
    }

    // 일시 정지 후, 문구 초기화한 뒤 타이핑 재시작하는 함수
    const retypeAfterPause = function () {
      textElement.style.setProperty('--cursor-opacity', 0) // 이리 정지하는 동안 커서 깜빡이도록
      clearInterval(interval) // 기존 setInterval 제거

      // pause 이 후, setInterval 재시작
      setTimeout(() => {
        textElement.innerText = '' // 문구 초기화
        interval = setInterval(typeText, 1000 / speed)
      }, pause)
    }

    interval = setInterval(typeText, 1000 / speed)

    return () => {
      // clean-up 함수
      clearInterval(interval)
    }
  }, [phrases, speed, pause, isInfinite])

  return <p ref={textElementRef} className="type-animation" style={style}></p>
}

export default TypeAnimation

.type-animation-text {
  // 가상 선택자로 커서 생성
  &::after {
    content: '|';
    opacity: 1;
    animation: blink 1s steps(1, end) infinite;
  }
}

// 커서가 깜빡이는 애니메이션
// js에서 전달받은 값에 따라 깝빡임 여부 결정
@keyframes blink {
  50% {
    opacity: var(--cursor-opacity);
  }
}

3. 사용 예시

애니매이션 라이브 페이지로 이동!

my-type-animation.gif

import * as React from 'react'
import TypeAnimation from './TypeAnimation.tsx'

const App: React.FC = () => {
  return (
    <div>
      <TypeAnimation
        phrases={['I love chicken', 'Do you like chicken?', 'Of course!!!']}
        speed={7}
        style={{
          color: 'gray',
          fontSize: '8vw',
          fontWeight: 'bold',
          textAlign: 'center',
        }}
        pause={2000}
        isInfinite={true}
      />
    </div>
  )
}

export default App
다음 포스트

CSS 높이(height) 0 to auto 애니메이션

작성자 프로필
전체 글 (127)
  • Animation
    • Backend
      • Django
      • Spring
    • DevOps
      • AWS
      • CI&CD
      • Docker
      • Git
      • Gunicorn
      • Kubernetes
      • Nginx
    • Frontend
      • Gatsby
      • React
      • Vue
    • Knowledge
      • .etc
      • Algorithm
      • Data Structure
      • Database
      • Design Pattern
      • Interview
      • Network
      • Web
    • Language
      • CSS
      • HTML
      • Java
      • JavaScript
      • Linux
      • Python

    Copyright © 2023 Byongho96  & Powered by Gatsby