Animación de relleno HTML5 SVG con CSS3 y JavaScript Vanilla
En este artículo, puede aprender cómo crear la visualización de notas animadas desde el sitio web de Awwwards . Analiza el elemento circular HTML5 SVG, sus propiedades de trazo y cómo animarlos con variables CSS y JavaScript Vanilla.
SVG significa Scalable V ector Graphics y es un lenguaje de marcado estándar basado en XML para gráficos vectoriales . Le permite dibujar trazados, curvas y formas determinando un conjunto de puntos en el plano 2D. Además, puede agregar propiedades de contracción en esos trazados (como trazo, color, grosor, relleno y más) para producir animaciones.
Desde abril de 2017, el módulo de trazo y relleno de nivel 3 de CSS permite configurar colores SVG y patrones de relleno desde una hoja de estilo externa, en lugar de establecer atributos en cada elemento. En este tutorial, usaremos un color hexadecimal simple, pero las propiedades de relleno y trazo también aceptan patrones, degradados e imágenes como valores.
Nota : Al visitar el sitio web de Awwwards , la visualización de notas animadas solo se puede ver con el ancho del navegador configurado en 1024 px o más.
- Demostración: Proyecto de visualización de notas
- Repositorio: repositorio de visualización de notas
Estructura de archivos
Comencemos creando los archivos en la terminal:
mkdir note-display cd note-display touch index.html styles.css scripts.js
HTML
Aquí está la plantilla inicial que vincula ambos css
archivos js
:
htmlhead meta charset="UTF-8" titleNote Display/title link rel="stylesheet" href="./styles.css"/headbody script src="./scripts.js"/script/body/html
Cada elemento de nota consta de un elemento de lista: li
que contiene el circle
, el note
valor y su label
.
.circle
: .percent
y .label
. ( Vista previa grande )
Es .circle_svg
un elemento SVG que envuelve dos elementos circle . El primero es el camino a llenar mientras que el segundo es el relleno que se animará.
Está note
separado en números enteros y decimales para que se les puedan aplicar diferentes tamaños de fuente. Es label
un sencillo span
. Entonces, juntando todo esto queda así:
li div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanTransparent/span/li
Los atributos cx
y cy
definen el punto central del eje x y del eje y del círculo. El r
atributo define su radio.
Probablemente hayas notado el patrón de guión bajo/guión en los nombres de las clases. Eso es BEM , que significa block
y element
. modifier
Es una metodología que hace que la denominación de sus elementos sea más estructurada, organizada y semántica.
Lectura recomendada : Una explicación de BEM y por qué lo necesita
Para finalizar las estructuras de la plantilla, envolvamos los cuatro elementos de la lista en un elemento de lista desordenado:
li
elementos secundarios ( vista previa grande )
ul li div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanTransparent/span /li li div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanReasonable/span /li li div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanUsable/span /li li div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanExemplary/span /li/ul
Debes preguntarte qué significan las etiquetas Transparent
, Reasonable
y Usable
. Exemplary
Cuanto más se familiarice con la programación, se dará cuenta de que escribir código no se trata solo de hacer que la aplicación sea funcional, sino también de garantizar que sea escalable y mantenible a largo plazo. Esto sólo se logra si su código es fácil de cambiar.
"El acrónimo
TRUE
debería ayudar a decidir si el código que escriba podrá adaptarse a los cambios en el futuro o no".
Así que la próxima vez pregúntate:
Transparent
: ¿Están claras las consecuencias de los cambios de código?Reasonable
: ¿Vale la pena la relación costo-beneficio?Usable
: ¿Podré reutilizarlo en escenarios inesperados?Exemplary
: ¿Presenta alta calidad como ejemplo para código futuro?
Nota : " Diseño práctico orientado a objetos en Ruby " de Sandi Metz explica TRUE
junto con otros principios y cómo lograrlos a través de patrones de diseño. Si aún no se ha tomado el tiempo para estudiar patrones de diseño, considere agregar este libro a su lectura antes de dormir.
CSS
Importemos las fuentes y apliquemos un reinicio a todos los elementos:
@import url('https://fonts.googleapis.com/css?family=Nixie+One|Raleway:200');* { padding: 0; margin: 0; box-sizing: border-box;}
La box-sizing: border-box
propiedad incluye valores de relleno y borde en el ancho y alto total de un elemento, por lo que es más fácil calcular sus dimensiones.
Nota : Para obtener una explicación visual box-sizing
, lea " Haga su vida más fácil con el tamaño de caja CSS ".
body { height: 100vh; color: #fff; display: flex; background: #3E423A; font-family: 'Nixie One', cursive;}.display-container { margin: auto; display: flex;}
Al combinar las reglas display: flex
en body
y margin-auto
en .display-container
, es posible centrar el elemento secundario tanto vertical como horizontalmente. El .display-container
elemento también será un flex-container
; de esa manera, sus hijos se colocarán en la misma fila a lo largo del eje principal.
El .note-display
elemento de la lista también será un archivo flex-container
. Como hay muchos elementos secundarios para centrar, hagámoslo mediante las propiedades justify-content
y align-items
. Todo flex-items
estará centrado a lo largo del eje cross
y main
. Si no está seguro de cuáles son, consulte la sección de alineación en " Guía visual de fundamentos de CSS Flexbox ".
.note-display { display: flex; flex-direction: column; align-items: center; margin: 0 25px;}
Apliquemos un trazo a los círculos estableciendo las reglas stroke-width
, stroke-opacity
y stroke-linecap
eso en conjunto le da estilo al trazo en vivo. A continuación, agreguemos un color a cada círculo:
.circle__progress { fill: none; stroke-width: 3; stroke-opacity: 0.3; stroke-linecap: round;}.note-display:nth-child(1) .circle__progress { stroke: #AAFF00; }.note-display:nth-child(2) .circle__progress { stroke: #FF00AA; }.note-display:nth-child(3) .circle__progress { stroke: #AA00FF; }.note-display:nth-child(4) .circle__progress { stroke: #00AAFF; }
Para posicionar el percent
elemento de manera absoluta, es necesario saber exactamente hacia qué. El .circle
elemento debe ser la referencia, así que agregémosle position: relative
.
Nota : Para obtener una explicación visual más profunda sobre el posicionamiento absoluto, lea " Cómo entender la posición absoluta de CSS de una vez por todas ".
Otra forma de centrar elementos es combinarlos top: 50%
y posicionar el centro del elemento en el centro de su padre left: 50%
.transform: translate(-50%, -50%);
.circle { position: relative;}.percent { width: 100%; top: 50%; left: 50%; position: absolute; font-weight: bold; text-align: center; line-height: 28px; transform: translate(-50%, -50%);}.percent__int { font-size: 28px; }.percent__dec { font-size: 12px; }.label { font-family: 'Raleway', serif; font-size: 14px; text-transform: uppercase; margin-top: 15px;}
A estas alturas, la plantilla debería verse así:
Transición de relleno
La animación del círculo se puede crear con la ayuda de dos propiedades SVG del círculo: stroke-dasharray
y stroke-dashoffset
.
"
stroke-dasharray
Define el patrón de espacio entre guiones en un trazo".
Puede tomar hasta cuatro valores:
- Cuando se establece en un único número entero (
stroke-dasharray: 10
), los guiones y los espacios tienen el mismo tamaño; - Para dos valores (
stroke-dasharray: 10 5
), el primero se aplica a guiones, el segundo a espacios; - Las formas tercera y cuarta (
stroke-dasharray: 10 5 2
ystroke-dasharray: 10 5 2 3
) generarán guiones y espacios en varios tamaños.
La imagen de la izquierda muestra la propiedad stroke-dasharray
configurada de 0 a 238 px, que es la longitud de la circunferencia del círculo.
La segunda imagen representa la stroke-dashoffset
propiedad que desplaza el comienzo de la matriz de guiones. También se establece de 0 a la longitud de la circunferencia del círculo.
Para producir el efecto de relleno, estableceremos la stroke-dasharray
longitud de la circunferencia, de modo que toda su longitud se llene con un guión grande y sin espacios. También lo compensaremos con el mismo valor, para que quede "oculto". Luego se stroke-dashoffset
actualizará al valor de nota correspondiente, llenando el trazo de acuerdo con la duración de la transición.
La actualización de propiedades se realizará en los scripts a través de Variables CSS . Declaremos las variables y establezcamos las propiedades:
.circle__progress--fill { --initialStroke: 0; --transitionDuration: 0; stroke-opacity: 1; stroke-dasharray: var(--initialStroke); stroke-dashoffset: var(--initialStroke); transition: stroke-dashoffset var(--transitionDuration) ease;}
Para establecer el valor inicial y actualizar las variables, comencemos seleccionando todos .note-display
los elementos con document.querySelectorAll
. Se transitionDuration
establecerá en 900
milisegundos.
Luego, iteramos a través de la matriz de pantallas, la seleccionamos .circle__progress.circle__progress--fill
y extraemos el r
atributo establecido en el HTML para calcular la longitud de la circunferencia. Con eso, podemos establecer los valores iniciales --dasharray
y --dashoffset
.
La animación se producirá cuando la --dashoffset
variable se actualice con un setTimeout de 100 ms:
const displays = document.querySelectorAll('.note-display');const transitionDuration = 900;displays.forEach(display = { let progress = display.querySelector('.circle__progress--fill'); let radius = progress.r.baseVal.value; let circumference = 2 * Math.PI * radius; progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); progress.style.setProperty('--initialStroke', circumference); setTimeout(() = progress.style.strokeDashoffset = 50, 100);});
Para que la transición comience desde arriba, el .circle__svg
elemento debe rotarse:
.circle__svg { transform: rotate(-90deg);}
Ahora, calculemos el dashoffset
valor relativo a la nota. El valor de la nota se insertará en cada li
elemento a través del atributo data-* . Se *
puede cambiar por cualquier nombre que se adapte a sus necesidades y luego se puede recuperar en JavaScript a través del conjunto de datos del elemento: element.dataset.*
.
Nota : Puede leer más sobre el atributo data-* en MDN Web Docs .
Nuestro atributo se llamará “ data-note
”:
ul+ li data-note="7.50" div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanTransparent/span /li+ li data-note="9.27" div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanReasonable/span /li+ li data-note="6.93" div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanUsable/span /li+ li data-note="8.72" div svg circle cx="41" cy="41" r="38"/circle circle cx="41" cy="41" r="38"/circle /svg div span0./span span00/span /div /div spanExemplary/span /li/ul
El parseFloat
método convertirá la cadena devuelta por display.dataset.note
en un número de punto flotante. Representa offset
el porcentaje que falta para alcanzar la puntuación máxima. Entonces, para una 7.50
nota, tendríamos (10 - 7.50) / 10 = 0.25
, lo que significa que la circumference
longitud debe compensarse con 25%
su valor:
let note = parseFloat(display.dataset.note);let offset = circumference * (10 - note) / 10;
Actualizando el scripts.js
:
const displays = document.querySelectorAll('.note-display');const transitionDuration = 900;displays.forEach(display = { let progress = display.querySelector('.circle__progress--fill'); let radius = progress.r.baseVal.value; let circumference = 2 * Math.PI * radius;+ let note = parseFloat(display.dataset.note);+ let offset = circumference * (10 - note) / 10; progress.style.setProperty('--initialStroke', circumference); progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);+ setTimeout(() = progress.style.strokeDashoffset = offset, 100);});
Antes de continuar, extraigamos la transición de stoke a su propio método:
const displays = document.querySelectorAll('.note-display');const transitionDuration = 900;displays.forEach(display = {- let progress = display.querySelector('.circle__progress--fill');- let radius = progress.r.baseVal.value;- let circumference = 2 * Math.PI * radius; let note = parseFloat(display.dataset.note);- let offset = circumference * (10 - note) / 10;- progress.style.setProperty('--initialStroke', circumference);- progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);- setTimeout(() = progress.style.strokeDashoffset = offset, 100);+ strokeTransition(display, note);});+ function strokeTransition(display, note) {+ let progress = display.querySelector('.circle__progress--fill');+ let radius = progress.r.baseVal.value;+ let circumference = 2 * Math.PI * radius;+ let offset = circumference * (10 - note) / 10;+ progress.style.setProperty('--initialStroke', circumference);+ progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);+ setTimeout(() = progress.style.strokeDashoffset = offset, 100);+ }
Aumento del valor de la nota
Todavía queda 0.00
por crear la transición de la nota al valor de la nota. Lo primero que debemos hacer es separar los valores enteros y decimales. Usaremos el método de cadena split()
(toma un argumento que determina dónde se dividirá la cadena y devuelve una matriz que contiene ambas cadenas rotas). Estos se convertirán en números y se pasarán como argumentos a la increaseNumber()
función, junto con el display
elemento y una bandera que indica si es un número entero o un decimal.
const displays = document.querySelectorAll('.note-display');const transitionDuration = 900;displays.forEach(display = { let note = parseFloat(display.dataset.note);+ let [int, dec] = display.dataset.note.split('.');+ [int, dec] = [Number(int), Number(dec)]; strokeTransition(display, note);+ increaseNumber(display, int, 'int');+ increaseNumber(display, dec, 'dec');});
En la increaseNumber()
función, seleccionamos el elemento .percent__int
o .percent__dec
, dependiendo de className
, y también en caso de que la salida contenga un punto decimal o no. Hemos configurado nuestro transitionDuration
en 900ms
. Ahora, para animar un número del 0 al 7, por ejemplo, hay que dividir la duración por la nota 900 / 7 = 128.57ms
. El resultado representa cuánto tiempo llevará cada iteración de aumento. Esto significa que setInterval
dispararemos cada 128.57ms
.
Con esas variables configuradas, definamos el setInterval
. La counter
variable se agregará al elemento como texto y se aumentará en cada iteración:
function increaseNumber(display, number, className) { let element = display.querySelector(`.percent__${className}`), decPoint = className === 'int' ? '.' : '', interval = transitionDuration / number, counter = 0; let increaseInterval = setInterval(() = { element.textContent = counter + decPoint; counter++; }, interval);}
¡Fresco! Aumenta los valores, pero lo hace para siempre. Necesitamos borrar el setInterval
momento en que las notas alcanzan el valor que queremos. Eso se hace con clearInterval
la función:
function increaseNumber(display, number, className) { let element = display.querySelector(`.percent__${className}`), decPoint = className === 'int' ? '.' : '', interval = transitionDuration / number, counter = 0; let increaseInterval = setInterval(() = {+ if (counter === number) { window.clearInterval(increaseInterval); } element.textContent = counter + decPoint; counter++; }, interval);}
Ahora el número se actualiza hasta el valor de la nota y se borra con clearInterval()
la función.
Eso es todo por este tutorial. ¡Espero que lo hayan disfrutado!
Si tienes ganas de crear algo un poco más interactivo, consulta mi Tutorial de juego de memoria creado con Vanilla JavaScript. Cubre conceptos básicos de HTML5, CSS3 y JavaScript, como posicionamiento, perspectiva, transiciones, Flexbox, manejo de eventos, tiempos de espera y ternarios.
¡Feliz codificación!
(dm, ra, il)Explora más en
- javascript
- CSS
- HTML
- SVG
- Animación
Deja un comentario