Cuando Webflow adquirió GSAP, supimos que SplitText sería la base para los efectos de texto, así que decidimos reescribirlo por completo desde cero.
Para crear animaciones de texto atractivas donde las letras, palabras o líneas se muevan de forma independiente, tienes que construir manualmente la estructura HTML colocando cada letra, palabra o línea en su propio elemento, lo cual es bastante engorroso, o bien usar JavaScript para hacerlo de forma dinámica. SplitText es uno de los complementos más populares para GSAP y hace exactamente eso.
Comenzamos con un conjunto de objetivos ambiciosos:
- Reducir el tamaño del archivo a la mitad para minimizar los tiempos de carga
- Añadir accesibilidad para lectores de pantalla
- Añadir división responsiva para que, cuando las fuentes se carguen o el elemento cambie de tamaño y el texto se redistribuya en diferentes líneas, no se produzcan saltos de línea extraños.
- Permitir que un único elemento en línea (como <strong>) se fragmente para encajar dentro de múltiples elementos de línea.
- Añadir máscaras para efectos de revelado sencillos
- Permitir que ciertos elementos anidados sean inmunes a la división
- Mejorar la forma en que se dividen caracteres complejos como emojis y caracteres de idiomas extranjeros
- Hacer que la delimitación personalizada de palabras sea más flexible para adaptarse a casos especiales
- Hacer posible el uso de SplitText independientemente del núcleo de GSAP (de forma autónoma)
- Convertir a TypeScript
En este artículo, compartiré algunos de los desafíos técnicos, soluciones y lecciones aprendidas durante el proceso.
Precisión en la división de caracteres
A primera vista, dividir el texto en caracteres individuales puede parecer increíblemente sencillo. Por ejemplo, podrías intentar usar .split(""), lo cual funciona bien para letras simples:
"Easy".split(""); // ["E", "a", "s", "y"]Pero mira lo que sucede cuando caracteres especiales como los emojis se dividen de esta manera:
"💚".split(""); // ['\uD83D', '\uDC9A']¡Vaya! Ese emoji es en realidad una combinación de 2 caracteres. Y empeora:
"👨👨👦👦".split(""); // ['\uD83D', '\uDC68', '', '\uD83D', '\uDC68', '', '\uD83D', '\uDC66', '', '\uD83D', '\uDC66']¡Eso son 11 caracteres para un solo emoji! Hay ciertos caracteres de idiomas extranjeros que se comportan de forma similar.
Usar Array.from() (introducido en ES6) funciona con algunos emojis, pero no con los más complejos:
Array.from("💚").length; // 1
Array.from("👨👨👦👦").length; // 7La mejor solución es usar Intl.Segmenter():
[...new Intl.Segmenter().segment("👨👨👦👦")].map(s => s.segment); // ["👨👨👦👦"]Pero en navegadores antiguos que no admiten Intl.Segmenter(), se debe usar un split() basado en RegExp como alternativa.
Lección aprendida: una solución “obvia” puede fallar en casos excepcionales, así que realiza pruebas con escenarios complicados. Mantente abierto a nuevas API de navegador que puedan ser útiles, pero ten siempre una alternativa preparada cuando sea posible.
Manejo de elementos anidados
Considera un elemento <strong>...</strong> anidado que se divide en dos líneas…
<p id="target">
What if a <strong>nested element
wraps onto multiple lines?</strong>
</p>
…para envolver cada línea dentro de su propio <div>, debemos dividir ese <strong> en múltiples elementos (un <strong> para cada línea):
<p id="target">
<div class="line1">What if a <strong>nested element</strong></div>
<div class="line2"><strong>wraps onto multiple lines?</strong></div>
</p>Eso es lo que hace la función deepSlice en SplitText para cualquier elemento anidado. Cuando detecta una nueva línea mientras divide recursivamente el texto dentro de un elemento anidado, utiliza cloneNode() y reorganiza los contenidos.
Dado que esta función duplica y reestructura elementos anidados, podría afectar a los selectores CSS (como p strong:first-of-type), por lo que era importante ofrecer a los usuarios una forma de desactivarla (deepSlice: false). Y como uno de los objetivos clave era mantener el tamaño del archivo al mínimo, tuvimos que considerar si el coste en KB valía la pena. En este caso, así fue.
Conclusión: anticipa casos extremos complejos y ofrece soluciones que los usuarios puedan desactivar si prefieren no duplicar o reestructurar elementos del DOM. Y si la solución es demasiado costosa (en términos de tamaño de archivo o rendimiento), puede que no valga la pena.
Reducción del tamaño del archivo
Aquí tienes algunas de las estrategias utilizadas para reducir el tamaño del archivo en un 50%:
- Reemplacé una RegExp extremadamente larga para dividir emojis de forma segura por Intl.Segmenter() (para navegadores modernos) y una alternativa con una RegExp más sencilla para navegadores antiguos. Eliminé sin piedad las redundancias, integré la funcionalidad común en funciones auxiliares y revisé el código varias veces para que fuera lo más breve y eficiente posible. En lugar de concatenar todos los elementos en una cadena larga como "<div class='char'>S</div><div class='char'>p</div>..." que se inserta en .innerHTML, ahora manipula los elementos directamente, como document.createElement("div"). Esto requirió menos código y resultó más flexible. Eliminé algunas funciones que casi nadie usaba, como position: "absolute", que era una reliquia de la época en la que algunos navegadores no admitían la propiedad CSS transform.
Conclusión: a menudo se requieren varias pasadas por una base de código para reducir drásticamente el tamaño del archivo y hacerlo lo más eficiente posible. Sin embargo, algunas de esas eficiencias se logran a costa de la legibilidad del código, por lo que debes decidir qué es lo más importante en tu proyecto. En este caso, un tamaño de archivo reducido era mucho más importante que tener un código legible.
División automática
Si solo se dividen palabras y/o caracteres, estos se reorganizan de forma natural cuando el contenedor cambia de tamaño, pero si divides por líneas, cada elemento de línea encierra un conjunto específico de palabras o caracteres. Si el contenedor se vuelve más estrecho o si la fuente se carga después de la división, por ejemplo, el texto puede reorganizarse y hacer que algunas palabras pertenezcan a líneas diferentes (la última palabra de una línea puede pasar a la siguiente). La única forma de evitar saltos de línea extraños es volver a dividir (restaurar el innerHTML original y hacer que SplitText ejecute su lógica de división de nuevo) para que los elementos de línea encierren las palabras correctas.
Por eso, para SplitText, añadimos una función autoSplit: true que utiliza un detector de eventos "loadingdone" en document.fonts, así como un ResizeObserver en el elemento para revertir y volver a dividir automáticamente cuando ocurre cualquiera de estos eventos. Para maximizar el rendimiento, los eventos de cambio de ancho se limitan (debounced) para que la nueva división solo ocurra cuando se detengan durante 200 ms o más. Además, ignora los cambios de altura, ya que estos no provocan la reorganización del texto.
Conclusión: considera siempre qué sucederá cuando cambie el tamaño de la ventana y ofrece a los usuarios la capacidad de reaccionar a esos cambios de forma eficiente (por ejemplo, mediante el uso de debouncing).
Conversión a TypeScript
Esta fue mi primera vez programando un proyecto completamente en TypeScript. Entiendo por qué muchos desarrolladores aprecian las protecciones que ofrece y la mejora en las sugerencias de código. Hizo que el archivo fuera más extenso y tomó un poco más de tiempo, pero sigo inclinado a usar TypeScript más a menudo en el futuro. Gran parte de la motivación proviene del hecho de que el resto del equipo de Webflow también tiene una fuerte preferencia por TypeScript.
Honestamente, puedo ver argumentos válidos a favor y en contra del uso de TypeScript, pero los dos factores decisivos más importantes para mí son:
- Ayudó a identificar más rápidamente posibles errores en el código durante el desarrollo. Es difícil saber con certeza si ahorró más tiempo del que costó añadir toda la información de TypeScript, pero supongo que al menos en proyectos más grandes sí lo haría. Coherencia con las prácticas y preferencias del resto del equipo de Webflow. Históricamente, no he tenido que considerar trabajar en equipo, así que esto es nuevo para mí, pero parece importante tener prácticas cohesivas como equipo.
Conclusión
He omitido muchas de las complejidades en aras de la brevedad en este artículo, pero es difícil apreciar todos los pequeños inconvenientes que implica dividir eficazmente el texto dentro de un elemento en caracteres, palabras y líneas hasta que lo intentas tú mismo.
Si quieres ver cómo SplitText maneja la división en comparación con la alternativa más popular, aquí tienes una demostración: https://codepen.io/GreenSock/full/VYwPVoB
Debo admitir que fue muy satisfactorio reducir el tamaño del archivo en un 50% mientras añadía unas 14 funciones nuevas y convertía todo con éxito a TypeScript. SplitText ahora se siente listo para servir como una base sólida para las capacidades de animación de texto integradas de Webflow. Y lo que es aún más emocionante es que el plugin SplitText ahora es gratuito para todos, gracias a Webflow.
SplitText ahora sirve como base para las animaciones de texto en la nueva función de Interacciones con GSAP de Webflow. Esta actualización revolucionaria significa que Webflow carga SplitText de forma nativa, con GSAP impulsando todas las interacciones creadas en la elegante nueva interfaz de línea de tiempo horizontal. Y aquí está la mejor parte: SplitText es gratuito para usar fuera de Webflow, lo que brinda a los desarrolladores de todas partes acceso a las mismas herramientas de animación de texto de nivel profesional que ahora están integradas directamente en la plataforma de Webflow 🥳.

¡Estamos contratando!
Buscamos talento en producto e ingeniería para que se una a nuestra misión de poner superpoderes de desarrollo al alcance de todos.









