분할 텍스트 애니메이션 빌드

글자 및 단어 분할 애니메이션을 빌드하는 방법에 관한 기본 개요입니다.

이 게시물에서는 최소한의 기능으로 액세스 가능하며 여러 브라우저에서 작동하는 웹용 분할 텍스트 애니메이션과 상호작용을 해결하는 방법에 관한 생각을 공유하고자 합니다. 데모를 사용해 보세요.

데모

동영상을 선호하는 경우 이 게시물의 YouTube 버전을 확인하세요.

개요

텍스트 분할 애니메이션은 놀라운 효과를 줄 수 있습니다. 이 게시물에서는 애니메이션의 가능성을 간단히 살펴보지만, 이를 바탕으로 발전시킬 수 있습니다. 목표는 점진적으로 애니메이션을 적용하는 것입니다. 텍스트는 기본적으로 읽을 수 있어야 하며 애니메이션은 그 위에 빌드되어야 합니다. 분할 텍스트 모션 효과는 화려하고 방해가 될 수 있으므로 HTML을 조작하거나 사용자가 모션에 동의하는 경우에만 모션 스타일을 적용합니다.

다음은 워크플로 및 결과에 대한 일반적인 개요입니다.

  1. CSS 및 JS용으로 축소된 모션 조건부 변수를 준비합니다.
  2. JavaScript에서 분할 텍스트 유틸리티를 Prepare로 설정합니다.
  3. 페이지 로드 시 조건문과 유틸리티를 조정합니다.
  4. 문자와 단어의 CSS 전환 및 애니메이션을 작성합니다(멋진 부분!).

다음은 만들려는 조건부 결과의 미리보기입니다.

요소 패널이 열려 있고 모션 감소가 '감소'로 설정되어 있으며 h1이 분할되지 않은 상태로 표시된 Chrome Devtools의 스크린샷
사용자가 작은 움직임을 선호함: 텍스트를 읽기 쉽고 분할되지 않음

사용자가 모션 감소를 선호하는 경우 HTML 문서를 그대로 두고 애니메이션을 실행하지 않습니다. 움직임이 괜찮다면 조각으로 자릅니다. 다음은 JavaScript가 텍스트를 문자별로 분할한 후의 HTML 미리보기입니다.

Elements 패널이 열려 있고 축소된 모션이 '축소'로 설정되어 있고 h1이 분할되지 않은 상태로 표시된 Chrome DevTools의 스크린샷
사용자가 모션에 문제가 없음. 텍스트가 여러 <span> 요소로 분할됨

모션 조건문 준비 중

편리하게 사용할 수 있는 @media (prefers-reduced-motion: reduce) 미디어 쿼리는 이 프로젝트의 CSS 및 JavaScript에서 사용됩니다. 이 미디어 쿼리는 텍스트를 분할할지 여부를 결정하는 기본 조건부입니다. CSS 미디어 쿼리는 전환 및 애니메이션을 보류하는 데 사용되고 JavaScript 미디어 쿼리는 HTML 조작을 보류하는 데 사용됩니다.

CSS 조건부 준비

PostCSS를 사용하여 미디어 쿼리 불리언을 변수에 저장할 수 있는 미디어 쿼리 수준 5의 구문을 사용 설정했습니다.

@custom-media --motionOK (prefers-reduced-motion: no-preference);

JS 조건부 준비

JavaScript에서 브라우저는 미디어 쿼리를 확인하는 방법을 제공합니다. 여기에서는 디스트럭처링을 사용하여 미디어 쿼리 확인에서 불리언 결과를 추출하고 이름을 바꿨습니다.

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

그런 다음 motionOK를 테스트하고 사용자가 모션 감소를 요청하지 않은 경우에만 문서를 변경할 수 있습니다.

if (motionOK) {
  // document split manipulations
}

PostCSS를 사용하여 중첩 초안 1@nest 구문을 사용 설정하면 동일한 값을 확인할 수 있습니다. 이렇게 하면 애니메이션에 관한 모든 로직과 상위 요소 및 하위 요소의 스타일 요구사항을 한곳에서 저장할 수 있습니다.

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

PostCSS 맞춤 속성과 JavaScript 불리언을 사용하면 조건부로 효과를 업그레이드할 수 있습니다. 이제 다음 섹션으로 넘어가 문자열을 요소로 변환하는 JavaScript를 살펴봅니다.

텍스트 분할

텍스트 글자, 단어, 줄 등은 CSS 또는 JS로 개별적으로 애니메이션을 적용할 수 없습니다. 효과를 얻으려면 상자가 필요합니다. 각 문자에 애니메이션을 적용하려면 각 문자가 요소여야 합니다. 각 단어에 애니메이션을 적용하려면 각 단어가 요소여야 합니다.

  1. 문자열을 요소로 분할하는 JavaScript 유틸리티 함수 만들기
  2. 이러한 유틸리티의 사용 조정

문자 분할 유틸리티 함수

문자열을 사용하고 배열의 각 문자를 반환하는 함수로 시작하는 것이 좋습니다.

export const byLetter = text =>
  [...text].map(span)

ES6의 스프레드 문법은 이 작업을 신속하게 처리하는 데 큰 도움이 되었습니다.

단어 분할 유틸리티 함수

문자 분할과 마찬가지로 이 함수는 문자열을 입력해서 각 단어를 배열로 반환합니다.

export const byWord = text =>
  text.split(' ').map(span)

JavaScript 문자열의 split() 메서드를 사용하면 자르려는 문자를 지정할 수 있습니다. 단어 사이의 구분을 나타내는 빈 공간을 전달했습니다.

상자 유틸리티 함수 만들기

이 효과를 사용하려면 각 문자에 상자가 필요하며 이러한 함수에서 map()span() 함수로 호출되고 있음을 확인할 수 있습니다. 다음은 span() 함수입니다.

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

--index라는 맞춤 속성이 배열 위치로 설정된다는 점에 유의해야 합니다. 문자 애니메이션의 상자가 있으면 좋지만 CSS에서 사용할 색인이 있으면 큰 영향을 미치는 작은 추가사항이 됩니다. 이 큰 영향에서 가장 주목할 만한 점은 놀라운 점입니다. --index를 사용하여 애니메이션을 오프셋하여 교차 모양을 만들 수 있습니다.

유틸리티 결론

완료된 splitting.js 모듈은 다음과 같습니다.

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

export const byLetter = text =>
  [...text].map(span)

export const byWord = text =>
  text.split(' ').map(span)

다음은 이러한 byLetter()byWord() 함수를 가져오고 사용하는 것입니다.

분할 조정

분할 유틸리티를 사용할 준비가 되었다면, 이를 모두 합치면 다음과 같은 이점이 있습니다.

  1. 분할할 요소 찾기
  2. 텍스트를 분할하고 텍스트를 HTML로 대체

그런 다음 CSS가 요소/상자에 애니메이션을 적용합니다.

요소 찾기

저는 속성과 값을 사용하여 원하는 애니메이션과 텍스트 분할 방법에 관한 정보를 저장하기로 했습니다. 이러한 선언적 옵션을 HTML에 배치하는 것이 좋았습니다. split-by 속성은 JavaScript에서 요소를 찾고 문자 또는 단어의 상자를 만드는 데 사용됩니다. letter-animation 또는 word-animation 속성은 CSS에서 요소 자식을 타겟팅하고 변환 및 애니메이션을 적용하는 데 사용됩니다.

다음은 두 속성을 보여주는 HTML 샘플입니다.

<h1 split-by="letter" letter-animation="breath">animated letters</h1>
<h1 split-by="word" word-animation="trampoline">hover the words</h1>

JavaScript에서 요소 찾기

속성 존재에 관한 CSS 선택자 문법을 사용하여 텍스트를 분할할 요소 목록을 수집했습니다.

const splitTargets = document.querySelectorAll('[split-by]')

CSS에서 요소 찾기

또한 CSS에서 속성 존재 선택기를 사용하여 모든 문자 애니메이션에 동일한 기본 스타일을 지정했습니다. 나중에 이 속성 값을 사용하여 더 구체적인 스타일을 추가하여 효과를 달성합니다.

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

텍스트를 제자리에서 분할

JavaScript에서 찾은 각 분할 타겟의 경우 속성 값에 따라 텍스트를 분할하고 각 문자열을 <span>에 매핑합니다. 그런 다음 요소의 텍스트를 만든 상자로 바꿀 수 있습니다.

splitTargets.forEach(node => {
  const type = node.getAttribute('split-by')
  let nodes = null

  if (type === 'letter') {
    nodes = byLetter(node.innerText)
  }
  else if (type === 'word') {
    nodes = byWord(node.innerText)
  }

  if (nodes) {
    node.firstChild.replaceWith(...nodes)
  }
})

조정 종료

완료 시 index.js:

import {byLetter, byWord} from './splitting.js'

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

if (motionOK) {
  const splitTargets = document.querySelectorAll('[split-by]')

  splitTargets.forEach(node => {
    const type = node.getAttribute('split-by')
    let nodes = null

    if (type === 'letter')
      nodes = byLetter(node.innerText)
    else if (type === 'word')
      nodes = byWord(node.innerText)

    if (nodes)
      node.firstChild.replaceWith(...nodes)
  })
}

이 JavaScript는 다음 영어로 읽을 수 있습니다.

  1. 도우미 유틸리티 함수를 가져옵니다.
  2. 이 사용자에게 모션이 적합한지 확인합니다. 적합하지 않은 경우 아무것도 하지 않습니다.
  3. 분할하려는 각 요소
    1. 원하는 분할 방식에 따라 분할하세요.
    2. 텍스트를 요소로 대체합니다.

애니메이션 및 전환 분할

위의 분할 문서 조작을 통해 CSS 또는 JavaScript로 다양한 애니메이션과 효과를 사용할 수 있게 되었습니다. 이 도움말 하단에는 분할 가능성을 높이는 데 도움이 되는 몇 가지 링크가 있습니다.

이제 이 기능으로 무엇을 할 수 있는지 보여주세요. CSS 기반 애니메이션과 전환 4가지를 공유하겠습니다. 🤓

문자 분할

분할 문자 효과의 기초로 다음 CSS가 유용하다는 것을 알았습니다. 모든 전환과 애니메이션을 모션 미디어 쿼리 뒤에 배치한 다음 각 새 하위 문자 span에 디스플레이 속성과 공백 처리 스타일을 지정합니다.

[letter-animation] > span {
  display: inline-block;
  white-space: break-spaces;
}

공백 스타일은 공백으로만 이루어진 스팬이 레이아웃 엔진에 의해 축소되지 않도록 하는 데 중요합니다. 이제 상태 저장 기능을 살펴보겠습니다.

전환 분할 문자 예시

이 예에서는 CSS 전환을 사용하여 텍스트 분할 효과를 적용합니다. 전환을 사용하려면 엔진이 애니메이션을 적용할 상태가 필요합니다. 저는 마우스 오버 없음, 문장 내 마우스 오버, 문자 위 마우스 오버라는 세 가지 상태를 선택했습니다.

사용자가 문장(즉, 컨테이너) 위로 마우스를 가져가면 사용자가 더 멀리 밀어낸 것처럼 모든 하위 요소의 크기를 줄입니다. 그런 다음 사용자가 문자를 가리키면 앞으로 가져옵니다.

@media (--motionOK) {
  [letter-animation="hover"] {
    &:hover > span {
      transform: scale(.75);
    }

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:hover {
        transform: scale(1.25);
      }
    }
  }
}

분할된 문자 애니메이션 예

이 예에서는 사전 정의된 @keyframe 애니메이션을 사용하여 각 문자를 무한대로 애니메이션 처리하고 인라인 맞춤 속성 색인을 활용하여 지연 효과를 만듭니다.

@media (--motionOK) {
  [letter-animation="breath"] > span {
    animation:
      breath 1200ms ease
      calc(var(--index) * 100 * 1ms)
      infinite alternate;
  }
}

@keyframes breath {
  from {
    animation-timing-function: ease-out;
  }
  to {
    transform: translateY(-5px) scale(1.25);
    text-shadow: 0 0 25px var(--glow-color);
    animation-timing-function: ease-in-out;
  }
}

단어 분할

이 예시에서는 Flexbox가 컨테이너 유형으로 작동하여 ch 단위를 적절한 간격 길이로 활용했습니다.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
단어 사이의 간격을 보여주는 Flexbox devtools

단어 전환 예시

이 전환 예에서는 마우스 오버를 다시 사용합니다. 효과는 처음에는 마우스 오버할 때까지 콘텐츠를 숨기므로 기기에 마우스 오버 기능이 있는 경우에만 상호작용과 스타일이 적용되도록 했습니다.

@media (hover) {
  [word-animation="hover"] {
    overflow: hidden;
    overflow: clip;

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:not(:hover) {
        transform: translateY(50%);
      }
    }
  }
}

단어 분할 애니메이션 예

이 애니메이션 예에서는 CSS @keyframes를 다시 사용하여 텍스트의 일반 단락에 지그재그형의 무한 애니메이션을 만듭니다.

[word-animation="trampoline"] > span {
  display: inline-block;
  transform: translateY(100%);
  animation:
    trampoline 3s ease
    calc(var(--index) * 150 * 1ms)
    infinite alternate;
}

@keyframes trampoline {
  0% {
    transform: translateY(100%);
    animation-timing-function: ease-out;
  }
  50% {
    transform: translateY(0);
    animation-timing-function: ease-in;
  }
}

결론

이제 제가 어떻게 했는지 알았으니 어떻게 하시겠어요? 🙂

접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. Codepen을 만들거나 자체 데모를 호스팅한 후 트윗으로 보내주시면 아래의 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.

소스

더 많은 데모 및 아이디어

커뮤니티 리믹스