Las animaciones hermosas se han establecido durante mucho tiempo en las tendencias de diseño. Los diseñadores de UI hacen carruseles elaborados, descargas, animaciones de menú y otras decoraciones, mientras que los desarrolladores frontend los traducen a código. Pero el sitio no solo debería verse bien, sino también funcionar rápidamente.
Una interfaz moderna debería optimizar su código. Esto es especialmente cierto para los productos en los que la mayoría de la audiencia va al sitio desde dispositivos móviles. Algunos métodos de animación se retrasan incluso en Chrome en las mejores computadoras, pero deberían funcionar sin problemas en un teléfono inteligente promedio.
Nuestros desarrolladores utilizaron una gran cantidad de técnicas que ayudaron a optimizar el sitio y acelerar su trabajo. Reuní 4 de los más interesantes de ellos. Compartimos conocimientos que serán útiles para principiantes y profesionales, así como también proporcionamos enlaces a tutoriales útiles.

1. Animación en SCSS
El sitio tiene mucha animación. Necesitamos que el navegador lo reproduzca con una velocidad de cuadro estable de 60 fps. En CSS puro, esto es difícil de hacer, así que usamos SCSS.
Para crear los controles deslizantes, utilizamos la biblioteca Swiper . Para un control deslizante horizontal, el uso de esta biblioteca está justificado, ya que necesitábamos proporcionar soporte para svayp por parte del usuario. Pero para un carrusel sin fin vertical, Swiper no es necesario, no hay interacción del usuario. Por lo tanto, queríamos repetir la misma funcionalidad usando solo funciones CSS.
La primera y más importante condición cuando se trabaja con animación CSS es usar solo las propiedades de transformación y opacidad. Los navegadores pueden optimizar de forma independiente la animación de estas propiedades y producir 60 fps estables. Sin embargo, el uso de @keyframes solo no es posible escribir diferentes animaciones para diferentes elementos, y animar cada elemento individualmente en CSS puro lleva demasiado tiempo. ¿Cómo escribir rápidamente la animación que necesitamos? Elegimos SCSS, el dialecto SASS, una extensión CSS más funcional.
Analicemos el uso de SCSS utilizando el ejemplo de nuestro control deslizante vertical.

A nuestra disposición hay un contenedor cuya altura es igual a la altura de los tres elementos del carrusel. En su interior hay otro contenedor que contiene todos los elementos del carrusel.
<div class="b-vertical-carousel-slider"> <div class="vertical-carousel-slider-wrapper slider-items"> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> </div> </div>
Eliminamos la visibilidad de los elementos del contenedor que irán más allá y establecemos la altura de los bloques.
.b-vertical-carousel-slider { position: relative; overflow: hidden; height: $itemHeight * 3; .vertical-carousel-slider-item { height: $itemHeight; } }
El cálculo de la animación cambia solo si cambia el número de elementos en el carrusel. A continuación, escribimos un mixin que toma un parámetro de entrada: $itemCount
@mixin verticalSlideAnimation($itemCount) { }
En el mixin, genere un fotograma clave para cada elemento, establezca el estado inicial y use :nth-child
determinar la animación para el elemento.
for $i from * 1 through $itemCount { $animationName: carousel-item-#{$itemCount}-#{$i}; @keyframes #{$animationName} { 0% { transform: translate3d(0, 0, 0) scale(.95); } } .vertical-carousel-slider-item:nchild(#{$i}) { animation: $stepDuration * $itemCount $animation ease infinite; } }
Además, durante la animación, solo moveremos los elementos a lo largo del eje y y cambiaremos la escala del elemento en el centro.
Estados del elemento carrusel:
- Paz
- Desplazamiento Y
- Desplazamiento Y
- Y compensado con disminución
Cada elemento se moverá hacia arriba $itemCount
veces, aumentará una vez y disminuirá una vez durante el movimiento. Por lo tanto, generaremos y calcularemos la animación para cada uno de los movimientos ascendentes.
@keyframes #{$animationName} { 0% { transform: translate3d(0, 0, 0) scale(.95); } @for $j from 0 through $itemCount { $isFocusedStep: $i == $j + 2; $isNotPrevStep: $i != $j + 1; $offset: 100% / $itemCount * ($animationTime / $stepDuration); @if ($isFocusedStep) { #{getPercentForStep($j, $itemCount, $offset)} { transform: getTranslate($j - 1) scale(.95); } #{getPercentForStep($j, $itemCount)} { transform: getTranslate($j) scale(1); } #{getPercentForStep($j + 1, $itemCount, $offset)} { transform: getTranslate($j) scale(1); } #{getPercentForStep($j + 1, $itemCount)} { transform: getTranslate($j + 1) scale(.95); } } @else if ($isNotPrevStep) { #{getPercentForStep($j, $itemCount, $offset)} { transform: getTranslate($j - 1) scale(.95); } #{getPercentForStep($j, $itemCount)} { transform: getTranslate($j) scale(.95); } } } }
Queda por definir algunas variables y funciones:
$animationTime
- tiempo de interpolación de movimiento$stepDuration
: tiempo total de ejecución de un paso de animación ( $animationTime
+ tiempo de descanso del carrusel)getPercentForStep($step, $itemCount, $offset)
: una función que devuelve en porcentaje el punto extremo de uno de los estados.getTranslate($step)
: devuelve la traducción según el paso de animación
Ejemplo de implementaciones de funciones:
@function getPercentForStep($step, $count, $offset: 0) { @return 100% * $step / $count - $offset; } @function getTranslate($step) { @return translate3d(0, -100% * $step, 0); }
Tenemos un carrusel en funcionamiento con un elemento creciente en el medio. Queda por hacer una sombra debajo de los elementos crecientes. Inicialmente, cada elemento del carrusel tenía un pseudo-elemento: después, que a su vez tenía una sombra. Para no animar la propiedad de sombra, utilizamos la propiedad de opacidad para ella, es decir Solo mostró y escondió la sombra.
Pero en la nueva implementación para tal solución, debe generar muchos fotogramas clave adicionales para cada pseudo-elemento. Decidimos hacerlo más fácil: habrá un bloque con una sombra y ocupará espacio exactamente debajo del elemento central del carrusel.
Agregue un div que sea responsable de la sombra.
<div class="b-vertical-carousel-slider"> <div class="vertical-carousel-slider-wrapper"> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> <div class="vertical-carousel-slider-item"></div> </div> <div class="vertical-carousel-slider-shadow"></div> </div>
Lo estilizamos y agregamos animaciones.
@keyframes shadowAnimation { 0% { opacity: 1; } 80% { opacity: 1; } 90% { opacity: 0; } 100% { opacity: 1; } } .vertical-carousel-slider-shadow { top: $itemHeight; left: 0; right: 0; height: $itemHeight; animation: $stepDuration shadowAnimation ease infinite; }
En este caso, no necesita generar y pensar en una forma de animar la sombra debajo de cada elemento, solo animamos un bloque, tiene dos estados: es visible y está oculto
Como resultado, tenemos un carrusel de CSS puro que anima solo con propiedades bien optimizadas. Esto permite que el navegador use aceleración de hardware para renderizar. De ahí el beneficio tangible en comparación con la animación JS:
- Al desplazarse por páginas con animaciones en dispositivos débiles, los marcos se omitieron claramente en las animaciones JS, bajando FPS a 15-20. Las animaciones CSS han mejorado claramente las cosas. En estos dispositivos, esta cifra era de al menos 50-55 FPS.
- Nos deshicimos del trabajo de un módulo de terceros donde no era necesario.
- La animación se reproducirá incluso cuando JS esté desactivado
La animación JS debe usarse si se necesita un control estricto sobre cada cuadro: pausa, reproducción inversa, rebobinado, reacción a las acciones del usuario. En otros casos, recomendamos usar CSS puro.
Enlaces utiles
2. Usando la API de Intersection Observer
La animación que el visitante del sitio no ve se reproduce en algún lugar fuera de la vista y carga la CPU. Usando el Intersection Observer, determinamos qué tipo de animación es visible en la pantalla en este momento, y solo la reproducimos.
Todos los trucos del último párrafo se pueden combinar con el Intersection Observer. Esta herramienta ayuda a no cargar el navegador con animación que el visitante del sitio no ve. Anteriormente, los "oyentes" de los eventos con uso intensivo de recursos se usaban para comprender si un visitante estaba mirando un elemento animado y esto no producía un fuerte "escape". La diferencia entre usar animación fuera de la ventana gráfica y usar oyentes fue mínima. Intersection Observer API requiere menos recursos y ayuda a reproducir solo la animación que es visible para el visitante.
En nuestro sitio, la animación se activa solo cuando aparece un elemento en la ventana gráfica. Si no hiciéramos esto, las páginas se sobrecargarían con la ejecución constante de animaciones en bucle que no se veían. Intersection Observer API le permite monitorear la intersección del elemento con el elemento primario o el alcance del documento.
Ejemplo de implementación
Como ejemplo, mostramos cómo optimizar la animación en JS. La idea es simple: la animación se reproduce mientras el elemento animado está en la ventana gráfica. Para la implementación, utilizamos la API Intersection Observer.
Agregue manejo de clases is-paused
a los estilos
.b-vertical-carousel-slider.is-paused { .vertical-carousel-slider-wrapper { .vertical-carousel-slider-item { animation-play-state: paused; } } .vertical-carousel-slider-shadow { animation-play-state: paused; } }
Es decir cuando aparezca esta clase, la animación se detendrá.
Ahora describimos la lógica de agregar y eliminar esta clase
if (window.IntersectionObserver) { const el = document.querySelector('.b-vertical-carousel-slider'); const observer = new IntersectionObserver(intersectionObserverCallback); observer.observe(el); }
Aquí creamos una instancia de IntersectionObserver, especificamos la función intersectionObserverCallback
, que funcionará cuando cambie la visibilidad.
Ahora defina intersectionObserverCallback
function intersectionObserverCallback(entries){ if (entries[0].intersectionRatio === undefined) { return; } helperDOM.toggleClass(el, 'is-paused', entries[0].intersectionRatio <= 0); };
Ahora la animación se reproduce solo en aquellos elementos que son visibles. Tan pronto como el elemento ha desaparecido del campo de visión, la animación se detiene. Cuando el visitante vuelva a él, la reproducción continuará.
Enlaces utiles
3. Representación SVG
Una gran cantidad de imágenes o el uso de sprites provoca frisos y retrasos en los primeros segundos después de la carga. Incrustar SVG en el código de la página ayuda visualmente a facilitar la carga.
Cuando seleccionamos métodos para trabajar con imágenes, teníamos 2 opciones de optimización: incrustar SVG en HTML o usar sprites. Nos decidimos por la inclusión. Incrustamos el código XML de cada imagen directamente en el código HTML de las páginas. Esto aumenta ligeramente su tamaño, pero SVG se sirve en línea inmediatamente con el documento.
Muchos desarrolladores continúan usando sprites SVG. ¿Cuál es la esencia del método? Una colección de imágenes (por ejemplo, iconos) se recogen en un gran lienzo de imágenes, que se llama un sprite. Cuando necesita mostrar un icono específico, se llama a un sprite, después de lo cual se proporcionan las coordenadas de la pieza específica en la que se encuentra. Esto se ha hecho durante mucho tiempo, incluso en la primera versión de HTTP. Sprites ayudó a almacenar el archivo en caché agresivamente y a reducir la cantidad de solicitudes del servidor. Esto fue importante porque muchas solicitudes simultáneas ralentizaron el navegador. El uso de sprites SVG es una muleta típica con la que descuida la lógica de trabajar en aras de ahorrar recursos. Ahora, el número de solicitudes no es tan importante, por lo que recomendamos insertar.
En primer lugar, afecta positivamente el rendimiento desde el punto de vista del visitante. Él ve cómo los íconos se cargan instantáneamente y no sufre los primeros segundos después de cargar la página. Cuando se usan imágenes sprite o PNG, la página que se está cargando se ralentiza un poco. Esto se siente especialmente si el visitante se desplaza inmediatamente por la página cargada: FPS se reducirá a 5-15 en dispositivos que no sean los mejores. Incrustar SVG en HTML ayuda a reducir la latencia de página (subjetiva, desde el punto de vista del cliente) y deshacerse de los frisos y saltos de fotogramas durante la carga.
4. Almacenamiento en caché con Service Worker y HTTP Cache
No tiene sentido volver a cargar una página que no ha cambiado y utilizar el tráfico de visitantes. Hay muchas estrategias de almacenamiento en caché, nos decidimos por la combinación más eficiente.
Vale la pena optimizar el uso no solo de la CPU / GPU, sino también de la red. Los dispositivos móviles son una limitación no solo en términos de recursos, sino también en términos de velocidad y tráfico de Internet. El almacenamiento en caché nos ayudó aquí. Le permite guardar respuestas a solicitudes HTTP y usarlas sin recibir una respuesta del servidor nuevamente.
Cuando consideramos una estrategia de almacenamiento en caché, elegimos usar Service Worker y HTTP Cache al mismo tiempo. Comencemos con el primero y más avanzado. Service Worker es un archivo js que puede controlar su página o archivo, interceptar y modificar solicitudes, así como solicitudes de memoria caché mediante programación. Actúa como un proxy entre el sitio y el servidor y determina su comportamiento fuera de línea. Todo esto se hace en el "frente", sin conectar un "back-end".
Service Worker tiene una tremenda variabilidad. Podemos programar el comportamiento como queramos. Por ejemplo, sabemos que un visitante que fue a la página número 1, con una probabilidad del 90%, irá a la página número 2. Le pedimos a SW que cargue la segunda página con el fondo cuando el visitante todavía está en la primera. Cuando vaya a él, la página se cargará al instante. Se puede usar para diferentes tareas:
- Sincronización de datos en segundo plano
- Calculadoras fuera de línea
- Plantillas personalizadas
- Reacción a una hora y fecha específicas.
Los archivos de Service Worker se pueden hacer en diferentes servicios. Recomendamos Workbox . Es bastante simple y le permite crear un conjunto de reglas mediante las cuales se realiza el almacenamiento en caché, por ejemplo, precache.
Los trabajadores de servicio no son compatibles con todos los navegadores, como IE, Safari o Chrome anteriores a la versión 40.0. Si el dispositivo no puede funcionar con él, sigue las reglas de almacenamiento en caché de HTTP Cache. Agregamos los siguientes encabezados HTTP a las páginas:
cache-control: no-cache last-modified: Mon, 06 May 2019 04:26:29 GMT
En este caso, el navegador agrega respuestas a las solicitudes al repositorio, pero con cada solicitud posterior envía un encabezado para verificar los cambios.
if-modified-since: Mon, 06 May 2019 04:26:29 GMT
En caso de que no se produzcan cambios, el navegador recibe una respuesta con el código 304 No modificado y utiliza el contenido almacenado en la memoria caché. Si se realizan cambios en el documento, la respuesta se devuelve con el código 200 y se escribe una nueva respuesta en el almacenamiento del navegador.
Un poco más tarde, cambiamos el método de almacenamiento en caché y verificamos la cantidad hash en el nombre del archivo. Asegura que el recurso seguirá siendo único. Por lo tanto, podríamos almacenar agresivamente el contenido en caché. El siguiente encabezado se agregó en respuesta:
Cache-control:max-age=31536000, immutable
La edad máxima indica el tiempo máximo de almacenamiento en caché, en nuestro caso es de 1 año. Un valor inmutable significa que dicha respuesta no necesita ser verificada por cambios.
Enlaces utiles
Estas no son todas las formas de optimizar el sitio. Esto podría agregar un rechazo de bootstrap, reducir la cantidad de elementos DOM y eventos, y mucho más. Pero estos son los consejos que nos ayudaron a hacer que el sitio sea rápido y receptivo, a pesar de la gran cantidad de animación.
Te invitamos a nuestro equipo
Siempre estamos buscando especialistas geniales en nuestra oficina en San Petersburgo para nuestras ambiciosas tareas: desarrolladores, evaluadores, diseñadores. A continuación hay vacantes: únete a nosotros.