CSS Deep-Dive : matrix3d() pour une barre de défilement personnalisée parfaite à l'image

Les barres de défilement personnalisées sont extrêmement rares, principalement les barres de défilement sont l’un des éléments restants sur le Web qui sont à peu près impossibles à mettre en forme (je vous regarde, sélecteur de date). Vous pouvez utiliser JavaScript pour créer le vôtre, mais et peuvent sembler lentes. Dans cet article, nous allons nous intéresser des matrices CSS non conventionnelles pour créer un conteneur de défilement personnalisé qui ne nécessite aucun JavaScript lors du défilement, juste du code de configuration.

TL;DR

Vous ne vous souciez pas des détails ? Il suffit d'examiner Démonstration de Nyan Cat et obtenir la bibliothèque ? Vous trouverez le code de la démonstration dans notre Dépôt GitHub

LAM;WRA (long et mathématique ; se lit quand même)

Il y a quelque temps, nous avons conçu un conteneur de défilement parallaxe cet article ? Elle est très intéressante et vaut bien la peine d'y consacrer du temps !). En repoussant les éléments à l'aide de CSS 3D les transformations, les éléments se déplacent plus lent que la vitesse de défilement réelle.

Récapitulatif

Commençons par récapituler le fonctionnement du conteneur de défilement parallaxe.

Comme le montre l'animation, nous avons obtenu l'effet de parallaxe en poussant des éléments "vers l'arrière" dans l'espace 3D, le long de l'axe Z. Le défilement d'un document le long de l'axe Y. Ainsi, si nous faisons défiler vers le bas de 100 pixels, par exemple, est traduit vers le haut de 100 px. Cela s'applique à tous les éléments, même celles qui se trouvent "plus loin". Mais car ils sont plus éloignés les mouvements observés à l'écran sont inférieurs à 100 pixels. l'effet de parallaxe souhaité.

Bien entendu, replacer un élément dans l'espace le rendra également plus petit, que nous corrigeons en redimensionnant l'élément à la hausse. Nous avons trouvé la formule mathématique exacte lorsque nous avons créé parallaxe donc je ne vais pas répéter tous les détails.

Étape 0: Que voulons-nous faire ?

Barres de défilement. C'est ce que nous allons créer. Mais avez-vous déjà pensé sur ce qu'ils font ? Certainement pas. Les barres de défilement sont un indicateur Quelle partie du contenu disponible est actuellement visible et quel pourcentage de sa progression en tant que lecteur. Si vous faites défiler l'écran vers le bas, la barre de défilement indiquent que vous progressez vers la fin. Si tout le contenu correspond dans la fenêtre d'affichage, la barre de défilement est généralement masquée. Si la hauteur du contenu est deux fois supérieure à la hauteur de la fenêtre d'affichage, la barre de défilement occupe la moitié de la hauteur de la fenêtre d'affichage. Contenus d'une valeur trois fois supérieure à La fenêtre d'affichage redimensionne la barre de défilement à un tiers de celle de la fenêtre d'affichage, etc. Le motif est visible. Au lieu de faire défiler la page, vous pouvez également cliquer sur la barre de défilement et la faire glisser pour vous déplacer le site plus rapidement. Ce comportement est surprenant un élément de ce type. Luttons une bataille à la fois.

Étape 1: Inversion

Bien, nous pouvons faire en sorte que les éléments se déplacent plus lentement que la vitesse de défilement avec CSS 3D comme indiqué dans l'article sur le défilement parallaxe. Pouvons-nous aussi inverser la direction ? Il s'avère que c'est possible et que c'est ainsi que une barre de défilement personnalisée et parfaite. Pour comprendre son fonctionnement, nous devons aborder quelques principes de base de la 3D CSS.

Pour obtenir n'importe quel type de projection de perspective au sens mathématique, finissent probablement par utiliser coordonnées homogènes. Je n'expliquerai pas en détail en quoi elles consistent et pourquoi elles fonctionnent, mais vous pouvez penser sous forme de coordonnées 3D, avec une quatrième coordonnée appelée w. Ce doit être égale à 1, sauf si vous souhaitez obtenir une distorsion de la perspective. Mer nous n'avons pas à nous soucier des détails de w, car nous n'allons pas utiliser une valeur autre que 1. Par conséquent, tous les points sont désormais des vecteurs à quatre dimensions [x, y, z, w=1] et, par conséquent, les matrices doivent en 4x4.

Vous pouvez voir que le CSS utilise des coordonnées homogènes sous la On parle de capuche lorsque vous définissez vos propres matrices 4x4 dans une propriété de transformation à l'aide de la fonction fonction matrix3d(). matrix3d utilise 16 arguments (car la matrice est 4 x 4), en spécifiant une colonne après l'autre. Nous pouvons donc utiliser cette fonction pour manuellement les rotations, les traductions, etc. c'est de la coordonnée W.

Avant de pouvoir utiliser matrix3d(), nous avons besoin d'un contexte 3D. En effet, sans Avec la 3D, il n'y aurait pas de distorsion de la perspective et pas besoin des coordonnées homogènes. Pour créer un contexte 3D, nous avons besoin d'un conteneur avec perspective et certains des éléments qu'il contient peuvent être transformés dans le nouvel a créé un espace 3D. Pour exemple:

Un extrait de code CSS qui déforme un élément div à l'aide de l'élément
    point de vue.

Les éléments à l'intérieur d'un conteneur de perspective sont traités par le moteur CSS comme suit:

  • Transformer chaque angle (vertex) d'un élément en coordonnées homogènes [x,y,z,w], par rapport au conteneur de la perspective.
  • Appliquer toutes les transformations de l'élément sous forme de matrices de droite à gauche.
  • Si l'élément de perspective est déroulant, appliquez une matrice de défilement.
  • Appliquez la matrice de perspective.

La matrice de défilement est une traduction le long de l'axe des ordonnées. Si nous faisons défiler 400 px, tous les éléments doivent être déplacés vers le haut de 400 px. La matrice de perspective est une qui "tire" les points au plus près du point de fuite en 3D et l'espace qu'elles occupent. Cela produit les deux effets de rendre les choses plus petites quand ils sont plus éloignés et ralentissent aussi la traduction. Ainsi, si un élément est repoussé, une traduction de 400 pixels de se déplacer à seulement 300 pixels sur l'écran.

Si vous souhaitez connaître tous les détails, lisez les spec sur les modèle de rendu, mais pour les besoins de cet article, j'ai simplifié l'algorithme ci-dessus.

Notre boîte se trouve dans un conteneur de perspective avec la valeur p pour perspective et que l'utilisateur fait défiler l'écran vers le bas n pixels.

Matrice perspective temps de la matrice temps de défilement des éléments de la matrice de transformation
  est égal à quatre par quatre matrice d'identité avec moins un sur p dans la quatrième ligne
  la troisième colonne une matrice d'identité quatre par quatre avec moins n dans la deuxième
  ligne quatrième colonne x élément 
matrice de transformation.

La première matrice est la matrice de perspective, la seconde est la matrice de défilement matricielle. En résumé: le rôle de la matrice de défilement est de faire déplacer un élément vers le haut lorsque nous défilent vers le bas, d'où le signe négatif.

Pour notre barre de défilement, nous voulons l'inverse : nous voulons que notre élément descendre lorsque nous faisons défiler la page vers le bas. Voici où nous pouvons utiliser une astuce: Inverser la coordonnée w des angles de notre cadre Si la coordonnée w est -1, toutes les traductions prendront effet dans le sens opposé. Alors, comment faire ça ? Le moteur CSS convertit les angles de notre zone en coordonnées homogènes et définit w sur 1. Il est temps pour matrix3d() de briller !

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    );
}

Cette matrice ne fait rien d'autre que la négation de w. Ainsi, lorsque le moteur CSS transformé chaque angle en un vecteur de la forme [x,y,z,1], la matrice le convertir en [x,y,z,-1].

Matrice d'identité quatre par quatre avec moins un sur p dans la quatrième ligne
  la troisième colonne une matrice d'identité quatre par quatre avec moins n dans la deuxième
  ligne quatrième colonne fois quatre par quatre matrice d'identité avec moins un dans le
  quatrième ligne quatrième colonne multipliée par un vecteur à quatre dimensions x, y, z, 1 est égal à quatre
  la matrice d'identité avec moins un sur p
dans la quatrième ligne, troisième colonne,
  moins n dans la deuxième ligne, quatrième colonne et moins un dans la quatrième ligne
  quatrième colonne est égal à x, y plus vecteur quatre dimensions n, z, moins z au-dessus
  p moins 1.

J'ai listé une étape intermédiaire pour montrer l'effet de la transformation de l'élément matricielle. Si vous n'êtes pas à l'aise avec les mathématiques matricielles, ce n'est pas grave. L'Eureka Dans la dernière ligne, nous ajoutons le décalage de défilement n à notre y coordonnées au lieu de la soustraire. L'élément est traduit vers le bas si nous faisons défiler la page vers le bas.

Toutefois, si nous plaçons cette matrice dans notre exemple, l'élément ne s'affichera pas. En effet, la spécification CSS exige que les sommet avec w < 0 bloque l'affichage de l'élément. Et puisque notre z dont la coordonnée est actuellement 0, p = 1, w est -1.

Heureusement, nous pouvons choisir la valeur de z ! Pour être sûrs d'obtenir w=1, nous devons pour définir z = -2.

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    translateZ(-2px);
}

Regarde, notre est de retour !

Étape 2: Bougez

Notre boîte est maintenant présente comme elle l'aurait été, de transformation. Pour le moment, il n'est pas possible de faire défiler le conteneur de perspective. le voir, mais nous savons que notre élément ira dans l'autre direction lorsqu'il que vous avez fait défiler. Faisons défiler le conteneur. Nous pouvons simplement ajouter un qui occupe de l'espace:

<div class="container">
    <div class="box"></div>
    <span class="spacer"></span>
</div>

<style>
/* … all the styles from the previous example … */
.container {
    overflow: scroll;
}
.spacer {
    display: block;
    height: 500px;
}
</style>

Et maintenant faites défiler la page. Le cadre rouge est déplacé vers le bas.

Étape 3: Indiquez une taille

Nous avons un élément qui descend lorsque la page fait défiler vers le bas. C'est la difficulté un peu compliqué, vraiment. Nous devons maintenant le styliser pour qu'il ressemble à une barre de défilement et le rendre un peu plus interactif.

Une barre de défilement se compose généralement d'un pouce et d'un titre, alors que en permanence. La hauteur du pouce est directement proportionnelle à la proportion est visible.

<script>
    const scroller = document.querySelector('.container');
    const thumb = document.querySelector('.box');
    const scrollerHeight = scroller.getBoundingClientRect().height;
    thumb.style.height = /* ??? */;
</script>

scrollerHeight est la hauteur de l'élément déroulant, tandis que scroller.scrollHeight correspond à la hauteur totale du contenu à faire défiler. scrollerHeight/scroller.scrollHeight est la fraction du contenu qui est visible. L'espace vertical couvert par les pouces doit être égal à proportion de contenu visible:

<ph type="x-smartling-placeholder">
</ph> style &quot;thumb point&quot; point par-dessus la hauteur de défilement sur la hauteur de défilement
  au-dessus de la hauteur de défilement des points de défilement si et seulement si la hauteur des points est au niveau du curseur
  est égal à la hauteur du conteneur de défilement multiplié par la hauteur du défilement sur le défilement point de défilement
  hauteur.
<script>
    // …
    thumb.style.height =
    scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
    // Accommodate for native scrollbars
    thumb.style.right =
    (scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>

La taille du pouce est assez bon, mais cela va beaucoup trop vite. C'est ici que nous pouvons récupérer notre technique parallaxe. Si nous repoussons l'élément, il se déplacera plus lentement faire défiler l'écran. Nous pouvons corriger la taille en l'agrandissant. Mais dans quelle mesure devrions-nous insister le rétablir exactement ? Faisons des calculs, vous l'avez deviné ! C'est la dernière fois, prometteurs.

L'information essentielle est que nous voulons que le bord inférieur du pouce s'aligner sur le bord inférieur de l'élément déroulant lorsqu'il est entièrement fait défiler vers le bas. Autrement dit: si nous avons fait défiler scroller.scrollHeight - scroller.height pixels, nous souhaitons que notre pouce soit traduit par scroller.height - thumb.height. Pour chaque pixel de défilement, que notre pouce déplace d'une fraction d'un pixel:

Facteur égal à la hauteur du point de défilement moins la hauteur du point du curseur au-dessus du conteneur de défilement
  « Dot drop_height » [hauteur du défilement par point] moins la hauteur des points de défilement.

C'est notre facteur de scaling. Nous devons maintenant convertir le facteur de scaling en le long de l'axe Z, comme nous l'avons fait pour le défilement parallaxe . Selon le section correspondante des spécifications: Le facteur de mise à l'échelle est égal à p/(p − z). On peut résoudre cette équation pour que z soit déterminer la mesure dont nous avons besoin pour traduire notre pouce le long de l'axe z. Mais gardez à l'esprit qu'en raison de la manipulation de nos coordonnées, nous devons traduire une -2px supplémentaires le long de z. Notez également que les transformations d'un élément sont appliquées de droite à gauche, ce qui signifie que toutes les traductions antérieures à la matrice spéciale Cependant, toutes les traductions qui suivent notre matrice spéciale le seront ! codifier ceci !

<script>
    // ... code from above...
    const factor =
    (scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
    thumb.style.transform = `
    matrix3d(
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, -1
    )
    scale(${1/factor})
    translateZ(${1 - 1/factor}px)
    translateZ(-2px)
    `;
</script>

Nous avons un scrollbar! Il s'agit simplement d'un élément DOM dont nous pouvons personnaliser le style. Une chose qui est en termes d'accessibilité, c'est de faire réagir le pouce cliquer et faire glisser, car de nombreux utilisateurs sont habitués à interagir avec une barre de défilement de cette façon. Pour ne pas allonger encore davantage cet article de blog, je ne vais pas vous expliquer les détails de cette partie. Consultez le Code de la bibliothèque pour en savoir plus.

Qu'en est-il d'iOS ?

Ah, mon vieil ami iOS Safari. Comme pour le défilement parallaxe, le problème ici. Comme nous faisons défiler un élément, nous devons spécifier -webkit-overflow-scrolling: touch, mais cela provoque un aplatissement 3D et l'intégralité l'effet de défilement cesse de fonctionner. Nous avons résolu ce problème dans le conteneur de défilement parallaxe en détectant iOS Safari et en utilisant position: sticky comme solution de contournement ; nous allons faire exactement la même chose ici. Consultez le article sur le parallaxe pour vous rafraîchir la mémoire.

Qu'en est-il de la barre de défilement du navigateur ?

Sur certains systèmes, nous devons gérer une barre de défilement native permanente. Auparavant, la barre de défilement ne pouvait pas être masquée (sauf avec un bouton pseudo-sélecteur non standard). Donc, pour le dissimuler, nous devons faire appel à un peu de hacker (sans maths). Nous plaçons notre l'élément de défilement dans un conteneur avec overflow-x: hidden et rendre l'élément de défilement plus large que le conteneur. La barre de défilement native du navigateur qui n'est plus visible.

Fin

En combinant tous ces éléments, nous pouvons créer un frame personnalisé comme celle de notre Démonstration de Nyan Cat

Si le chat Nyan ne s'affiche pas, cela signifie que vous rencontrez le problème suivant : un bug que nous avons détecté et signalé lors de la création de cette démonstration (cliquez sur le pouce pour faire apparaître le chat Nyan). Chrome est très efficace pour éviter les tâches inutiles comme peindre ou animer des choses qui sont hors écran. Malheureusement, notre des manigances matricielles font croire à Chrome que le gif du chat Nyan est en fait hors écran. J'espère que ce problème sera bientôt résolu.

Voilà. Cela demandait beaucoup de travail. je vous félicite d’avoir lu l’intégralité une chose. Voici quelques une véritable astuce pour faire fonctionner cela et cela n’en vaut probablement que rarement la peine, sauf lorsqu'une barre de défilement personnalisée est un élément essentiel de l'expérience. Toutefois, bon de savoir que c'est possible, non ? Le fait qu'il soit si difficile une barre de défilement personnalisée montre qu'il y a du travail à faire du côté du CSS. Mais n'ayez crainte ! À l'avenir, Houdini AnimationWorklet va générer des effets de défilement parfaits dans le cadre comme celui-ci.