Una de las cosas que nos confundió cuando estábamos construyendo nuestro nuevo sitio web fueron las ondas transformadas que se pueden ver en diferentes lugares de las páginas. Teníamos muchas ideas sobre cómo implementarlas de la manera correcta sin gran esfuerzo. Sin embargo, la mayoría de las soluciones eran lentas y tuvimos que construir desde cero algo que fuera más rápido que las librerías ya existentes.
Propuestas de solución
Empezamos con un objeto SVG plano que se animaba con diferentes librerías. Como queríamos tener 3 objetos en una página, el resultado no fue tan satisfactorio. Todas las animaciones eran simplemente lentas - todas las rutas de un único objeto SVG tenían que actualizarse en periodos de tiempo realmente cortos, lo que hacía que toda la página fuera tan lenta como un caracol. Tuvimos que rechazar la solución con SVG puro insertado en un documento. Eso nos dejó con otras dos soluciones para elegir.
En vídeo
era la segunda opción. Empezamos con dos problemas:
- fondo transparente, que no puede aplicarse con los formatos de vídeo más populares, como .mp4 o .webm,
- La capacidad de respuesta, que era un verdadero problema porque los vídeos no son escalables como tales.
Decidimos mantener esta solución en un segundo plano: "si no encontramos otra cosa, elegiremos esta".
La última opción era utilizar lona
con WebGL
renderizado. Era una opción tan inusual porque tuvimos que diseñar toda la mecánica de renderizado por nosotros mismos. Eso se debe a que las ondas mórficas que teníamos eran personalizadas, lo que nos obligó a diseñar una solución a medida. Y esa era la opción que queríamos seguir y en la que realmente queríamos centrarnos.
Arquitectura de la solución
Empecemos de cero. ¿Cuál era el material que teníamos que utilizar para construir estas ondas? La idea era que todas las ondas eran un archivo SVG de tamaño 1×1 y caminos específicos colocados alrededor de esta área. La animación de este SVG fue construido por algunas formas de estados a este archivo. Así, todas las animaciones fueron representados como un conjunto de archivos que contenían las etapas de mover una forma.
Mira más a fondo lo que son los estados - todos los caminos son sólo una especie de matriz con valores específicos colocados en un orden específico. Cambiar esos valores en posiciones específicas dentro de este array cambia la forma en sus partes específicas. Podemos simplificar esto con el siguiente ejemplo:
estado 1: [1, 50, 25, 40, 100]
estado 2: [0, 75, -20, 5, 120]
estado 3: [5, 0, -100, 80, 90]
Así, podemos suponer que la forma que queremos renderizar consiste en un array con 5 elementos que van cambiando con el easing lineal en periodos de tiempo específicos. Cuando la animación termina la última etapa, comienza de nuevo con la primera para darnos la impresión de una animación infinita.
Pero... espera - ¿qué es exactamente el array presentado arriba? Como he mencionado, es un camino que es responsable de mostrar una forma específica. Toda la magia está incluida en el d
de la ruta de SVG. Esta propiedad contiene un conjunto de "comandos" para dibujar una forma y cada comando tiene un tipo de argumentos. El array mencionado anteriormente consiste en todos los valores (argumentos) adjuntos a estos comandos.
La única diferencia entre estos "archivos de estado" eran los valores de comandos específicos, ya que el orden de los comandos era el mismo. Así que toda la magia consistía en obtener todos los valores y animarlos.
El asistente llamado Física
En el párrafo anterior, he mencionado que la única magia en la animación de un objeto es la creación de transiciones entre todas las etapas de una forma. La pregunta es - ¿cómo hacer eso con canvas?
La función que todos los que trabajaron con lona
debe saber es requestAnimationFrame. Si ves esto por primera vez, creo sinceramente que deberías empezar por leer esto. Entonces, lo que nos interesa de esta función es el argumento - DOMHighResTimeStamp
. Parece realmente aterrador pero en la práctica no es tan difícil de entender. Podemos decir que es un timestamp del tiempo transcurrido desde la primera renderización.
Vale, pero ¿qué podemos hacer con esto? Como el requestAnimationFrame
debe ser llamada recursivamente, podemos acceder a un delta de tiempo entre sus llamadas. ¡Y aquí vamos con la ciencia! ⚛️ (ok, tal vez no sea ciencia espacial... todavía )
La física nos enseña que el delta de una distancia es igual al delta del tiempo multiplicado por la velocidad. En nuestro caso, la velocidad es constante porque queremos llegar al punto final en un tiempo determinado. Así pues, veamos cómo podemos representarlo con los estados anteriores:
Digamos que queremos que la transición entre estos estados se produzca en mil milisegundos, por lo que los valores de velocidad serán los siguientes:
delta: [ -1, 25, -45, -35, 20]
velocidad: [-1/1000, 25/1000, -45/1000, -35/1000, 20/1000]
La velocidad anterior nos dice: por cada milisegundo aumentemos el valor en -1/1000. Y aquí es donde podemos volver a nuestro requestAnimationFrame
y delta de tiempo. El valor de una posición concreta que queremos aumentar es el delta de tiempo multiplicado por la velocidad de su posición. Otra cosa para conseguirlo sin problema es limitar el valor para que no sobrepase el estado al que va.
Cuando termina la transición, llamamos a otra y así sucesivamente. Y no parece tan difícil de implementar, pero una de las principales reglas en desarrollo de software es no dedicar tiempo a cosas que ya están implantadas. Así que elegimos un pequeña biblioteca que nos permite crear estas transiciones sin esfuerzo.
Así creamos una ola animada que parece un ser vivo.
Unas palabras sobre la clonación de formas
Como puede ver, las olas de la marca The Codest no son una única figura animada. Se componen de muchas figuras con la misma forma pero diferente tamaño y posición. En este paso, vamos a echar un vistazo rápido sobre cómo duplicar de tal manera.
Así, el contexto del lienzo nos permite área de dibujo a escala (bajo el capó - podemos decir que multiplica todas las dimensiones pasadas a los métodos dibujables por "k", donde "k" es un factor de escala, por defecto fijado en "1"), hacer lienzo traducidoes como cambiar el punto de anclaje de un área de dibujo. Y también podemos saltar entre estos estados con estos métodos: guardar y restaurar.
Estos métodos nos permiten guardar el estado de "cero modificaciones" y luego renderizar un número específico de ondas en el bucle con el lienzo correctamente escalado y trasladado. Justo después de esto, podemos volver al estado guardado. Eso es todo sobre la clonación de figuras. Mucho más fácil que clonar ovejas, ¿no?
Cereza encima
He mencionado que rechazamos una de las posibles soluciones debido al rendimiento. La opción con canvas es bastante rápida pero nadie dijo que no se pudiera optimizar aún más. Empecemos por el hecho de que realmente no nos importa la transición de las formas cuando están fuera de la ventana gráfica del navegador.
Hay otra API de navegador que encantó a los programadores - IntersectionObserver. Nos permite seguir elementos específicos de la página y manejar eventos que son invocados cuando esos elementos aparecen o desaparecen del viewport. Ahora mismo - tenemos una situación bastante fácil - vamos a crear el estado de visibilidad, cambiarlo debido a los manejadores de eventos IntersectionObserver y simplemente activar/desactivar el sistema de renderizado para formas específicas. Y ... ¡boom el rendimiento ha mejorado mucho! Estamos renderizando las únicas cosas que son visibles en el viewport.
Resumen
Elegir una forma de aplicar las cosas suele ser una elección difícil, sobre todo cuando las opciones disponibles parecen tener ventajas e inconvenientes similares. La clave para elegir correctamente es analizar cada una de ellas y excluir las que veamos menos beneficiosas. No todo está claro: puede que una solución requiera más tiempo que las otras, pero que su optimización sea más sencilla o más personalizable.
Aunque aparecen nuevas librerías JS casi cada minuto, hay cosas que no pueden resolver. Y por eso todo ingeniero front-end (y no sólo ellos) debería conocer las APIs de los navegadores, mantenerse al día de las novedades técnicas y a veces simplemente pensar "¿cómo sería mi implementación de esta librería si tuviera que hacer esto?". Con un mayor conocimiento sobre los navegadores, podemos construir cosas realmente sofisticadas, tomar buenas decisiones sobre las herramientas que utilizamos, y tener más confianza en nuestra código.
Más información:
– Ruby 3.0. Ruby y los métodos de control de la privacidad menos conocidos
– Cállate y llévate tu dinero #1: Costes ocultos y agilidad real en el proceso de desarrollo de productos
– CTO retos - ampliación y crecimiento de productos de software