Catégories
Astuces et Design

Faisons une de ces animations de défilement de fantaisie utilisées sur les pages de produits Apple

Apple est bien connu pour les animations élégantes sur leurs pages de produits. Par exemple, lorsque vous faites défiler la page, les produits peuvent apparaître, les MacBook se plient et les iPhones tournent, tout en présentant le matériel, en montrant le logiciel et en racontant des histoires interactives sur la façon dont les produits sont utilisés.

Il suffit de regarder cette vidéo de l'expérience Web mobile pour l'iPad Pro:

Beaucoup d'effets que vous voyez là-bas ne sont pas créés uniquement en HTML et CSS. Que demandez-vous alors? Eh bien, cela peut être un peu difficile à comprendre. Même en utilisant les DevTools du navigateur, la réponse ne sera pas toujours révélée, car il est souvent impossible de voir au-delà d'un élément.

Examinons en profondeur l'un de ces effets pour voir comment il est créé afin que vous puissiez recréer certains de ces effets magiques dans nos propres projets. Plus précisément, reproduisons la page du produit AirPods Pro et l'effet de lumière changeante dans l'image du héros.

Le concept de base

L'idée est de créer une animation comme une séquence d'images en succession rapide. Vous savez, comme un flip book! Aucune scène WebGL complexe ni bibliothèque JavaScript avancée n'est nécessaire.

En synchronisant chaque image avec la position de défilement de l'utilisateur, nous pouvons lire l'animation lorsque l'utilisateur fait défiler (ou sauvegarder) la page.

Commencez avec le balisage et les styles

Le HTML et CSS pour cet effet est très facile car la magie se produit à l'intérieur du élément que nous contrôlons avec JavaScript en lui donnant un ID.

En CSS, nous allons donner à notre document une hauteur de 100vh et rendre notre 5⨉ plus haut que cela pour nous donner la longueur de défilement nécessaire pour faire ce travail. Nous associerons également la couleur d'arrière-plan du document à la couleur d'arrière-plan de nos images.

La dernière chose que nous allons faire est de positionner le , centrez-le et limitez la max-width et height il ne dépasse donc pas les dimensions de la fenêtre.

html {
  height: 100vh;
}


body {
  background: #000;
  height: 500vh;
}


canvas {
  position: fixed;
  left: 50%;
  top: 50%;
  max-height: 100vh;
  max-width: 100vw;
  transform: translate(-50%, -50%);
}

À l'heure actuelle, nous pouvons faire défiler la page (même si le contenu ne dépasse pas la hauteur de la fenêtre d'affichage) et notre reste en haut de la fenêtre. C'est tout le HTML et CSS dont nous avons besoin.

Passons au chargement des images.

Récupération des images correctes

Puisque nous travaillerons avec une séquence d'images (encore une fois, comme un flip book), nous supposerons que les noms de fichiers sont numérotés séquentiellement dans l'ordre croissant (par exemple 0001.jpg, 0002.jpg, 0003.jpg, etc.) dans le même répertoire.

Nous allons écrire une fonction qui renvoie le chemin du fichier avec le numéro du fichier image que nous voulons, en fonction de la position de défilement de l'utilisateur.

const currentFrame = index => (
  `https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${index.toString().padStart(4, '0')}.jpg`
)

Étant donné que le numéro d'image est un entier, nous devons le transformer en chaîne et utiliser padStart(4, '0') pour ajouter des zéros devant notre index jusqu'à ce que nous atteignions quatre chiffres pour correspondre à nos noms de fichiers. Ainsi, par exemple, le passage de 1 à cette fonction renverra 0001.

Cela nous donne un moyen de gérer les chemins d'image. Voici la première image de la séquence dessinée sur le élément:

Comme vous pouvez le voir, la première image est sur la page. À ce stade, il s'agit simplement d'un fichier statique. Ce que nous voulons, c'est le mettre à jour en fonction de la position de défilement de l'utilisateur. Et nous ne voulons pas simplement charger un fichier image, puis l'échanger en chargeant un autre fichier image. Nous voulons dessiner les images sur le et mettez à jour le dessin avec l'image suivante de la séquence (mais nous y reviendrons dans un instant).

Nous avons déjà créé la fonction pour générer le chemin du fichier image en fonction du nombre que nous lui transmettons donc ce que nous devons faire maintenant est de suivre la position de défilement de l'utilisateur et de déterminer le cadre d'image correspondant pour cette position de défilement.

Connexion d'images à la progression du défilement de l'utilisateur

Pour savoir quel nombre nous devons passer (et donc quelle image charger) dans la séquence, nous devons calculer la progression du défilement de l'utilisateur. Nous allons créer un écouteur d'événements pour effectuer le suivi et effectuer des calculs pour calculer l'image à charger.

Nous avons besoin de savoir:

  • Là où le défilement commence et se termine
  • La progression du défilement de l'utilisateur (c'est-à-dire un pourcentage de la distance parcourue par l'utilisateur sur la page)
  • L'image qui correspond à la progression du défilement de l'utilisateur

Nous utiliserons scrollTop pour obtenir la position de défilement vertical de l'élément, qui dans notre cas se trouve être le haut du document. Cela servira de valeur de point de départ. Nous obtiendrons la valeur de fin (ou maximale) en soustrayant la hauteur de la fenêtre de la hauteur de défilement du document. À partir de là, nous diviserons scrollTop valeur par la valeur maximale que l'utilisateur peut faire défiler vers le bas, ce qui nous donne la progression du défilement de l'utilisateur.

Ensuite, nous devons transformer cette progression de défilement en un numéro d'index qui correspond à la séquence de numérotation des images pour que nous puissions renvoyer l'image correcte pour cette position. Nous pouvons le faire en multipliant le nombre de progrès par le nombre d'images (images) que nous avons. Nous utiliserons Math.floor() pour arrondir ce nombre et l'envelopper Math.min() avec notre nombre d'images maximal afin qu'il ne dépasse jamais le nombre total d'images.

window.addEventListener('scroll', () => {  
  const scrollTop = html.scrollTop;
  const maxScrollTop = html.scrollHeight - window.innerHeight;
  const scrollFraction = scrollTop / maxScrollTop;
  const frameIndex = Math.min(
    frameCount - 1,
    Math.floor(scrollFraction * frameCount)
  );
});

Mise à jour avec l'image correcte

Nous savons maintenant quelle image nous devons dessiner à mesure que la progression du défilement de l'utilisateur change. C'est là que la magie de entre en jeu. a de nombreuses fonctionnalités intéressantes pour tout construire, des jeux et des animations à la conception de générateurs de maquettes et tout le reste!

L'une de ces fonctionnalités est une méthode appelée requestAnimationFrame qui fonctionne avec le navigateur pour mettre à jour d'une manière que nous ne pourrions pas faire si nous travaillions avec des fichiers image droits à la place. Voilà pourquoi je suis allé avec un approche au lieu, disons, d'un élément ou un

avec une image de fond.

requestAnimationFrame correspondra au taux de rafraîchissement du navigateur et activera l'accélération matérielle à l'aide de WebGL pour le rendre à l'aide de la carte vidéo de l'appareil ou des graphiques intégrés. En d'autres termes, nous obtiendrons des transitions super fluides entre les images – aucune image ne clignote!

Appelons cette fonction dans notre écouteur d'événements de défilement pour permuter les images lorsque l'utilisateur fait défiler la page vers le haut ou vers le bas. requestAnimationFrame prend un argument de rappel, nous allons donc passer une fonction qui mettra à jour la source de l'image et dessine la nouvelle image sur le :

requestAnimationFrame(() => updateImage(frameIndex + 1))

Nous augmentons la frameIndex de 1 car, alors que la séquence d'images commence à 0001.jpg, notre calcul de progression de défilement commence en fait à 0. Cela garantit que les deux valeurs sont toujours alignées.

La fonction de rappel que nous transmettons pour mettre à jour l'image ressemble à ceci:

const updateImage = index => {
  img.src = currentFrame(index);
  context.drawImage(img, 0, 0);
}

Nous passons le frameIndex dans la fonction. Cela définit la source d'image avec l'image suivante dans la séquence, qui est dessinée sur notre élément.

Encore mieux avec le préchargement d'image

Nous avons techniquement terminé à ce stade. Mais allez, on peut faire mieux! Par exemple, un défilement rapide entraîne un léger décalage entre les images. En effet, chaque nouvelle image envoie une nouvelle demande réseau, nécessitant un nouveau téléchargement.

Nous devons essayer de précharger les images des nouvelles demandes du réseau. De cette façon, chaque image est déjà téléchargée, ce qui rend les transitions beaucoup plus rapides et l'animation beaucoup plus fluide!

Tout ce que nous devons faire est de parcourir toute la séquence d'images et de les charger:

const frameCount = 148;


const preloadImages = () => {
  for (let i = 1; i < frameCount; i++) {
    const img = new Image();
    img.src = currentFrame(i);
  }
};


preloadImages();

Démo!

Une note rapide sur les performances

Bien que cet effet soit assez lisse, il est également beaucoup d'images. 148 pour être exact.

Peu importe la façon dont nous optimisons les images, ou la rapidité avec laquelle le CDN les dessert, le chargement de centaines d'images entraînera toujours une page gonflée. Disons que nous avons plusieurs instances de cela sur la même page. Nous pouvons obtenir des statistiques de performances comme celle-ci:

1 609 requêtes, 55,8 mégaoctets transférés, 57,5 ​​mégaoctets de ressources, temps de chargement de 30,45 secondes.

Cela peut convenir à une connexion Internet haut débit sans limitation des données, mais nous ne pouvons pas en dire autant des utilisateurs sans ces luxes. C'est un équilibre délicat à trouver, mais nous devons être conscients de l'expérience de chacun - et de la façon dont nos décisions les affectent.

Voici quelques mesures que nous pouvons prendre pour aider à trouver cet équilibre:

  • Chargement d'une seule image de secours au lieu de la séquence d'images entière
  • Création de séquences utilisant des fichiers image plus petits pour certains appareils
  • Permettre à l'utilisateur d'activer la séquence, peut-être avec un bouton qui démarre et arrête la séquence

Apple utilise la première option. Si vous chargez la page AirPods Pro sur un appareil mobile connecté à une connexion 3G lente et, hé, les statistiques de performances commencent à être bien meilleures:

8 demandes sur 111, 347 kilo-octets de 2,6 mégaoctets transférés, 1,4 mégaoctets de 4,5 mégaoctets de ressources, temps de chargement d'une minute et une seconde.

Oui, c'est toujours une page lourde. Mais il est beaucoup plus léger que ce que nous obtiendrions sans aucune considération de performances. C’est ainsi qu’Apple parvient à obtenir autant de séquences complexes sur une seule page.


Lectures complémentaires

Si vous êtes intéressé par la façon dont ces séquences d'images sont générées, un bon point de départ est la bibliothèque Lottie d'AirBnB. Les documents vous présentent les principes de base de la génération d'animations avec After Effects tout en offrant un moyen simple de les inclure dans des projets.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *