• Home
  • About
  • Posts
  • 1. 목표

  • 2. 전체 코드

    • 2.1. 리액트 코드
    • 2.2. CSS 코드
    • 3. 사용 예시

무한 텍스트 테이프 애니메이션 (React 컴포넌트)

📅 2023-07-07
🖋️ Byongho96
  • CSS
  • JavaScript
  • canvas
  • 애니메이션
  • 텍스트 테이프

1. 목표

"YouTube: 자바스크립트로 5분만에 텍스트 스크롤 애니메이션 효과(text marquee effect) 마스터하기, 맛있는 코딩" 를 따라잡고, 이를 커스텀할 수 있는 리액트 컴포넌트로 만드는게 목표다.

infinite-text-tape.gif)

다음과 같은 Prop을 받아 리액트 컴포넌트를 커스텀할 수 있도록 할 것이다.

type Props = {
  text?: string     // 텍스트 문구
  fontColor?: string    // 텍스트 색깔
  tapeColor?: string    // 테이프 색깔
  isRight?: boolean     // 텍스트 흐름 방향
  rotationDeg?: number  // 테이프 회전 각도
}

2. 전체 코드

2.1. 리액트 코드

Github 소스코드

강의의 자바스크립트 코드를 리액트 컴포넌트를 만들었다.

Prop의 변화에 따라 속성을 변하게 하기 위해, useEffect문에 실행코드를 넣고 적절하게 클린업함수를 작성했다.

import React, { useEffect, useRef } from 'react'
import './style.scss'

type Props = {
  text?: string
  fontColor?: string
  tapeColor?: string
  isRight?: boolean
  rotationDeg?: number
}

const ScrollInfiniteTape: React.FC = ({
  text = 'Lorem ipsum',
  fontColor = 'black',
  tapeColor = 'yellow',
  isRight = true,
  rotationDeg = 0,
}: Props) => {
  const tapeRef = useRef<HTMLDivElement>(null)
  const textRef = useRef<HTMLParagraphElement>(null)
  const position = useRef(0) // 중간에 Prop이 바뀌더라도, position 유지하기 위해 밖에 선언

  useEffect(() => {
    const tapeElement = tapeRef.current
    const textElement = textRef.current

    if (
      !(tapeElement instanceof HTMLDivElement) ||
      !(textElement instanceof HTMLParagraphElement)
    )
      return

    // 텍스트가 흐르는 방향을 설정
    let textDirection = 1
    if (isRight) {
      tapeElement.style.justifyContent = 'flex-end'
    } else {
      tapeElement.style.justifyContent = 'flex-start'
      textDirection = -1
    }

    // 텍스트를 이동 시키는 함수
    const moveText = function () {
      position.current += 1
      // 반만큼 이동 했을 때, 다시 원위치로 복귀하여 무한스크롤
      if (position.current > textElement.scrollWidth / 2) {
        textElement.style.transform = `translateX(0)`
        position.current = 0
      }
      textElement.style.transform = `translateX(${
        textDirection * position.current
      }px)`
    }

    // 애니메이션 실행 함수
    let animationId: number = null
    const animate = function () {
      moveText()
      animationId = window.requestAnimationFrame(animate) // 희한하게 정의되기 전에 쓰네?
    }

    // 애니메이션 실행
    animate()

    // 스크롤 함수
    const speedUp = function () {
      position.current += 10
    }
    window.addEventListener('scroll', speedUp)

    // 클린업
    return () => {
      window.cancelAnimationFrame(animationId)
      window.removeEventListener('scroll', speedUp)
    }
  }, [isRight])

  return (
    <div
      ref={tapeRef}
      className="scroll-infinite-tape__tape"
      style={{
        backgroundColor: tapeColor,
        transform: `rotate(${rotationDeg}deg) translateX(-10%)`,
      }}
    >
      <p
        ref={textRef}
        className="scroll-infinite-tape__text"
        style={{ color: fontColor }}
      >
        {(text + '\u00a0\u00a0\u00a0\u00a0').repeat(20)}
      </p>
    </div>
  )
}

2.2. CSS 코드

테이프가 회전했을 때, 여백이 보이지 않게 하기 위해 두 가지 처리를 했다.

  • width
    먼저 너비를 120%로 설정해, 화면 너비보다 여유를 두었고
  • translateX(-10%)
    위의 리액트 코드를 보면, 인라인 스타일로 transform: translateX(-10%) 를 두어, 확보된 여백을 포함하여 화면의 중앙에 오도록 했다.

한 가지 유의해야할 점은, 이 컴포넌트를 쓰는 부모 컴포넌트가 overflow-x:hidden 설정을 가져야 한다.

.scroll-infinite-tape__tape {
  display: flex;
  width: 120%; // 회전했을 때, 약간의 여백 방지
  padding: 1vw 0 1.3vw;
}

.scroll-infinite-tape__text {
  min-width: fit-content; // grid 설정 때문에 p의 너비가 제한되었음
  font-size: 3vw;
  font-weight: 700;
  white-space: nowrap;
}

3. 사용 예시

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

infinite-text-tape.gif)

import * as React from 'react'
import './style.css'
import ScrollInfiniteTape from './ScrollInfiniteTape.tsx'

const App: React.FC = () => {
  return (
    <div className="scroll-infinite-tape-page">
      <ScrollInfiniteTape
        text="Orange is the New Black"
        fontColor="black"
        tapeColor="orange"
        isRight={false}
        rotationDeg={5}
      />
      <ScrollInfiniteTape
        text="Easy Peasy Lemon Squeezy"
        fontColor="ivory"
        tapeColor="gold"
        rotationDeg={10}
      />
      <ScrollInfiniteTape
        text="Have a good one"
        fontColor="white"
        tapeColor="skyblue"
        isRight={false}
        rotationDeg={-12}
      />
      <ScrollInfiniteTape
        text="Super Green"
        fontColor="beige"
        tapeColor="green"
        rotationDeg={5}
      />
      <ScrollInfiniteTape
        text="Friday Night"
        fontColor="pink"
        tapeColor="black"
        isRight={false}
        rotationDeg={-3}
      />
    </div>
  )
}

export default App
.scroll-infinite-tape-page {
  display: flex;
  overflow: hidden;
  flex-direction: column;
  justify-content: center;
  gap: 10vh;
  height: 200vh;
}
이전 포스트

비 내리는 캔버스 애니메이션 (React 컴포넌트)

다음 포스트

반응형 2D 캐러셀 (React 컴포넌트)

작성자 프로필
전체 글 (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