Les belles animations sont depuis longtemps installées dans les tendances du design. Les concepteurs d'interface utilisateur créent des carrousels élaborés, des téléchargements, des animations de menu et d'autres décorations, tandis que les développeurs frontaux les traduisent en code. Mais le site ne doit pas seulement être beau, mais aussi fonctionner rapidement.
Un frontend moderne devrait optimiser son code. Cela est particulièrement vrai pour les produits dans lesquels la majorité de l'audience se rend sur le site à partir d'appareils mobiles. Certaines méthodes d'animation sont en retard même dans Chrome sur les ordinateurs les plus performants, mais devraient fonctionner sans problème sur un smartphone moyen.
Nos développeurs ont utilisé un grand nombre de techniques qui ont permis d'optimiser le site et d'accélérer son travail. J'ai rassemblé 4 des plus intéressants d'entre eux. Nous partageons des connaissances qui seront utiles aux débutants et aux professionnels, ainsi que des liens vers des tutoriels utiles.

1. Animation sur SCSS
Le site a beaucoup d'animation. Nous avons besoin du navigateur pour le lire avec une fréquence d'images stable de 60 ips. En CSS pur, c'est difficile à faire, nous utilisons donc SCSS.
Pour créer les curseurs, nous avons utilisé la bibliothèque Swiper . Pour un curseur horizontal, l'utilisation de cette bibliothèque est justifiée, car nous devions fournir un support pour svayp de l'utilisateur. Mais pour un carrousel sans fin vertical, Swiper n'est pas nécessaire, il n'y a pas d'interaction avec l'utilisateur. Par conséquent, nous voulions répéter la même fonctionnalité en utilisant uniquement des fonctionnalités CSS.
La première condition et la plus importante lorsque vous travaillez avec une animation CSS est de n'utiliser que les propriétés de transformation et d'opacité. Les navigateurs sont capables d'optimiser indépendamment l'animation de ces propriétés et de produire 60 fps stables. Cependant, l'utilisation de @keyframes seule n'est pas possible d'écrire différentes animations pour différents éléments, et animer chaque élément individuellement en CSS pur prend trop de temps. Comment écrire rapidement l'animation dont nous avons besoin? Nous avons choisi SCSS, le dialecte SASS - une extension CSS plus fonctionnelle.
Analysons l'utilisation de SCSS en utilisant l'exemple de notre curseur vertical.

À notre disposition, il y a un conteneur dont la hauteur est égale à la hauteur des trois éléments du carrousel. À l'intérieur, il y a un autre conteneur contenant tous les éléments du carrousel.
<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>
Nous supprimons la visibilité des éléments du conteneur qui vont le dépasser et définissons la hauteur des blocs.
.b-vertical-carousel-slider { position: relative; overflow: hidden; height: $itemHeight * 3; .vertical-carousel-slider-item { height: $itemHeight; } }
Le calcul de l'animation ne change que si le nombre d'éléments du carrousel change. Ensuite, nous écrivons un mixin qui prend un paramètre d'entrée - $itemCount
@mixin verticalSlideAnimation($itemCount) { }
Dans le mixage, générez une image clé pour chaque élément, définissez son état initial et utilisez :nth-child
déterminer l'animation de l'élément.
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; } }
De plus, pendant l'animation, nous déplacerons uniquement les éléments le long de l'axe y et changerons l'échelle de l'élément au centre.
L'élément carrousel indique:
- La paix
- Décalage Y
- Décalage Y
- Décalage Y avec décroissance
Chaque élément montera $itemCount
fois, augmentera une fois et diminuera une fois pendant le mouvement. Par conséquent, nous allons générer et calculer l'animation pour chacun des mouvements ascendants.
@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); } } } }
Reste à définir quelques variables et fonctions:
$animationTime
- temps d'interpolation de mouvement$stepDuration
- temps total d'exécution d'une étape d'animation ( $animationTime
+ temps de repos du carrousel)getPercentForStep($step, $itemCount, $offset)
- une fonction qui renvoie en pourcentage le point extrême de l'un des états.getTranslate($step)
- renvoie la traduction en fonction de l'étape d'animation
Exemples d'implémentations de fonctions:
@function getPercentForStep($step, $count, $offset: 0) { @return 100% * $step / $count - $offset; } @function getTranslate($step) { @return translate3d(0, -100% * $step, 0); }
Nous avons un carrousel de travail avec un élément croissant au milieu. Reste à faire de l'ombre sous les éléments croissants. Initialement, chaque élément du carrousel avait un pseudo-élément: après, qui à son tour avait une ombre. Afin de ne pas animer la propriété shadow, nous avons utilisé la propriété opacity pour elle, c'est-à-dire juste montré et caché l'ombre.
Mais dans la nouvelle implémentation d'une telle solution, vous devez générer de nombreuses images clés supplémentaires pour chaque pseudo-élément. Nous avons décidé de le faire plus facilement: il y aura un bloc avec une ombre et il occupera l'espace exactement sous l'élément central du carrousel.
Ajoutez un div responsable de l'ombre.
<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>
Nous le stylisons et ajoutons des animations.
@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; }
Dans ce cas, vous n'avez pas besoin de générer et de penser à un moyen d'animer les ombres sous chaque élément, nous n'animons qu'un bloc, il a deux états - il est visible et caché
En conséquence, nous avons un carrousel CSS pur qui anime avec uniquement des propriétés bien optimisées. Cela permet au navigateur d'utiliser l'accélération matérielle pour le rendu. D'où le profit tangible par rapport à l'animation JS:
- Lors du défilement des pages avec des animations sur des appareils faibles, les cadres étaient clairement ignorés dans les animations JS, laissant tomber FPS à 15-20. Les animations CSS ont clairement amélioré les choses. Sur ces appareils, ce chiffre était d'au moins 50-55 FPS.
- Nous nous sommes débarrassés du travail d'un module tiers où il n'était pas nécessaire.
- L'animation sera jouée même lorsque JS est désactivé
L'animation JS doit être utilisée si un contrôle strict de chaque image est nécessaire: pause, lecture inversée, rembobinage, réaction aux actions de l'utilisateur. Dans d'autres cas, nous vous recommandons d'utiliser du CSS pur.
Liens utiles
2. Utilisation de l'API Intersection Observer
L'animation que le visiteur du site ne voit pas est jouée hors de vue et charge le CPU. À l'aide de l'intersection Observer, nous déterminons quel type d'animation est visible à l'écran en ce moment, et nous ne la lisons que.
Toutes les astuces du dernier paragraphe peuvent être combinées avec l'observateur d'intersection. Cet outil permet de ne pas charger le navigateur avec une animation que le visiteur du site ne voit pas. Auparavant, des «auditeurs» d'événements gourmands en ressources étaient utilisés pour comprendre si un visiteur regardait un élément animé et cela ne produisait pas un «épuisement» fort. La différence entre l'utilisation d'une animation en dehors de la fenêtre et l'utilisation d'écouteurs était minime. L'API Intersection Observer nécessite moins de ressources et permet de lire uniquement l'animation visible par le visiteur.
Sur notre site, l'animation n'est activée que lorsqu'un élément apparaît dans la fenêtre. Si nous ne faisions pas cela, alors les pages seraient surchargées avec une exécution constante d'animations en boucle qui restaient hors de vue. L'API Intersection Observer vous permet de surveiller l'intersection de l'élément avec le parent ou la portée du document.
Exemple d'implémentation
À titre d'exemple, nous montrons comment optimiser l'animation sur JS. L'idée est simple - l'animation est jouée pendant que l'élément animé est dans la fenêtre. Pour l'implémentation, nous utilisons l'API Intersection Observer.
Ajouter is-paused
gestion de classe is-paused
aux styles
.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; } }
C'est-à-dire lorsque cette classe apparaît, l'animation est suspendue.
Nous décrivons maintenant la logique de l'ajout et de la suppression de cette classe
if (window.IntersectionObserver) { const el = document.querySelector('.b-vertical-carousel-slider'); const observer = new IntersectionObserver(intersectionObserverCallback); observer.observe(el); }
Ici, nous avons créé une instance de IntersectionObserver, spécifié la fonction intersectionObserverCallback
, qui fonctionnera lorsque la visibilité changera.
Définissez maintenant intersectionObserverCallback
function intersectionObserverCallback(entries){ if (entries[0].intersectionRatio === undefined) { return; } helperDOM.toggleClass(el, 'is-paused', entries[0].intersectionRatio <= 0); };
Maintenant, l'animation n'est jouée que sur les éléments visibles. Dès que l'élément a disparu du champ de vision, l'animation est suspendue. Lorsque le visiteur revient vers lui, la lecture continue.
Liens utiles
3. Rendu SVG
Un grand nombre d'images ou l'utilisation de sprites provoque des frises et des retards dans les premières secondes après le chargement. L'intégration de SVG dans le code de la page aide à rendre visuellement le chargement plus fluide.
Lorsque nous avons sélectionné des méthodes pour travailler avec des images, nous avions 2 options d'optimisation: intégrer SVG en HTML ou utiliser des sprites. Nous nous sommes installés sur l'intégration. Nous intégrons le code XML de chaque image directement dans le code HTML des pages. Cela augmente légèrement leur taille, mais SVG est servi en ligne immédiatement avec le document.
De nombreux développeurs continuent d'utiliser des sprites SVG. Quelle est l'essence de la méthode: un tableau d'images (par exemple, des icônes) est collecté dans une grande toile d'image, qui est appelée un sprite. Lorsque vous devez afficher une icône spécifique, un sprite est appelé, après quoi les coordonnées de la pièce spécifique sur laquelle il se trouve sont données. Cela a été fait depuis longtemps, même sur la première version de HTTP. Les sprites ont aidé à mettre en cache le fichier de manière agressive et à réduire le nombre de demandes de serveur. C'était important car de nombreuses demandes simultanées ralentissaient le navigateur. L'utilisation des sprites SVG est une béquille typique avec laquelle vous négligez la logique de travailler pour économiser des ressources. Maintenant, le nombre de demandes n'est pas si important, nous recommandons donc l'incorporation.
Tout d'abord, elle affecte positivement les performances du point de vue du visiteur. Il voit comment les icônes se chargent instantanément et ne souffre pas les premières secondes après le chargement de la page. Lorsque vous utilisez des images sprite ou PNG, la page en cours de chargement ralentit un peu. Cela est particulièrement ressenti si le visiteur fait immédiatement défiler la page chargée - FPS passera à 5-15 sur les appareils non top. L'incorporation de SVG dans HTML permet de réduire la latence de chargement des pages (subjective, du point de vue du client) et de se débarrasser des frises et des chutes de trames pendant le chargement.
4. Mise en cache à l'aide de Service Worker et du cache HTTP
Cela n'a aucun sens de recharger une page qui n'a pas changé et d'utiliser le trafic des visiteurs. Il existe de nombreuses stratégies de mise en cache, nous avons opté pour la combinaison la plus efficace.
Il vaut la peine d'optimiser l'utilisation non seulement du CPU / GPU, mais aussi du réseau. Les appareils mobiles sont une limitation non seulement en termes de ressources, mais aussi en termes de vitesse et de trafic Internet. La mise en cache nous a aidés ici. Il vous permet d'enregistrer les réponses aux requêtes HTTP et de les utiliser sans recevoir à nouveau de réponse du serveur.
Lorsque nous avons envisagé une stratégie de mise en cache, nous avons choisi d'utiliser Service Worker et HTTP Cache en même temps. Commençons par le premier et le plus avancé. Service Worker est un fichier js qui peut contrôler sa page ou son fichier, intercepter et modifier les demandes, ainsi que les demandes de cache par programme. Il agit comme un proxy entre le site et le serveur et détermine leur comportement hors ligne. Tout cela se fait sur le «front», sans connecter de «backend».
Le travailleur de service a une énorme variabilité. Nous pouvons programmer le comportement à notre guise. Par exemple, nous savons qu'un visiteur qui est allé à la page numéro 1, avec une probabilité de 90%, ira à la page numéro 2. Nous demandons à SW de charger la deuxième page avec l'arrière-plan lorsque le visiteur est toujours sur la première. Lorsqu'il y accède, la page se charge instantanément. Il peut être utilisé pour différentes tâches:
- Synchronisation des données d'arrière-plan
- Calculatrices hors ligne
- Modèles personnalisés
- Réaction à une heure et une date spécifiques.
Les fichiers Service Worker peuvent être créés dans différents services. Nous recommandons Workbox . Il est assez simple et vous permet de créer un ensemble de règles par lesquelles la mise en cache est effectuée, par exemple, la mise en cache préalable.
Les techniciens de service ne prennent pas en charge tous les navigateurs, tels que IE, Safari ou Chrome avant la version 40.0. Si le périphérique ne peut pas fonctionner avec lui, il suit les règles de mise en cache du cache HTTP. Nous avons ajouté les en-têtes HTTP suivants aux pages:
cache-control: no-cache last-modified: Mon, 06 May 2019 04:26:29 GMT
Dans ce cas, le navigateur ajoute des réponses aux demandes au référentiel, mais à chaque demande suivante, il envoie un en-tête pour vérifier les modifications.
if-modified-since: Mon, 06 May 2019 04:26:29 GMT
Dans le cas où aucun changement ne s'est produit, le navigateur reçoit une réponse avec le code 304 Non modifié et utilise le contenu stocké dans le cache. Si des modifications sont apportées au document, la réponse est renvoyée avec le code 200 et une nouvelle réponse est écrite dans la mémoire du navigateur.
Un peu plus tard, nous avons changé la méthode de mise en cache et vérifié la quantité de hachage dans le nom du fichier. Il garantit que la ressource restera unique. Par conséquent, nous pourrions mettre en cache de manière agressive le contenu. L'en-tête suivant a été ajouté en réponse:
Cache-control:max-age=31536000, immutable
Max-age indique la durée maximale de mise en cache, dans notre cas, elle est de 1 an. Une valeur immuable signifie qu'une telle réponse n'a pas besoin d'être vérifiée pour les changements.
Liens utiles
Ce ne sont pas tous des moyens d'optimiser le site. Cela pourrait ajouter un rejet de bootstrap, réduisant le nombre d'éléments et d'événements DOM, et bien plus encore. Mais ce sont les conseils qui nous ont aidés à rendre le site rapide et réactif, malgré la grande quantité d'animation.
Nous vous invitons à notre équipe
Nous recherchons toujours des spécialistes sympas dans notre bureau de Saint-Pétersbourg pour nos tâches ambitieuses: développeurs, testeurs, designers. Ci-dessous, il y a des postes vacants - rejoignez-nous.