자바스크립트

Motion으로 splitText 구현하기

Motion 라이브러리를 이용해 splitText 애니메이션 구현하고 정리하기|7 days ago

splitText 애니메이션

framer motion에 대해서 아는 바는 없지만 이 라이브러리가 motion으로 이전된 것 같다. React/Vue용 라이브러리 및 범용 바닐라 자바스크립트를 지원한다. 여러 트랜지션 및 애니메이션에 대한 유틸리티/컴포넌트를 제공하고, CSS로 구현하기 귀찮은 여러 부분에 대해 알아서 처리해주는 유틸리티가 존재한다.

사용자의 제스쳐에 관련된 컨트롤도 가능하다는 것이 특장점 중 하나이다. 예를 들어, 사용자가 스크롤 할 때, 컨텐츠에 마우스를 올렸을 때, Viewport에 컨텐츠가 들어왔을 때 등 사용자와의 Interaction 부분도 다양하게 관리할 수 있다.

유틸리티 기능 중 splitText라는 기능이 있는데, 텍스트가 점진적으로 나타나는 효과를 주며 모던하고 깔끔한 느낌을 주는 유틸리티 기능이다. 해당 기능이 유료 버전에 한해서만 지원하는 것 같아, 직접 구현해보고 정리한다.

Idea

글자의 철자 분리를 통해 opacity 속성과 x, y 속성을 조작하면 쉽게 구현 가능해보였다.

function splitText(container, text) {}

철자 분리 함수의 시그니처는 이 정도, 글자들을 포함하고 있는 엘리먼트(container)와 텍스트 애니메이션 적용 대상인 텍스트를 인자로 받는다.

function splitText(container, text) {
  const lines = text.split('\n')
  const textEls = []

  for (const line of lines) {
    const lineEl = document.createElement('p')
    let temp = ''

    function push(pushBlank = false) {
      const textEl = document.createElement('span')
      textEl.textContent = temp
      temp = ''
      lineEl.appendChild(textEl)
      textEls.push(textEl)
      if (pushBlank) {
        const blank = document.createTextNode(' ')
        lineEl.appendChild(blank)
      }
    }

    for (const char of line) {
      if (char === ' ') {
        push(true)
        continue
      }
      temp += char
    }

    if (temp) push(false)
    container.appendChild(lineEl)
  }

  return textEls
}

텍스트는 ABC ABC ABC\nABCD ABCDE ABC 형태로 받고, 줄바꿈 문자 단위로 라인별 문자열을 배열에 담고 있다가, 라인별로 단어 사이를 잘라 단어 단위로 애니메이션 적용을 하는 걸로 선회했다. (철자 단위 애니메이션은 뭔가 조잡하고 내용이 애니메이션에 치우친 느낌이라 전달하고자 하는 내용이 확실히 전달되지 못하는 느낌이 든다. 음... 내용을 깔끔하고 모던하게 전달하는게 아니라, 내가 만든 애니메이션을 한번 볼래? 라는 느낌?) 그리고 단어 단위 엘리먼트를 만들어 라인 단위 엘리먼트 하위에 넣는다. 이러면 단어 단위의 엘리먼트에 애니메이션을 적용하면 문서에 있는 애니메이션과 비슷한 느낌을 구현할 수 있을 것 같다.

const text =
  'Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. Vestibulum imperdiet mauris\nornare turpis semper, non egestas leo ultricies.\nPraesent sed laoreet ex. In eget orci arcu.'
const { animate, stagger } = Motion

function animateText() {
  return animate(splitText(document.querySelector('.container'), text))
}

애니메이션 실행 함수까지 만들어주면 준비는 끝났다. stagger 메서드는 앞서 말했듯 애니메이션 대상이 복수개인 경우 상대적인 딜레이(앞 애니메이션이 실행되고 지연 시간)를 할당할 수 있게 해주는 유틸리티다. 이 기능이 해당 이펙트의 핵심 유틸리티다.

애니메이션 심화

inView 유틸리티는 HTML Intersection API를 활용해 애니메이션 대상이 viewport에 진입했는지 여부에 따라 애니메이션 실행 여부 결정을 관리할 수 있다. 제작한 애니메이션에 이 유틸리티를 적용해본다.

const { motion, stagger, inView } = Motion

inView(document.querySelector('.container'), el => {
  animateText()

  return leaving => {
    animate(document.querySelector('.container'), { opacity: 0 })
  }
})

첫번째 인자로 애니메이션 대상을 넘기고, 두번째 인자로 콜백 함수를 넘기면 엘리먼트가 viewport에 진입했을 때 애니메이션이 수행된다. 그리고, viewport에서 엘리먼트가 벗어나면 리턴한 콜백 함수가 실행된다. 즉, 여기서 viewport 벗어남 애니메이션을 수행하면 적절하다.

See the Pen splitText by n2ptune (@n2ptune) on CodePen.

새 탭을 열어서 확인

글씨 크기가 커서 작은 화면에서는 이상하게 보이는데, 큰 화면에서는 제대로 보인다. 얼추 예상한 것과 비슷하게 동작하므로 만족한다. 이 구현 과정을 통해 motion 라이브러리의 각각의 애니메이션 대상에 상대적인 딜레이를 넣을 수 있는 유틸리티인 stagger에 대해서 알게되었고, inView 메서드를 통해 Intersection API를 쉽게 활용할 수 있는 방법도 알게 되었다. 그리고 심화 과정을 통해, stagger의 딜레이를 조정하면 좀 더 다양한 애니메이션을 구현할 수 있는 걸 알게 되었다.

자바스크립트

간단한 자바스크립트 배열 원소 정렬

자바스크립트 기본 제공 함수를 이용해 배열 내 원소를 정렬하는 방법|5 years ago

일반적인 배열 정렬

const arr = ['c', 'a', 'b', 1]

for (const i of arr) {
  console.log(i.toString().charCodeAt())
}

// output: 99
//         97
//         98
//         49

문자열과 숫자로 이루어진 간단한 배열이 있다. 배열의 원소들을 모두 읽어들여 문자열로 변환한 뒤 해당 문자의 UTF-16 코드를 출력한다. Array.prototype.sort 함수는 매개변수로 정렬 방식을 정하지 않으면 기본적으로 이 코드를 기준으로 정렬한다.

const arr = ['c', 'a', 'b', 1, '', '', '']

for (const i of arr) {
  console.log(i.toString().charCodeAt())
}

arr.sort()

console.log(...arr)

/** result
 * 99 (c)
 * 97 (a)
 * 98 (b)
 * 49 (1)
 * 45208 (나)
 * 44032 (가)
 * 45796 (다)
 * 1 'a' 'b' 'c' '가' '나' '다'
 **/

배열 원소에 숫자, 영어 알파벳, 한글이 있다. 각각 UTF-16 코드로 해당 숫자를 가지므로 이 숫자를 기준으로 정렬하게 되면 1, 'a', 'b', 'c', '가', '나', '다' 순이 된다. 이 함수는 기본적으로 이 함수를 호출하는 배열 자체를 바꾼다. 반환 값도 정렬 된 배열을 반환한다.

const numArr = [1, 100000, 99]
numArr.sort()

console.log(...numArr)

// 1 100000 99

문제점이 하나 존재한다. 배열 정렬 함수를 지정하지 않으면 배열의 모든 원소를 문자열로 바꿔 정렬을 시도한다. 그렇기 때문에 위의 배열에서 기대하는 값 1, 99, 100000은 출력되지 않는다.

정렬 함수 지정

위의 문제를 해결하기 위해서 정렬 함수를 지정한다. Array.prototype.sort 함수는 매개변수로 2개의 매개변수(여기서는 a,b)를 갖는 함수를 받으며 이 함수에서 양수를 반환하면 b가 더 낮은 값으로 정렬, 음수를 반환하면 a가 더 낮은 값으로 정렬 됨.

만약 이 함수가 0을 반환하면 a, b 두 값에 대해 변경하지 않고 다음 요소로 넘어감 이 정렬 함수 방식을 이용해서 위의 숫자 정렬을 다시 해보면 아래와 같이 된다.

const numArr = [1, 100000, 99]
numArr.sort((a, b) => (a > b ? 1 : -1))

console.log(...numArr)

// 1 99 100000

수가 오름차순으로 정렬된다. 반환하는 수를 거꾸로하면(부등호를 반대로 하면) 내림차순으로 정렬된다. 배열의 모든 원소가 사칙연산이 가능하다면 아래와 같은 방식도 가능하다.

const numArr = [1, 100000, 99]
numArr.sort((a, b) => a - b)

console.log(...numArr)

// 1 99 100000

반대로 더한 값을 반환하면 내림차순으로 정렬한다.

객체 정렬

배열 안의 모든 원소가 객체일 경우 객체의 어떤 값을 기준으로 정렬이 가능하다.

const personArr = [
  {
    name: 'Kim',
    age: 30,
    job: 'Teacher'
  },
  {
    name: 'Lee',
    age: 24,
    job: 'Student'
  },
  {
    name: 'Hwang',
    age: 35,
    job: 'Zookeeper'
  }
]

위처럼 객체가 3개 들어있는 배열이 있다. 이 배열의 객체 값들 중 나이순으로 이들을 정렬하고 싶다고 하면 아래와 같이 a, b 객체의 age 속성을 서로 비교하면 된다.

personArr.sort((a, b) => a.age - b.age)

console.log(...personArr)

// { name: 'Lee', age: 24, job: 'Student' } { name: 'Kim', age: 30, job: 'Teacher' } { name: 'Hwang', age: 35, job: 'Zookeeper' }

참고

Copyright © 2025 imkh.dev