web-dev-qa-db-fra.com

Modification de la transformation CSS sur le défilement: mouvement saccadé vs mouvement fluide

Je ne suis pas satisfait des bibliothèques de parallaxe existantes, alors j'essaie d'écrire les miennes. Mon cours actuel se compose de trois classes principales:

  • ScrollDetector suit la position de défilement d'un élément par rapport à l'écran; il a des fonctions pour renvoyer un flottant représentant sa position actuelle:
    • 0 Représente le bord supérieur de l'élément se trouvant au bord inférieur de la fenêtre
    • 1 Représente le bord inférieur de l'élément se trouvant au bord supérieur de la fenêtre
    • Toutes les autres positions sont interpolées/extrapolées linéairement.
  • ScrollAnimation utilise une instance 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 particulier d'une image d'arrière-plan qui doit défiler à un facteur précis de la vitesse de défilement de la fenêtre.

Ma situation actuelle est la suivante:

  • ScrollAnimations utilisant transform: translateY(x) fonctionne sans problème.
  • ParallaxativeAnimations utilisant translateY(x) fonctionne, mais anime saccadé.
  • ParallaxativeAnimations utilisant translate3d(0, x, 0) sont saccadés, mais pas aussi mal.
  • Les animations de la bibliothèque Rellax , qui utilisent translate3d(0, x, 0), fonctionnent parfaitement.

Vous pouvez voir la comparaison sur ce stylo . (La secousse apparaît mieux dans Firefox.) Ma bibliothèque est sur Bitbucket .

Je ne sais pas où se situe le problème dans ma bibliothèque et je ne sais pas comment le résoudre. Voici un collage abrégé de l'endroit où le gros du travail est effectué lors du défilement dans la classe ScrollAnimation qui fonctionne correctement:

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é:

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;
}

Le calcul ne semble pas plus compliqué, donc je ne peux pas comprendre comment cela affecte les performances de l'animation. Je pensais que la différence pourrait avoir été mon style sur l'image de parallaxe, mais dans le stylo ci-dessus, la version Rellax a exactement le même CSS, mais s'anime parfaitement en douceur. Rellax semble peut-être faire des calculs plus compliqués sur chaque image:

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 de Chrome Developer Tools est que le framerate n'est pas trop bas en dessous de 60 fps, donc peut-être que ce n'est pas que je fais trop de travail à chaque image, mais que je fais quelque chose de mathématiquement incorrect quand je calcule la position?

Donc je ne sais pas. Je suis clairement sur ma tête ici. Je suis désolé de lancer une bibliothèque entière sur StackOverflow et de dire "FIX IT", mais si quelqu'un peut dire ce que je fais mal, ou dites moi comment utiliser les outils de développement pour peut-être comprendre ce que je fais mal, je l'apprécierais beaucoup.


ÉDITER

D'accord, j'ai compris que le facteur le plus important dans la nervosité du défilement est la hauteur de l'élément en cours de traduction. J'ai eu un mauvais calcul dans ma bibliothèque qui faisait que les images d'arrière-plan étaient beaucoup plus hautes qu'elles ne devaient l'être quand ma propriété scrollPixelsPerParallaxPixel était élevée. Je suis en train d'essayer de corriger cela maintenant.

31
75th Trombone

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

La propriété CSS will-change indique aux navigateurs comment un élément devrait changer. Les navigateurs peuvent configurer 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 des travaux potentiellement coûteux avant qu'ils ne soient réellement nécessaires.

Vous pouvez soit le propulser comme:

function gogoJuice() {
  // The optimizable properties that are going to change
  self.elems[i].style.willChange = 'transform';
}

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

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

.parallax {
  will-change: transform;
}

Cette propriété est conçue comme une méthode permettant aux auteurs de faire connaître à l'agent utilisateur les propriétés susceptibles de changer à l'avance. Ensuite, le navigateur peut choisir d'appliquer les optimisations anticipées requises pour le changement de propriété avant que le changement de propriété ne se produise . Il est donc important de donner au navigateur un certain temps pour réellement effectuer les optimisations. Trouvez un moyen de prédire au moins légèrement à l'avance que quelque chose va changer, et définissez ensuite le changement.

3
Zze

Outre les calculs, vous pouvez 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 la performance.

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

0
manavortex