Cómo crear animaciones de texto dividido

Una descripción general fundamental de cómo crear animaciones de letras y palabras divididas.

En esta publicación, quiero compartir mis ideas sobre cómo resolver animaciones y interacciones de texto divididas para la Web que sean minimalistas, accesibles y funcionen en todos los navegadores. Prueba la demostración.

Demo

Si prefieres ver un video, aquí tienes una versión de esta publicación en YouTube:

Descripción general

Las animaciones de texto dividido pueden ser increíbles. En esta publicación, apenas haremos un repaso del potencial de la animación, pero proporciona una base sobre la que construir. El objetivo es animar de forma progresiva. El texto debe ser legible de forma predeterminada, con la animación integrada en la parte superior. Los efectos de movimiento de texto dividido pueden ser extravagantes y potencialmente disruptivos, por lo que solo manipularemos el HTML o aplicaremos estilos de movimiento si el usuario está de acuerdo con el movimiento.

Esta es una descripción general del flujo de trabajo y los resultados:

  1. Prepara las variables condicionales de movimiento reducido para CSS y JS.
  2. Prepara las utilidades de texto dividido en JavaScript.
  3. Organiza las condiciones y las utilidades cuando se carga la página.
  4. Escribe transiciones y animaciones de CSS para letras y palabras (la parte genial).

Esta es una vista previa de los resultados condicionales que buscamos:

Captura de pantalla de las herramientas para desarrolladores de Chrome con el panel Elementos abierto y el movimiento reducido establecido en "Reducir", y el h1 se muestra sin dividir.
El usuario prefiere reducir el movimiento: el texto es legible o no está dividido.

Si un usuario prefiere reducir el movimiento, dejamos el documento HTML intacto y no realizamos ninguna animación. Si el movimiento está bien, podemos continuar y cortarlo en pedazos. Esta es una vista previa del código HTML después de que JavaScript haya dividido el texto por letra.

Captura de pantalla de las herramientas para desarrolladores de Chrome con el panel Elementos abierto y el movimiento reducido establecido en "Reducir", y el h1 se muestra sin dividir.
El usuario está de acuerdo con el movimiento; el texto se divide en varios elementos <span>

Prepara condiciones de movimiento

La consulta de medios @media (prefers-reduced-motion: reduce) disponible de forma conveniente se usará desde CSS y JavaScript en este proyecto. Esta consulta de medios es nuestra condición principal para decidir si dividir el texto o no. La consulta de medios de CSS se usará para retener las transiciones y las animaciones, mientras que la consulta de medios de JavaScript se usará para retener la manipulación de HTML.

Prepara el condicional de CSS

Usé PostCSS para habilitar la sintaxis del nivel 5 de las consultas de medios, en el que puedo almacenar un valor booleano de consulta de medios en una variable:

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

Prepara el condicional de JS

En JavaScript, el navegador proporciona una forma de verificar las consultas multimedia. Usé la desestructuración para extraer y cambiar el nombre del resultado booleano de la verificación de la consulta multimedia:

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

Luego, puedo probar motionOK y solo cambiar el documento si el usuario no solicitó reducir el movimiento.

if (motionOK) {
  // document split manipulations
}

Puedo verificar el mismo valor con PostCSS para habilitar la sintaxis @nest del borrador de anidación 1. Esto me permite almacenar toda la lógica sobre la animación y sus requisitos de estilo para el elemento superior y los secundarios en un solo lugar:

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

Con la propiedad personalizada de PostCSS y un valor booleano de JavaScript, ya podemos actualizar el efecto de forma condicional. Eso nos lleva a la siguiente sección, en la que analizo el código JavaScript para transformar cadenas en elementos.

Cómo dividir texto

Las letras, palabras, líneas, etc., de texto no se pueden animar de forma individual con CSS o JS. Para lograr el efecto, necesitamos cuadros. Si queremos animar cada letra, cada letra debe ser un elemento. Si queremos animar cada palabra, cada una de ellas debe ser un elemento.

  1. Crea funciones de utilidad de JavaScript para dividir cadenas en elementos
  2. Orquestar el uso de estas utilidades

Función de utilidad para dividir letras

Una forma divertida de comenzar es con una función que tome una cadena y muestre cada letra en un array.

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

La sintaxis de propagación de ES6 realmente ayudó a que esa tarea fuera rápida.

Función de utilidad para dividir palabras

Al igual que dividir letras, esta función toma una cadena y muestra cada palabra en un array.

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

El método split() en las cadenas de JavaScript nos permite especificar en qué caracteres cortar. Pasé un espacio vacío, lo que indica una división entre palabras.

Cómo hacer que las cajas funcionen como utilidad

El efecto requiere cuadros para cada letra, y vemos en esas funciones que se llama a map() con una función span(). Esta es la función span().

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

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

  return node
}

Es fundamental tener en cuenta que se está configurando una propiedad personalizada llamada --index con la posición del array. Tener los cuadros para las animaciones de letras es genial, pero tener un índice para usar en CSS es una adición aparentemente pequeña con un gran impacto. Lo más notable de este gran impacto es que es desfasado. Podremos usar --index como una forma de compensar animaciones para lograr un aspecto escalonado.

Conclusión de las utilidades

El módulo splitting.js completo:

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)

A continuación, importaremos y usaremos estas funciones byLetter() y byWord().

Organización dividida

Con las utilidades de división listas para usar, poner todo en orden significa lo siguiente:

  1. Cómo encontrar los elementos que se deben dividir
  2. Dividiéndolos y reemplazando el texto con HTML

Después de eso, el CSS se hará cargo y animará los elementos o cuadros.

Cómo encontrar elementos

Elegí usar atributos y valores para almacenar información sobre la animación deseada y cómo dividir el texto. Me gustó colocar estas opciones declarativas en el HTML. El atributo split-by se usa desde JavaScript para encontrar elementos y crear cuadros para letras o palabras. El atributo letter-animation o word-animation se usa desde CSS para orientarse a elementos secundarios y aplicar transformaciones y animaciones.

Este es un ejemplo de HTML que demuestra los dos atributos:

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

Cómo encontrar elementos desde JavaScript

Usé la sintaxis del selector CSS para la presencia de atributos para recopilar la lista de elementos que desean que se divida su texto:

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

Cómo encontrar elementos desde CSS

También usé el selector de presencia de atributos en CSS para darles a todas las animaciones de letras los mismos estilos básicos. Más adelante, usaremos el valor del atributo para agregar estilos más específicos y lograr un efecto.

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

Cómo dividir el texto en su lugar

Para cada uno de los objetivos divididos que encontramos en JavaScript, dividiremos su texto según el valor del atributo y asignaremos cada cadena a un <span>. Luego, podemos reemplazar el texto del elemento por los cuadros que hicimos:

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

Conclusión de la organización

index.js en finalización:

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

El código JavaScript se podría leer en el siguiente inglés:

  1. Importa algunas funciones de utilidad auxiliares.
  2. Verifica si el movimiento es adecuado para este usuario. De lo contrario, no hagas nada.
  3. Para cada elemento que se quiere dividir.
    1. Dividirlos según cómo quieran dividirlos
    2. Reemplaza el texto por elementos.

Cómo dividir animaciones y transiciones

La manipulación de documentos de división anterior acaba de desbloquear una gran cantidad de animaciones y efectos potenciales con CSS o JavaScript. En la parte inferior de este artículo, encontrarás algunos vínculos que te ayudarán a inspirar tu potencial de división.

Es hora de mostrar lo que puedes hacer con esto. Compartiré 4 animaciones y transiciones impulsadas por CSS. 🤓

Cómo dividir letras

Como base para los efectos de letras divididas, el siguiente CSS me resultó útil. Puse todas las transiciones y animaciones detrás de la consulta de medios de movimiento y, luego, le di a cada letra secundaria nueva span una propiedad de visualización y un estilo para lo que se debe hacer con los espacios en blanco:

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

El estilo de los espacios en blanco es importante para que el motor de diseño no contraiga los tramos que son solo un espacio. Ahora, vamos a lo divertido con el estado.

Ejemplo de letras divididas en la transición

En este ejemplo, se usan transiciones de CSS para el efecto de texto dividido. Con las transiciones, necesitamos estados para que el motor anime entre ellos, y elegí tres estados: sin desplazamiento del mouse, desplazamiento del mouse en una oración y desplazamiento del mouse sobre una letra.

Cuando el usuario coloca el cursor sobre la oración, también conocida como contenedor, reduzco todos los elementos secundarios como si el usuario los hubiera alejado. Luego, cuando el usuario coloca el cursor sobre una letra, la muestro.

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

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

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

Ejemplo de animación de letras divididas

En este ejemplo, se usa una animación @keyframe predefinida para animar cada letra de forma infinita y se aprovecha el índice de propiedad personalizada intercalada para crear un efecto de espaciado.

@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 palabras

Flexbox funcionó como un tipo de contenedor para mí en estos ejemplos, aprovechando de manera óptima la unidad ch como una longitud de espacio adecuada.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
Herramientas para desarrolladores de Flexbox que muestran la brecha entre palabras

Ejemplo de palabras divididas de transición

En este ejemplo de transición, vuelvo a usar el desplazamiento del mouse. Como el efecto oculta el contenido inicialmente hasta que se coloca el cursor sobre él, me aseguré de que la interacción y los estilos solo se aplicaran si el dispositivo tenía la capacidad de colocar el cursor sobre él.

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

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

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

Ejemplo de animación de palabras divididas

En este ejemplo de animación, vuelvo a usar CSS @keyframes para crear una animación infinita intercalada en un párrafo 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;
  }
}

Conclusión

Ahora que sabes cómo lo hice, ¿cómo lo harías? 🙂

Diversifiquemos nuestros enfoques y aprendamos todas las formas de compilar en la Web. Crea una cuenta de Codepen o aloja tu propia demo, envíame un tweet con ella y la agregaré a la sección de remixes de la comunidad a continuación.

Fuente

Más demostraciones y fuentes de inspiración

Remixes de la comunidad