Effets liés au défilement

La définition d’un effet lié au défilement est un effet mis en œuvre dans une page web où quelque chose change en fonction de la position de défilement, par exemple une propriété de positionnement, dans le but de créer un effet de parallaxe. Cet article aborde les effets liés au défilement, leur impact sur les perfomances, les outils associés, et les techniques possibles d’atténuation.

Les effets de défilement expliqués

Souvent, les effets de défilement sont mis en œuvre en écoutant l’événement scroll puis en mettant à jour des éléments de la page d’une certaine façon (généralement les propriétés CSS position ou transform). Vous pouvez trouver un exemple de ces effets ici : CSS Scroll API: Use Cases.

Ces effets fonctionnent très bien avec les navigateurs où le défilement se fait de manière synchrone sur le thread (fil d’exécution) principal du navigateur. Toutefois, la plupart des navigateurs supportent aujourd’hui une sorte de défilement asynchrone afin de proposer aux utilisatrices et utilisateurs une expérience stable à 60 images par seconde. Dans le modèle de défilement asynchrone, la position visuelle du défilement est mise à jour dans le thread de composition, et est visible à l’utilisatrice et l’utilisateur avant que l’évènement scroll soit mis à jour dans le DOM et émis sur le thread principal. Cela signifie que les effets mis en œuvre seront légèrement en retard par rapport à la position du défilement telle que l’utilisatrice ou l’utilisateur la voit à l’écran. Cela peut rendre l’effet décalé ou saccadé, ce que nous voulons éviter.

Les exemples suivants présentent des effets qui fonctionneraient mal avec le scroll asynchrone, accompagnés de versions équivalentes qui fonctionnent correctement.

Exemple 1 : positionnement sticky

Voici une mise en œuvre d’un effet de positionnement sticky (adhérent), où la div "toolbar" va adhérer au haut de l’écran à mesure que vous défilez vers le bas.

<body style="height: 5000px" onscroll="document.getElementById('toolbar').style.top = Math.max(100, window.scrollY) + 'px'">
 <div id="toolbar" style="position: absolute; top: 100px; width: 100px; height: 20px; background-color: green"></div>
</body>

Cette mise en œuvre de positionnement sticky s’appuie sur un gestionnaire d’évènement scroll pour repositionner la div "toolbar". Comme le gestionnaire d’évènement scroll est exécuté dans le JavaScript sur le thread principal du navigateur, il sera asynchrone par rapport au défilement vu par l’utilisatrice ou l’utilisateur. Par conséquent, avec le défilement asynchrone, le gestionnaire d’évènement sera retardé par rapport au défilement visible, et ainsi la div ne restera pas visuellement fixe comme prévu. Au lieu de cela, elle va se déplacer avec le défilement de l’utilisatrice ou utilisateur, puis « sauter » vers sa position attendue quand le gestionnaire d’évènement scroll est exécuté. Ces déplacements et sauts constants produisent un effet visuel saccadé. Une manière de mettre cela en œuvre sans le gestionnaire d’évènement scroll est d’utiliser la propriété CSS conçue dans ce but :

<body style="height: 5000px">
 <div id="toolbar" style="position: sticky; top: 0px; margin-top: 100px; width: 100px; height: 20px; background-color: green"></div>
</body>

Cette version fonctionne bien avec le défilement asynchrone car la position de la div "toolbar" est mise à jour par le navigateur à mesure que l’utilisatrice ou l’utilisateur fait défiler la page.

Exemple 2 : défilement magnétique

Obsolète
Cette fonctionnalité a été supprimée des standards du Web. Bien que quelques navigateurs puissent encore la supporter, elle est en cours d'éradication. Ne l'utilisez ni dans d'anciens projets, ni dans de nouveaux. Les pages et applications Web l'utilisant peuvent cesser de fonctionner à tout moment.

Voici une mise en œuvre de défilement magnétique, où la position du défilement est ramenée vers une destination particulière quand le défilement de l’utilisatrice ou utilisateur s’arrête près de cette destination.

<body style="height: 5000px">
 <script>
    function snap(destination) {
        if (Math.abs(destination - window.scrollY) < 3) {
            scrollTo(window.scrollX, destination);
        } else if (Math.abs(destination - window.scrollY) < 200) {
            scrollTo(window.scrollX, window.scrollY + ((destination - window.scrollY) / 2));
            setTimeout(snap, 20, destination);
        }
    }
    var timeoutId = null;
    addEventListener("scroll", function() {
        if (timeoutId) clearTimeout(timeoutId);
        timeoutId = setTimeout(snap, 200, parseInt(document.getElementById('snaptarget').style.top));
    }, true);
 </script>
 <div id="snaptarget" style="position: relative; top: 200px; width: 100%; height: 200px; background-color: green"></div>
</body>

Dans cet exemple, un gestionnaire d’évènement scroll détecte si la position du défilement est à moins de 200 pixels du haut de la div "snaptarget". Le cas échéant, il déclenche une animation pour ramener la position du défilement sur le haut de la div. Comme cette animation est gérée par JavaScript sur le thread principal du navigateur, elle peut être interrompue par d’autres codes JavaScript s’exécutant dans d’autres onglets ou d’autres fenêtres. Par conséquent, il peut arriver que l’animation apparaisse saccadée et pas aussi fluide qu’attendu. À la place, utiliser la propriété CSS snap points permettra au navigateur de gérer l’animation de manière asynchrone, produisant un effet visuel fluide.

<body style="height: 5000px">
 <style>
    body {
        scroll-snap-type: proximity;
        scroll-snap-destination: 0 0;
    }
    #snaptarget {
        scroll-snap-coordinate: 0 -8px;
    }
 </style>
 <div id="snaptarget" style="position: relative; top: 200px; width: 100%; height: 200px; background-color: green"></div>
</body>

Cette version fonctionne de manière fluide dans le navigateur même si le thread principal du navigateur est occupé par de longs scripts.

Autres effets

Dans de nombreux cas, les effets liés au défilement peuvent être réécris en utilisant le CSS et en faisant en sorte qu’ils soient calculés sur le thread de composition. Toutefois, dans certains cas, les API actuelles proposées par les navigateurs ne permettent pas cela. Dans tous les cas, cependant, Firefox affichera un avertissement dans la console de développement (à partir de la version 46) s’il détecte la présence d’un effet lié au défilement sur une page. Les pages qui utilisent des effets de défilement sans écouter les évènements scroll en JavaScript n’auront pas cet avertissement. Voir l’article de blog Asynchronous scrolling in Firefox pour des exemples supplémentaires d’effets qui peuvent être implementés en utilisant CSS pour éviter les saccades.

Améliorations futures

À l’avenir, nous aimerions supporter davantage d’effets dans le compositeur. Dans ce but, nous avons besoin de vous (oui, vous !) pour nous en dire plus sur le genre d’effets liés au défilement que vous essayez de mettre en œuvre, afin que nous puissions trouver de bons moyens de les supporter dans le compositeur. Actuellement, il y a quelques propositions d’API qui permettraient de tels effets, et elles ont chacune leurs avantages et leurs inconvénients. Les propositions actuellement à l’étude sont :

  • Web Animations : Une nouvelle API pour contrôler precisément les animations web en JavaScript, avec une proposition additionelle de relier la position de défilement au temps et d’utiliser cela comme ligne temporelle pour l’animation.
  • CompositorWorker : Permet à JavaScript d’être exécuté sur le thread compositeur par petits fragments, à condition qu’il ne fasse pas baisser le nombre d’images par seconde.
  • Scroll Customization : Introduit une nouvelle API permettant au contenu de définir comment un delta de défilement est appliqué et consommé. Au moment où ces lignes sont écrites (), Mozilla ne prévoit pas de supporter cette proposition, mais elle est inclue pour l’exhaustivité.

Appel à l’action

Si vous avez des pensées et des opinions sur :

  • une ou plusieurs des propositions dans le contexte les effets liés au défilement 
  • des effets liés au défilement que vous essayez de mettre en œuvre ;
  • toute autre idée ou problème en relation ;

Contactez-nous ! Vous pouvez vous joindre à la discussion sur la liste de diffusion public-houdini.