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 vue1
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 deScrollDetector
pour interpoler des valeurs CSS arbitraires sur un autre élément, en fonction de l'élémentScrollDetector
.ParallaxativeAnimation
étendScrollAnimation
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
utilisanttransform: translateY(x)
fonctionnent en douceur. - Les
ParallaxativeAnimation
utilisanttranslateY(x)
fonctionnent, mais animaient de manière saccadée. - Les
ParallaxativeAnimation
utilisanttranslate3d(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 untranslateY()
. Cela pourrait affecter les performances, cartranslate3d()
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 appelezgetBoundingClientRect()
à 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 estwindow.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.
0 votes
Vous ne changez pas la hauteur de l'image en cours de défilement, n'est-ce pas ? D'autre part, et vous le savez probablement déjà, mais le scintillement du défilement est très courant lorsque l'élément défilé a une valeur de position autre que fixe. À mon avis, c'est le facteur numéro un qui rend le parallaxe si délicat, car le fait de fixer l'élément à la vue entraîne toutes sortes d'autres maux de tête de mise en page.
0 votes
Sauf si je fais quelque chose de très mal par accident, je ne fais que mettre à jour la hauteur de l'image lors du redimensionnement. Et oui, j'ai essayé de faire
position: fixe
, et cela aurait fonctionné si cela n'avait pas créé un nouveau contexte d'empilement plus masqué par le fait que le parent aitoverflow: caché
.0 votes
Aussi, j'ai réalisé que ma première tentative de réparation du calcul de la hauteur de l'arrière-plan était correcte après tout, donc c'est poussé vers mon dépôt. C'est fondamentalement bien maintenant, tant que je n'utilise pas un
scrollPixelsPerParallaxPixel
avec une valeur absolue inférieure à environ 5. Je vois beaucoup de recommandations pour simplement utiliserperspective
ettranslateZ
, mais j'ai eu des problèmes horribles pour le faire bien fonctionner, pourtant je pourrais être obligé d'y retourner si je veux que ces petites valeurs produisent de grands arrière-plans de manière acceptable.0 votes
Le parallaxe est certainement une PITA ! Dans votre cas, je crois que le navigateur fait parfois une redessein avant que la traduction soit appliquée, de sorte que pendant une fraction de seconde vous voyez l'élément dans sa position avant la traduction. Comme vous l'avez observé, le tremblement est moins évident avec de petites valeurs de défilement, c'est probablement pourquoi Rellax limite internement le mouvement parallaxe à une très petite valeur. Les éléments en position fixe ne rencontrent pas le même problème car ils ne bougent pas lorsque la page est défilée, seulement lorsque la traduction est appliquée.
0 votes
Je reçois cette erreur sur votre codepen
ScrollAnimation n'est pas défini
pouvez-vous vérifier pour que nous puissions reproduire le problème de défilement. Merci!0 votes
Je ne l'offrirai pas en tant que réponse réelle, juste en tant que commentaire. J'ai rencontré exactement les mêmes problèmes en appliquant les modifications CSS moi-même. J'ai depuis commencé à utiliser GSAP pour appliquer les transitions à mon élément et le résultat est génial!
0 votes
Les défilements fluides sont difficiles à réaliser en JavaScript. Je ne suis pas sûr de tout ce que vous faites dans le code source de votre framework, mais on m'a averti de ne pas trop compter, voire pas du tout, sur l'événement de défilement de la fenêtre, car il se déclenche un nombre incalculable de fois par seconde. Peut-être essayer de retarder avec setTimeout (pas setInterval)
0 votes
Comment écoutez-vous le défilement? Comment bouclez-vous, avec des temporisateurs ou
requestAnimationFrame
?