31 votes

Changer le transform CSS sur le défilement : mouvement saccadé vs. mouvement fluide

Je suis insatisfait des bibliothèques parallax existantes, donc j'essaie d'écrire la mienne. Ma bibliothèque actuelle se compose de trois classes principales :

  • ScrollDetector suit la position de défilement d'un élément par rapport à l'écran ; elle comporte des fonctions pour retourner un flottant représentant sa position actuelle :
    • 0 représente le bord supérieur de l'élément étant au bord inférieur de la vue
    • 1 représente le bord inférieur de l'élément étant au bord supérieur de la vue
    • Toutes les autres positions sont interpolées/extrapolées linéairement.
  • ScrollAnimation utilise une instance de ScrollDetector pour interpoler des valeurs CSS arbitraires sur un autre élément, en fonction de l'élément ScrollDetector.
  • ParallaxativeAnimation étend ScrollAnimation pour le cas spécial d'une image de fond qui doit défiler à un facteur précis de la vitesse de défilement de la fenêtre.

Ma situation actuelle est la suivante :

  • Les ScrollAnimation utilisant transform: translateY(x) fonctionnent en douceur.
  • Les ParallaxativeAnimation utilisant translateY(x) fonctionnent, mais animaient de manière saccadée.
  • Les ParallaxativeAnimation utilisant translate3d(0, x, 0) sont saccadées, mais pas aussi mal.
  • Les animations de la bibliothèque Rellax, qui utilisent translate3d(0, x, 0), fonctionnent parfaitement en douceur.

Vous pouvez voir la comparaison sur ce stylo. (La saccade est plus visible dans Firefox.) Ma bibliothèque est sur Bitbucket.

Je ne sais pas où se trouve le problème dans ma bibliothèque et je ne sais pas comment le résoudre. Voici un extrait de code où le travail intense est effectué lors du défilement dans la classe ScrollAnimation qui fonctionne en douceur :

getCSSValue(set, scrollPosition) {
    return set.valueFormat.replace(set.substitutionString, ((set.endValue - set.startValue) * scrollPosition + set.startValue).toString() + set.unit)
}

updateCSS() {
    var cssValues = [];

    var scrollPosition = this.scrollDetector.clampedRelativeScrollPosition();

    var length = this.valueSets.length;
    for(var i = 0; i < length; i++) {
        cssValues.push(getCSSValue(valueSets[i], scrollPosition) );
    }

    this.setCSS(cssValues);
    this.ticking = false;
}

requestUpdate() {
    if(!this.ticking) {
        requestAnimationFrame(() => { this.updateCSS(); });
    }

    this.ticking = true;
}

Et voici l'équivalent dans la classe ParallaxativeAnimation qui est saccadée :

updateCSS() {
    var scrollPosition = this.scrollDetector.clampedRelativeScrollPosition();
    var cssValues = [];

    var length = this.valueSets.length;
    for(var i = 0; i < length; i++) {
        var scrollTranslate = -((this.scrollTargetSize - this.valueSets[i].parallaxSize) * scrollPosition);

        cssValues.push(
            this.valueSets[i].valueFormat.replace(this.valueSets[i].substitutionString, scrollTranslate.toString() + 'px')
        );
    }

    this.setCSS(cssValues);
    this.ticking = false;
}

requestUpdate() {
    if(!this.ticking) {
        requestAnimationFrame(() => { this.updateCSS(); });
    }

    this.ticking = true;
}

Les calculs ne semblent pas plus compliqués, donc je ne peux pas comprendre comment cela affecte la performance de l'animation. Je pensais que la différence pourrait être dans le style de l'image parallax, mais dans le stylo ci-dessus, la version Rellax a exactement le même CSS, mais elle s'anime parfaitement en douceur. Rellax semble peut-être faire des calculs plus compliqués à chaque trame:

var updatePosition = function(percentage, speed) {
  var value = (speed * (100 * (1 - percentage)));
  return self.options.round ? Math.round(value) : Math.round(value * 100) / 100;
};

//
var update = function() {
  if (setPosition() && pause === false) {
    animate();
  }

  // loop again
  loop(update);
};

// Transform3d on parallax element
var animate = function() {
  for (var i = 0; i < self.elems.length; i++){
    var percentage = ((posY - blocks[i].top + screenY) / (blocks[i].height + screenY));

    // Subtracting initialize value, so element stays in same spot as HTML
    var position = updatePosition(percentage, blocks[i].speed) - blocks[i].base;

    var zindex = blocks[i].zindex;

    // Move that element
    // (Set the new translation and append initial inline transforms.)
    var translate = 'translate3d(0,' + position + 'px,' + zindex + 'px) ' + blocks[i].transform;
    self.elems[i].style[transformProp] = translate;
  }
  self.options.callback(position);
};

La seule chose que je peux vraiment dire depuis les outils de développement de Chrome est que le taux de rafraîchissement ne descend pas en dessous de 60 ips, donc peut-être que je ne fais pas trop de travail à chaque trame, mais que je fais quelque chose de mathématiquement incorrect lorsque je calcule la position ?

Donc je ne sais pas. Je suis clairement dépassé ici. Je suis désolé de jeter toute une bibliothèque sur StackOverflow et dire "CORRIGEZ", mais si quelqu'un peut dire ce que je fais de mal, ou me dire comment utiliser les outils de développement pour peut-être comprendre ce que je fais de mal, je vous en serais très reconnaissant.


ÉDITION

D'accord, j'ai compris que le facteur le plus important pour la saccade du défilement est la hauteur de l'élément en cours de traduction. J'avais une mauvaise estimation dans ma bibliothèque qui faisait que les images de fond étaient beaucoup plus hautes qu'elles ne le devraient lorsque ma propriété scrollPixelsPerParallaxPixel était élevée. Je suis en train d'essayer de corriger cela maintenant.

4 votes

Deux choses : Pour réaliser une comparaison équitable des deux versions de votre bibliothèque, vous devriez les appliquer à des éléments identiques. Comparer les performances de défilement d'un titre par rapport aux performances de défilement d'une grande image d'arrière-plan n'est pas une comparaison équitable. J'aimerais voir les deux versions fonctionner sur une image d'arrière-plan. Deuxièmement, je remarque que Rellax applique une transformation translate3d() à l'élément de défilement, tandis que vous appliquez un translateY(). Cela pourrait affecter les performances, car translate3d() forcera le navigateur à décharger le rendu vers le GPU.

1 votes

Un suivi de mon commentaire précédent: translate3d() ne semble pas améliorer le scroll jank. Une autre chose à considérer est la fréquence à laquelle vous mesurez les éléments. Il semble que vous appelez getBoundingClientRect() à chaque redessin. Vous devriez seulement avoir besoin de mesurer l'élément initialement et lorsqu'un redimensionnement s'est produit. Idéalement, la seule mesure dont vous avez besoin à chaque redessin est window.scrollY.

0 votes

Merci pour le conseil de mettre en cache le décalage, je l'ai implémenté. Cependant, la saccade est en fait causée par le fait que l'élément traduit est trop grand en hauteur. Veuillez consulter ma modification. Plus l'élément est grand, plus il y a de saccades lors du défilement. Il y a une erreur dans mes calculs qui entraîne parfois des arrière-plans parallaxe trop grands. Ma première tentative de correction a simplement rendu l'élément trop grand à des moments différents ; je travaille toujours sur l'algorithme correct pour calculer cette hauteur.

4voto

Zze Points 8878

Vous pouvez obtenir une amélioration des performances visuelles en implémentant will-change sur les éléments. Il est pris en charge dans les navigateurs récents (à l'exception de Edge et de IE).

La propriété CSS will-change indique aux navigateurs comment un élément est censé changer. Les navigateurs peuvent mettre en place des optimisations avant qu'un élément ne soit réellement modifié. Ces types d'optimisations peuvent augmenter la réactivité d'une page en effectuant un travail potentiellement coûteux avant qu'il ne soit réellement nécessaire.

Vous pouvez l'implémenter de deux façons :

function gogoJuice() {
  // Les propriétés optimisables qui vont changer
  self.elems[i].style.willChange = 'transform';
}

function sleepNow() {
  self.elems[i].style.willChange = 'auto';
}

Ou simplement dans le CSS de l'élément que vous modifiez :

.parallax {
  will-change: transform;
}

Cette propriété est destinée à permettre aux auteurs de faire savoir à l'agent utilisateur quelles sont les propriétés susceptibles de changer à l'avance. Ensuite, le navigateur peut choisir d'appliquer toutes les optimisations nécessaires à l'avance pour le changement de propriété avant que le changement de propriété ne se produise réellement. Il est donc important de laisser au navigateur le temps d'effectuer réellement les optimisations. Trouvez un moyen de prédire au moins légèrement à l'avance qu'un changement va se produire, et définissez will-change à ce moment-là.

0voto

manavortex Points 11

En dehors des calculs, vous pourriez essayer de l'exécuter de manière asynchrone en utilisant Promise :

await Promise.all([
  loop(update);
]);

juste pour voir si cela a un impact positif sur les performances.

Je commenterais, mais je n'ai pas encore assez de réputation.

-1voto

user5886944 Points 89

Vous voulez éviter de toucher le DOM ou de modifier quoi que ce soit à l'écran Si vous voulez changer quelque chose à l'écran, évitez de mettre à jour le DOM.

Modifier un attribut d'élément touchera le DOM, ce qui le rendra lent.

Vous obtiendrez de meilleures performances en implémentant l'animation en utilisant uniquement du CSS pur.

Vous obtiendrez des performances encore meilleures en implémentant l'animation en utilisant le canvas, tout en appliquant des techniques d'optimisation comme appeler l'API canvas le moins de fois possible.

0 votes

Le DOM n'est qu'une interface dans le backend du navigateur. Changer la transformation CSS sur des éléments avec leur propre couche empilée/composite contourne les parties les plus coûteuses de la boucle de rendu et passe directement à la composition. En théorie, cela devrait être l'un des moyens les plus rapides de mettre à jour un élément.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X