Como criar animações de texto dividido

Uma visão geral básica de como criar animações com letras divididas e animações de palavras.

Nesta postagem, quero compartilhar ideias sobre maneiras de resolver animações de texto dividido e interações para a Web que são mínimas, acessíveis e funcionam em vários navegadores. Teste a demonstração.

Demonstração

Se preferir vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Animações de texto dividido podem ser incríveis. Não vamos falar muito sobre o potencial da animação nesta postagem, mas essa é uma base de base. O objetivo é animar progressivamente. O texto precisa ser legível por padrão, com a animação criada na parte de cima. Os efeitos de movimento de texto dividido podem ficar extravagantes e possivelmente disruptivos. Portanto, só manipularemos o HTML ou aplicaremos estilos de movimento se o usuário concordar com os movimentos.

Esta é uma visão geral do fluxo de trabalho e dos resultados:

  1. Prepare variáveis condicionais de movimento reduzida para CSS e JS.
  2. Prepare os utilitários de texto divididos no JavaScript.
  3. Orquestre os condicionais e utilitários no carregamento da página.
  4. Escreva transições e animações CSS para letras e palavras (a parte mais ousada!).

Esta é uma visualização dos resultados condicionais que queremos:

captura de tela do Chrome DevTools com o painel Elements aberto e o movimento reduzido definido como "reduzir" e o H1 é mostrado sem divisão.
O usuário prefere movimento reduzido: o texto é legível / não dividido

Se um usuário preferir movimento reduzido, deixaremos o documento HTML em paz e não faremos nenhuma animação. Se o movimento estiver OK, vamos dividi-lo em partes. Esta é uma visualização do HTML depois que o JavaScript dividiu o texto por letra.

captura de tela do Chrome DevTools com o painel Elements aberto e o movimento reduzido definido como "reduzir" e o H1 é mostrado sem divisão.
O usuário aceita movimentos. Texto dividido em vários elementos <span>

Como preparar condicionais de movimento

A consulta de mídia @media (prefers-reduced-motion: reduce) convenientemente disponível será usada em CSS e JavaScript neste projeto. Essa consulta de mídia é nossa principal condicional para decidir se dividir o texto ou não. A consulta de mídia CSS será usada para reter transições e animações, enquanto a consulta de mídia JavaScript será usada para reter a manipulação HTML.

Como preparar a condicional do CSS

Usei PostCSS para ativar a sintaxe das Consultas de mídia nível 5, em que posso armazenar um booleano de consulta de mídia em uma variável:

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

Como preparar a condicional JS

Em JavaScript, o navegador oferece uma maneira de verificar consultas de mídia. Usei a desestruturação para extrair e renomear o resultado booleano da verificação de consulta de mídia:

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

Posso testar o motionOK e só mudar o documento se o usuário não pedir a redução do movimento.

if (motionOK) {
  // document split manipulations
}

Posso verificar o mesmo valor usando PostCSS para ativar a sintaxe @nest do Resumo de aninhamento 1. Isso me permite armazenar toda a lógica sobre a animação e seus requisitos de estilo para os pais e filhos em um só lugar:

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

Com a propriedade personalizada PostCSS e um booleano JavaScript, estamos prontos para fazer upgrade condicional do efeito. Isso nos leva à próxima seção, em que vou dividir o JavaScript para transformar strings em elementos.

Como dividir o texto

Letras de texto, palavras, linhas etc. não podem ser animadas individualmente com CSS ou JS. Para conseguir o efeito, precisamos de caixas. Se quisermos animar cada letra, então cada letra precisa ser um elemento. Se quisermos animar cada palavra, cada palavra precisa ser um elemento.

  1. Criar funções utilitárias JavaScript para dividir strings em elementos
  2. Orquestrar o uso desses utilitários

Função utilitária de divisão de letras

Um bom lugar para começar é com uma função que usa uma string e retorna cada letra de uma matriz.

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

A sintaxe distribuição do ES6 realmente ajudou a tornar isso uma tarefa rápida.

Função utilitária de divisão de palavras

Semelhante à divisão de letras, essa função usa uma string e retorna cada palavra em uma matriz.

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

O método split() em strings JavaScript permite especificar quais caracteres serão divididos. Passei um espaço vazio, indicando uma divisão entre as palavras.

Como fazer caixas com função utilitária

O efeito exige caixas para cada letra. Observe nessas funções que map() está sendo chamado com uma função span(). Veja a função span().

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

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

  return node
}

É importante observar que uma propriedade personalizada chamada --index está sendo definida com a posição da matriz. Ter as caixas para as animações de letras é ótimo, mas ter um índice para usar no CSS é uma adição aparentemente pequena com um grande impacto. O mais notável nesse grande impacto é esmagador. Podemos usar --index como uma forma de deslocar as animações para uma aparência escalonada.

Conclusão sobre serviços públicos

O módulo splitting.js foi concluído:

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)

Em seguida, está importando e usando essas funções byLetter() e byWord().

Orquestração dividida

Com os utilitários de divisão prontos para uso, unir tudo significa:

  1. Como descobrir quais elementos dividir
  2. Dividindo-os e substituindo o texto por HTML

Depois disso, o CSS assume o controle e anima os elementos / caixas.

Encontrar elementos

Escolhi usar atributos e valores para armazenar informações sobre a animação desejada e como dividir o texto. Gostei de colocar essas opções declarativas no HTML. O atributo split-by é usado em JavaScript para encontrar elementos e criar caixas para letras ou palavras. O atributo letter-animation ou word-animation é usado no CSS para direcionar os filhos de elementos e aplicar transformações e animações.

Aqui está um exemplo de HTML que demonstra os dois atributos:

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

Encontrar elementos do JavaScript

Usei a sintaxe do seletor de CSS para presença de atributos e reunir a lista de elementos que querem dividir o texto:

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

Encontrar elementos de CSS

Também usei o seletor de presença de atributo no CSS para dar a todas as animações de letras os mesmos estilos de base. Mais tarde, vamos usar o valor do atributo para adicionar estilos mais específicos e criar um efeito.

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

Dividir o texto no lugar

Para cada um dos destinos de divisão encontrados em JavaScript, dividiremos o texto com base no valor do atributo e mapearemos cada string para um <span>. Em seguida, podemos substituir o texto do elemento pelas caixas que fizemos:

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)
  }
})

Conclusão da orquestração

index.js concluído:

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)
  })
}

O JavaScript pode ser lido no seguinte inglês:

  1. Importe algumas funções utilitárias auxiliares.
  2. Confira se o movimento está adequado para o usuário. Caso contrário, não faça nada.
  3. Para cada elemento que quer ser dividido.
    1. Divida-os com base em como eles querem fazer isso.
    2. Substituir o texto por elementos.

Como dividir animações e transições

A manipulação de documentos de divisão acima desbloqueou uma infinidade de possíveis animações e efeitos com CSS ou JavaScript. Há alguns links na parte inferior deste artigo para ajudar a inspirar seu potencial de divisão.

Hora de mostrar do que você é capaz! vou compartilhar quatro animações e transições orientadas por CSS. 🤓

Letras divididas

Como base para os efeitos de letras divididas, achei o CSS a seguir útil. Coloquei todas as transições e animações por trás da consulta de mídia de movimento e, em seguida, forneço a cada nova letra filha span uma propriedade de exibição e um estilo para o que fazer com os espaços em branco:

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

O estilo dos espaços em branco é importante para que os períodos, que são apenas um espaço, não sejam recolhidos pelo mecanismo de layout. Agora vamos para a diversão com estado.

Exemplo de letras divididas de transição

Este exemplo usa transições CSS para o efeito de texto dividido. Com as transições, precisamos de estados para a animação do mecanismo. Eu escolhi três estados: não passar o cursor, passar o cursor na frase, passar o cursor sobre uma letra.

Quando o usuário passa o cursor sobre a frase (também chamada de contêiner), redimensiono todos os filhos como se o usuário os tivesse afastado. Então, quando o usuário passa o cursor sobre uma letra, eu a levo para frente.

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

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

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

Exemplo de animação de letras divididas

Este exemplo usa uma animação @keyframe predefinida para animar infinitamente cada letra e aproveita o índice de propriedade personalizada inline para criar um efeito escalonado.

@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;
  }
}

Dividir palavras

O Flexbox funcionou como um tipo de contêiner nesses exemplos, aproveitando bem a unidade ch como um comprimento de intervalo saudável.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
Flexbox DevTools mostrando a lacuna entre as palavras

Exemplo de palavras divididas de transição

Neste exemplo de transição, vou usar o recurso de passar o cursor novamente. Como o efeito inicialmente oculta o conteúdo até passar o cursor, garantimos que a interação e os estilos sejam aplicados apenas se o dispositivo puder passar o cursor.

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

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

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

Exemplo de animação de palavras divididas

Neste exemplo de animação, uso o CSS @keyframes novamente para criar uma animação infinita escalonada em um parágrafo normal de texto.

[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;
  }
}

Conclusão

Agora que você sabe como eu fiz isso, como você iria?! 🙂

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Codepen ou apresente sua própria demonstração, envie um tweet para mim e vou adicioná-la à seção "Remixes da comunidade" abaixo.

Origem

Mais demonstrações e inspiração

Remixes da comunidade