Complexité d'un conteneur de défilement infini

Résumé: Réutilisez vos éléments DOM et supprimez ceux qui sont éloignés fenêtre d'affichage. Utilisez des espaces réservés pour tenir compte des données différées. Voici un demo et le code de l'événement de défilement.

Les fenêtres à défilement infini s'affichent partout sur Internet. La liste d'artistes de Google Music est Premièrement, la chronologie Facebook et le flux en direct de Twitter en font partie. Toi et, avant d'atteindre le bas, un nouveau contenu apparaît comme par magie qui semble sortir de nulle part. C'est une expérience fluide pour les utilisateurs et une voir l'appel.

Toutefois, le défi technique lié à un conteneur de défilement infini est plus complexe qu'il semble être. L'éventail des problèmes que vous rencontrez lorsque vous voulez faire la bonne choseTM est vaste. Cela commence par des choses simples comme les liens dans le pied de page deviennent inaccessible, car le contenu continue à repousser le pied de page. Toutefois, les problèmes deviennent plus difficiles. Comment gérer un événement de redimensionnement lorsqu'un utilisateur tourne son téléphone en mode portrait ou paysage, ou comment éviter que votre téléphone grince s'interrompt péniblement lorsque la liste devient trop longue ?

La bonne choseTM

Nous avons pensé que cela suffisait pour proposer une implémentation de référence qui montre comment s'attaquer à tous ces problèmes de manière réutilisable maintenir des normes de performance.

Nous allons utiliser trois techniques pour atteindre notre objectif: le recyclage des DOM, les pierres tombales et l'ancrage du défilement.

Notre cas de démonstration sera une fenêtre de chat de type Hangouts dans laquelle nous pourrons faire défiler à travers les messages. Nous avons d'abord besoin d'une source infinie de discussions messages. Techniquement, aucun des systèmes de défilement infini n'existe vraiment mais avec la quantité de données disponibles pour y être qu'ils pourraient aussi bien être. Par souci de simplicité, nous allons simplement coder en dur un d'un ensemble de messages de chat et choisir le message, l'auteur et une image en pièce jointe occasionnellement à l'adresse avec un retard artificiel pour se comporter un peu plus un vrai réseau.

Capture d'écran de l'application Chat

Recyclage de DOM

Le recyclage DOM est une technique sous-utilisée pour limiter le nombre de nœuds DOM. La l'idée générale consiste à utiliser plutôt des éléments DOM déjà créés qui se trouvent hors écran. d'en créer de nouveaux. Certes, les nœuds DOM eux-mêmes sont bon marché, car chacune d'elles entraîne des coûts supplémentaires en termes de mémoire, de mise en page, de style et de peinture. Les appareils d'entrée de gamme seront nettement plus lents, s'ils ne sont pas complètement inutilisables si le votre site Web comporte un DOM trop volumineux à gérer. Gardez également à l'esprit que chaque nouvelle mise en page et une réapplication de vos styles, un processus qui se déclenche chaque fois qu'une classe est ajouté ou supprimé d'un nœud. Le coût de l'opération augmente avec un DOM plus grand. En recyclant vos nœuds DOM, vous devez conserver le nombre total considérablement plus bas, ce qui accélère tous ces processus.

Le premier obstacle est le défilement lui-même. Puisque nous n'aurons qu'un petit sous-ensemble de tous les éléments disponibles dans le DOM, à un moment donné, pour que la barre de défilement du navigateur reflète correctement la quantité de contenu théoriquement. Nous allons utiliser un élément sentinelle de 1 px par 1 px avec une transformation pour forcer l'élément qui contient les éléments (la piste) à avoir l'effet hauteur. Nous mettrons en avant chaque élément de la piste sur son propre calque pour vous assurer que la couche de la piste elle-même est complètement vide. Aucune couleur d'arrière-plan, rien. Si le calque de la piste n'est pas vide, il n'est pas éligible pour le et nous devrons stocker sur notre carte graphique une texture d'une hauteur de quelques centaines de milliers de pixels. Certainement pas viable sur appareil mobile.

À chaque fois que nous faisons défiler la page, nous vérifions si la fenêtre d'affichage est suffisamment proche la fin de la piste. Si c'est le cas, nous prolongerons la piste en déplaçant la sentinelle et en déplaçant les éléments qui ont quitté la fenêtre d'affichage vers le bas défilé et les remplir avec de nouveaux contenus.

Piste Sentinelle Fenêtre d'affichage

Il en va de même pour le défilement dans l'autre sens. Cependant, nous ne ferons jamais réduire la piste dans notre implémentation, de sorte que la position de la barre de défilement cohérentes.

Pierres tombales

Comme nous l'avons mentionné précédemment, nous essayons de faire en sorte que notre source de données se comporte dans le monde réel. avec la latence du réseau et tout le reste. Cela signifie que si notre les utilisateurs utilisent le défilement glissant, ils peuvent facilement faire défiler le dernier élément pour lesquelles nous disposons de données. Dans ce cas, nous plaçons un objet de pierre tombale : qui sera remplacé par l'élément avec le contenu réel une fois que les données sont arrivées. Les pierres tombales sont également recyclées et disposent d'un bassin séparé pour des éléments DOM réutilisables. Nous en avons besoin pour effectuer une transition efficace tombstone à l'élément rempli de contenu, ce qui serait sinon très perturbant pour l'utilisateur et pourrait en fait le faire perdre la trace de ce qu'il était vous concentrer.

Telles
mausolée. Excellent. Waouh !

Il est intéressant de noter que les objets réels peuvent avoir une hauteur l’élément de pierre tombale en raison de différentes quantités de texte par élément ou d’une pièce jointe l'image. Pour résoudre ce problème, nous ajusterons la position de défilement actuelle à chaque fois reçoivent des données et un tombstone est remplacé au-dessus de la fenêtre d'affichage, l'ancrage la position de défilement vers un élément plutôt que vers une valeur de pixel. Ce concept est appelé "ancrage de défilement".

Ancrage du défilement

Notre ancrage de défilement sera appelé à la fois lorsque les tombstones seront remplacés en ainsi que lorsque la fenêtre est redimensionnée (ce qui se produit également lorsque les appareils sont à l'envers !). Nous devrons déterminer quel élément le plus visible dans la fenêtre d'affichage. Comme cet élément ne peut être que partiellement visible, stocker le décalage par rapport au haut de l'élément, à l'endroit où commence la fenêtre d'affichage.

Diagramme d'ancrage de défilement.

Si la fenêtre d'affichage est redimensionnée et que la piste a changé, nous pouvons restaurer un situation qui semble visuellement identique à celle de l'utilisateur. Vous avez gagné ! À l'exception des images redimensionnées signifie que chaque élément a potentiellement changé de hauteur, alors comment faire jusqu'où le contenu ancré doit-il être placé ? Non ! Pour le savoir, nous devrions mettre en page chaque élément au-dessus de l'élément ancré et additionner tous leur hauteur ; cela pourrait entraîner une pause importante après un redimensionnement, et nous ne que vous voulez. Au lieu de cela, nous supposons que chaque élément ci-dessus a la même taille comme tombstone et ajuster la position de défilement en conséquence. Au fur et à mesure que les éléments sont dans la piste, nous ajustons la position de défilement, la mise en page au moment où elle est réellement nécessaire.

Mise en page

J'ai ignoré un détail important: la mise en page. Chaque recyclage d'un élément DOM aurait normalement replacé toute la piste, ce qui nous amènerait bien en dessous de notre cible de 60 images par seconde. Pour éviter cela, nous prenons la charge et utiliser des éléments positionnés de façon absolue avec des transformations. De cette façon, nous pouvons faire semblant que tous les éléments situés plus loin sur la piste qui occupent de l'espace alors qu'en réalité, il n'y a que de l'espace vide. Puisque nous faisons nous pouvons mettre en cache les positions où chaque élément se termine et nous pouvons charger immédiatement l'élément approprié à partir du cache lorsque l'utilisateur fait défiler l'écran vers l'arrière.

Idéalement, les éléments ne sont repeints qu'une seule fois lorsqu'ils sont rattachés au DOM. et ne pas être perturbé par les ajouts ou suppressions d'autres objets sur la piste. C'est-à-dire possible, mais uniquement avec des navigateurs récents.

Ajustements de pointe

Récemment, Chrome a ajouté la compatibilité avec le confinement des CSS, une fonctionnalité qui permet aux développeurs d'indiquer au navigateur qu'un élément est une limite la mise en page et la peinture. Nous faisons nous-mêmes de la mise en page ici, c'est donc une application de confinement. Chaque fois que nous ajoutons un élément à la piste, nous savons les autres éléments n'ont pas besoin d'être affectés par la remise en page. Ainsi, chaque élément doit obtenir contain: layout. Nous ne voulons pas non plus affecter le reste de notre site Web, Par conséquent, la piste elle-même doit également recevoir cette directive de style.

Nous avons également envisagé d'utiliser IntersectionObservers comme mécanisme de détection l'utilisateur a fait suffisamment défiler l'écran pour que nous puissions commencer à recycler les éléments et charger de nouveaux données. Cependant, les IntersectionObservers sont spécifiés comme ayant une latence élevée (comme si à l'aide de requestIdleCallback). Nous pourrions donc se sentir moins réactifs avec IntersectionObservers que sans. Même notre implémentation actuelle utilisant scroll événement souffre de ce problème, car les événements de défilement sont déclenchés sur la base du "meilleur effort". Le compositor Worklet d'Houdini serait la solution haute-fidélité à ce problème.

Ce n'est toujours pas parfait

Notre implémentation actuelle du recyclage DOM n'est pas idéale, car elle ajoute tous les éléments qui passent à travers la fenêtre d'affichage, au lieu de se contenter de celles qui sont réellement à l'écran. Cela signifie que lorsque vous faites défiler vraiment rapidement, tellement de travail pour la mise en page et la peinture sur Chrome qu'il est difficile de suivre le rythme. Vous allez terminer ne voient rien d'autre que l'arrière-plan. Ce n'est pas la fin du monde, mais certainement quelque chose à améliorer.

Nous espérons que vous comprenez à quel point des problèmes simples peuvent devenir difficiles lorsque vous voulez Combinez une expérience utilisateur exceptionnelle avec des performances élevées. Avec les progressive web apps deviennent des expériences essentielles sur les téléphones mobiles, gagner en importance et que les développeurs Web devront continuer à investir dans en utilisant des modèles qui respectent les contraintes de performances.

L'intégralité du code se trouve dans notre dépôt. Nous avons fait Nous faisons de notre mieux pour les réutiliser, mais nous ne les publierons pas en tant que bibliothèque npm ou en tant que dépôt distinct. L'utilisation principale est éducative.