Tejiendo una línea a través del texto en CSS
A principios de este año, me encontré con esta demostración de Florin Pop, que hace que una línea pase por encima o por debajo de las letras de un encabezado de una sola línea. Pensé que era una buena idea, pero hubo algunas pequeñas cosas acerca de la implementación que sentí que podía simplificar y mejorar al mismo tiempo.
En primer lugar, la demostración original duplica el texto del título, lo que sabía que podría evitarse fácilmente. Luego está el hecho de que la longitud de la línea que atraviesa el texto es un número mágico, lo cual no es un enfoque muy flexible. Y finalmente, ¿no podemos deshacernos de JavaScript?
Así que echamos un vistazo a dónde terminé tomando esto.
estructura HTML
Florin coloca el texto en un elemento de encabezado y luego duplica este encabezado, usando Splitting.js para reemplazar el contenido del texto del encabezado duplicado con intervalos, cada uno de los cuales contiene una letra del texto original.
Habiendo decidido hacer esto sin duplicación de texto, usar una biblioteca para dividir el texto en caracteres y luego poner cada uno en una span
sensación un poco exagerada, así que lo estamos haciendo todo con un preprocesador HTML.
- let text = 'We Love to Play';- let arr = text.split('');h1(role='image' aria-label=text) - arr.forEach(letter = { span.letter #{letter} - });
Dado que dividir texto en Múltiples elementos puede no funcionar bien con lectores de pantalla, le hemos dado a todo un de role
y image
un aria-label
.
Esto genera el siguiente HTML:
h1 role="image" aria-label="We Love to Play" spanW/span spane/span span /span spanL/span spano/span spanv/span spane/span span /span spant/span spano/span span /span spanP/span spanl/span spana/span spany/span/h1
Estilos básicos
Colocamos el encabezado en medio de su padre ( body
en este caso) usando un grid
diseño:
body { display: grid; place-content: center;}
También podemos añadir algunos toques bonitos, como un bonito font
o un background
en el recipiente.
A continuación, creamos la línea con un ::after
pseudoelemento de espesor ( height
) absolutamente posicionado $h
:
$h: .125em;$r: .5*$h;h1 { position: relative; ::after { position: absolute; top: calc(50% - #{$r}); right: 0; height: $h; border-radius: 0 $r $r 0; background: crimson; }}
El código anterior se encarga del posicionamiento y height
del pseudoelemento, pero ¿qué pasa con width
? ¿Cómo hacemos para que se extienda desde el borde izquierdo de la ventana gráfica hasta el borde derecho del texto del encabezado?
Longitud de la línea
Bueno, dado que tenemos un grid
diseño donde el título está alineado en el medio horizontalmente, esto significa que la línea media vertical de la ventana gráfica coincide con la del título, dividiendo ambos en dos mitades del mismo ancho:
En consecuencia, la distancia entre el borde izquierdo de la ventana gráfica y el borde derecho del encabezado es la mitad del ancho de la ventana gráfica ( 50vw
) más la mitad del ancho del encabezado, que puede expresarse como un %
valor cuando se usa en el cálculo. de sus pseudoelementos width
.
Entonces el width
de nuestro ::after
pseudoelemento es:
width: calc(50vw + 50%);
Haciendo que la línea vaya por encima y por debajo
Hasta ahora, el resultado es sólo una línea carmesí que cruza un texto negro:
Lo que queremos es que algunas de las letras aparezcan en la parte superior de la línea. Para conseguir este efecto, les damos (o no les damos) una clase de .over
azar. Esto significa alterar ligeramente el código Pug:
- let text = 'We Love to Play';- let arr = text.split('');h1(role='image' aria-label=text) - arr.forEach(letter = { span.letter(class=Math.random() .5 ? 'over' : null) #{letter} - });
Luego posicionamos relativamente las letras con una clase de .over
y les damos un positivo z-index
.
.over { position: relative; z-index: 1;}
Mi idea inicial implicaba usar translatez(1px)
en lugar de z-index: 1
, pero luego me di cuenta de que usar z-index
tiene mejor compatibilidad con el navegador e implica menos esfuerzo.
La línea pasa por encima de algunas letras, pero por debajo de otras:
¡Anímalo!
Ahora que superamos la parte complicada, también podemos agregar un animation
para hacer que la línea entre. Esto significa que la línea carmesí se desplaza hacia la izquierda (en la dirección negativa del eje x, por lo que el signo será menos) por su máximo width
( 100%
) al principio, sólo para luego permitirle volver a su posición normal.
@keyframes slide { 0% { transform: translate(-100%); } }
Opté por tener un poco de tiempo para respirar antes del inicio del animation
. Esto significó agregar el 1s
retraso que, a su vez, significó agregar la backwards
palabra clave para animation-fill-mode
, de modo que la línea permaneciera en el estado especificado por el 0%
fotograma clave antes del inicio de animation
:
animation: slide 2s ease-out 1s backwards;
Un toque 3D
Hacer esto me dio otra idea, que fue hacer que la línea atravesara cada letra, es decir, comenzar arriba de la letra, recorrerla y terminar debajo (o al revés).
Esto requiere 3D real y algunos pequeños ajustes.
En primer lugar, configuramos transform-style
el preserve-3d
encabezado ya que queremos que todos sus hijos (y pseudoelementos) formen parte del mismo ensamblaje 3D, lo que hará que se ordenen y se crucen según cómo estén posicionados en 3D.
A continuación, queremos rotar cada letra alrededor de su eje y, con la dirección de rotación dependiendo de la presencia de la clase asignada aleatoriamente (cuyo nombre cambiamos .rev
de “inverso” a “sobre” no sugiere realmente lo que queremos). estás haciendo aquí más).
Sin embargo, antes de hacer esto, debemos recordar que nuestros elementos todavía están en línea en este punto y establecer a transform
en un elemento en línea no tiene absolutamente ningún efecto.
Para solucionar este problema, nos fijamos display: flex
en el título. Sin embargo, esto crea un nuevo problema y es el hecho de que los elementos de intervalo que contienen solo un espacio ( " "
) quedan reducidos a cero width
.
Una solución sencilla para esto es configurar white-space: pre
nuestros .letter
tramos.
Una vez que hayamos hecho esto, podemos rotar nuestros tramos en un ángulo $a
… ¡en una dirección u otra!
$a: 2deg;.letter { white-space: pre; transform: rotatey($a);}.rev { transform: rotatey(-$a); }
Dado que la rotación alrededor del eje y aplasta nuestras letras horizontalmente, podemos escalarlas a lo largo del eje x mediante un factor ( $f
) que es el inverso del coseno de $a
.
$a: 2deg;$f: 1/cos($a).letter { white-space: pre; transform: rotatey($a) scalex($f)}.rev { transform: rotatey(-$a) scalex($f) }
Si desea comprender el motivo del uso de este factor de escala en particular, puede consultar este artículo anterior donde lo explico todo en detalle.
¡Y eso es! ¡Ahora tenemos el resultado 3D que buscábamos! Sin embargo, tenga en cuenta que la fuente utilizada aquí se eligió para que nuestro resultado se vea bien y es posible que otra fuente no funcione tan bien.
Deja un comentario