Uma visão geral básica de como criar animações de letras e palavras divididas.
Nesta postagem, quero compartilhar ideias sobre como resolver animações e interações de texto dividido para a Web que sejam mínimas, acessíveis e funcionem em todos os navegadores. Teste a demonstração.
Se preferir vídeo, confira uma versão desta postagem no YouTube:
Visão geral
As animações de texto dividido podem ser incríveis. Nesta postagem, vamos apenas arranhar a superfície do potencial de animação, mas ela oferece uma base para construir. O objetivo é animar progressivamente. O texto precisa estar legível por padrão, com a animação criada em cima. Os efeitos de movimento de texto dividido podem ficar extravagantes e potencialmente disruptivos. Por isso, só vamos manipular HTML ou aplicar estilos de movimento se o usuário concordar com isso.
Confira uma visão geral do fluxo de trabalho e dos resultados:
- Prepare variáveis condicionais de movimento reduzido para CSS e JS.
 - Prepare utilitários de texto dividido em JavaScript.
 - Orquestre as condições e os utilitários no carregamento da página.
 - Escreva transições e animações CSS para letras e palavras (a parte radical!).
 
Confira uma prévia dos resultados condicionais que queremos:
  Se um usuário preferir movimento reduzido, deixaremos o documento HTML intacto e não faremos animação. Se o movimento estiver OK, vamos em frente e dividimos em partes. Confira uma prévia do HTML depois que o JavaScript divide o texto por letra.
  Como preparar condicionais de movimento
A consulta de mídia disponível @media
(prefers-reduced-motion: reduce) será usada em CSS e
JavaScript neste projeto. Essa consulta de mídia é nossa principal condição para decidir se o texto será dividido 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 de HTML.
Como preparar a condição do CSS
Usei o PostCSS para ativar a sintaxe de 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 condição 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)'
)
Em seguida, posso testar motionOK e mudar o documento apenas se o usuário não tiver
solicitado a redução do movimento.
if (motionOK) {
  // document split manipulations
}
Posso verificar o mesmo valor usando o PostCSS para ativar a sintaxe @nest do
Nesting Draft 1. Isso me permite
armazenar toda a lógica sobre a animação e os requisitos de estilo para o
elemento pai e os filhos em um só lugar:
letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}
Com a propriedade personalizada do PostCSS e um booleano JavaScript, estamos prontos para atualizar o efeito condicionalmente. Isso nos leva à próxima seção, em que detalho o JavaScript para transformar strings em elementos.
Dividir texto
Letras, palavras, linhas de texto etc. não podem ser animados individualmente com CSS ou JS. Para conseguir o efeito, precisamos de caixas. Se quisermos animar cada letra, cada uma delas precisa ser um elemento. Se quisermos animar cada palavra, cada uma delas precisa ser um elemento.
- Criar funções utilitárias do JavaScript para dividir strings em elementos
 - Orquestrar o uso desses utilitários
 
Função utilitária para dividir letras
Um bom ponto de partida é uma função que recebe uma string e retorna cada letra em uma matriz.
export const byLetter = text =>
  [...text].map(span)
A sintaxe de propagação do ES6 ajudou muito a tornar essa tarefa rápida.
Função utilitária para dividir palavras
Semelhante à divisão de letras, essa função recebe 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 em quais caracteres fazer o corte.
Passei um espaço vazio, indicando uma divisão entre as palavras.
Função utilitária para criar caixas
O efeito exige caixas para cada letra, e vemos nessas funções que
map()
está sendo chamado com uma função span(). Esta é 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 em CSS é uma adição aparentemente pequena com um grande impacto.
O mais notável nesse grande impacto é o escalonamento.
Vamos usar --index como uma forma de compensar animações para uma aparência
escalonada.
Conclusão dos utilitários
O módulo splitting.js 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, importe e use essas funções byLetter() e byWord().
Orquestração de divisão
Com as utilidades de divisão prontas para uso, juntar tudo significa:
- Encontrar quais elementos dividir
 - Dividir e substituir texto por HTML
 
Depois disso, o CSS assume o controle e anima os elementos / caixas.
Como 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 segmentar elementos filhos e aplicar transformações e animações.
Confira 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>
Como encontrar elementos com JavaScript
Usei a sintaxe do seletor de CSS para presença de atributo e coletei a lista de elementos que querem dividir o texto:
const splitTargets = document.querySelectorAll('[split-by]')
Como encontrar elementos do CSS
Também usei o seletor de presença de atributo em CSS para dar a todas as animações de letras os mesmos estilos básicos. Mais tarde, vamos usar o valor do atributo para adicionar estilos mais específicos e alcançar um efeito.
letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}
Dividir texto no lugar
Para cada um dos destinos de divisão encontrados em JavaScript, vamos dividir o texto com base no valor do atributo e mapear cada string para um <span>. Em seguida, podemos substituir o texto do elemento pelas caixas que criamos:
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 em conclusão:
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 da seguinte forma em inglês:
- Importe algumas funções utilitárias auxiliares.
 - Verifique se o movimento está adequado para esse usuário. Se não estiver, não faça nada.
 - Para cada elemento que você quer dividir.
- Divida-os de acordo com a preferência de cada um.
 - Substituir texto por elementos.
 
 
Dividir animações e transições
A manipulação de divisão de documentos acima acabou de desbloquear uma infinidade de animações e efeitos potenciais com CSS ou JavaScript. Confira alguns links na parte de baixo deste artigo para ajudar você a dividir seu conteúdo.
É hora de mostrar o que você pode fazer com isso! Vou compartilhar quatro animações e transições baseadas em CSS. 🤓
Dividir letras
Como base para os efeitos de letras divididas, achei o seguinte CSS útil. Coloco todas as transições e animações atrás da consulta de mídia de movimento e dou a cada nova letra filha span uma propriedade de exibição mais um estilo para o que fazer com espaços em branco:
[letter-animation] > span {
  display: inline-block;
  white-space: break-spaces;
}
O estilo de espaços em branco é importante para que os intervalos que são apenas um espaço não sejam recolhidos pelo mecanismo de layout. Agora, vamos para a parte divertida com estado.
Exemplo de letras divididas em transição
Este exemplo usa transições CSS para o efeito de texto dividido. Com as transições, precisamos de estados para que o mecanismo faça a animação entre eles. Escolhi três estados: sem passar o cursor, passar o cursor em uma frase e passar o cursor em uma letra.
Quando o usuário passa o cursor sobre a frase, também conhecida como contêiner, eu reduzo todos os elementos filhos como se o usuário os tivesse afastado. Em seguida, quando o usuário passa o cursor sobre uma letra, ela é trazida para a 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 para mim nesses exemplos, aproveitando bem a unidade ch como um comprimento de lacuna adequado.
word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
Exemplo de palavras divididas na transição
Neste exemplo de transição, uso o passar o cursor novamente. Como o efeito inicialmente oculta o conteúdo até passar o cursor, garanti que a interação e os estilos só fossem aplicados se o dispositivo tivesse a capacidade de 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 palavras divididas animadas
Neste exemplo de animação, uso o CSS @keyframes novamente para criar uma animação infinita
escalonada em um parágrafo de texto normal.
[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ê faria? 🙂
Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Codepen ou hospede sua própria demonstração, me envie um tweet com ela e eu vou adicionar à seção de remixes da comunidade abaixo.
Origem
Mais demonstrações e inspiração
Remixes da comunidade
- Componente da Web 
<text-hover>de gnehcwu no CodeSandbox