Создание анимации разделенного текста

Базовый обзор того, как создавать разделенную анимацию букв и слов.

В этом посте я хочу поделиться размышлениями о способах решения проблемы анимации разделенного текста и взаимодействия в Интернете, которые были бы минимальными, доступными и работали во всех браузерах. Попробуйте демо .

Демо

Если вы предпочитаете видео, вот версия этого поста на YouTube:

Обзор

Анимация разделенного текста может быть потрясающей. В этой статье мы лишь коснемся поверхности потенциала анимации, но она дает основу для дальнейшего развития. Цель состоит в том, чтобы анимировать постепенно. По умолчанию текст должен быть читаемым, а анимация должна быть поверх него. Эффекты движения разделенного текста могут оказаться экстравагантными и потенциально разрушительными, поэтому мы будем манипулировать HTML или применять стили движения только в том случае, если пользователя устраивает движение.

Вот общий обзор рабочего процесса и результатов:

  1. Подготовьте условные переменные уменьшенного движения для CSS и JS.
  2. Подготовьте утилиты разделения текста на JavaScript.
  3. Организуйте условия и утилиты при загрузке страницы.
  4. Напишите CSS-переходы и анимацию для букв и слов (самая крутая часть!).

Вот предварительный просмотр условных результатов, к которым мы стремимся:

снимок экрана инструментов разработчика Chrome с открытой панелью «Элементы» и уменьшенным движением, установленным на «уменьшение», а h1 отображается неразделенным
Пользователь предпочитает ограниченное движение: текст разборчивый/неразбитый

Если пользователь предпочитает ограниченное движение, мы оставляем HTML-документ в покое и не делаем анимацию. Если движение в порядке, мы идем дальше и разрезаем его на куски. Вот предварительный просмотр HTML после того, как JavaScript разделил текст по буквам.

снимок экрана инструментов разработчика Chrome с открытой панелью «Элементы» и уменьшенным движением, установленным на «уменьшение», а h1 отображается неразделенным
Пользователь в порядке с движением; текст разделен на несколько элементов <span>

Подготовка условий движения

В этом проекте из CSS и JavaScript будет использоваться удобный медиа-запрос @media (prefers-reduced-motion: reduce) . Этот медиа-запрос является нашим основным условием для принятия решения о разделении текста или нет. Медиа-запрос 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 для включения синтаксиса @nest из Nesting Draft 1 . Это позволяет мне хранить всю логику анимации и требования к ее стилю для родителя и дочерних элементов в одном месте:

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)

Метод split() для строк JavaScript позволяет нам указать, по каким символам следует разрезать. Я пропустил пустое место, обозначающее разделение между словами.

Создание полезной функции ящиков

Для эффекта требуются поля для каждой буквы, и в этих функциях мы видим, что 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. 🤓

Разделенные буквы

В качестве основы для эффектов разделенных букв я нашел следующий 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, показывающие пробел между словами

Пример разделения слов перехода

В этом примере перехода я снова использую наведение. Поскольку эффект изначально скрывает содержимое до наведения, я позаботился о том, чтобы взаимодействие и стили применялись только в том случае, если устройство имело возможность наведения.

@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 или разместите собственную демоверсию, напишите мне о ней в Твиттере, и я добавлю ее в раздел ремиксов сообщества ниже.

Источник

Больше демонстраций и вдохновения

Ремиксы сообщества