JavaScript Performance bei Scroll-Event-Handlern verbessern

Vor einiger Zeit bin ich mal wieder in einem WordPress-Plugin auf etwas JavaScript-Code gestoßen, der zwar funktional genau das macht was er sollte, der aber in Bezug auf die Performance eher schlecht umgesetzt war. In diesem Artikel möchte ich euch beschreiben, wieso genau der Code nicht optimal ist und wie man diesen Schrittweise verbessern kann.

Der zu optimierende Code

Sehen wir uns zuerst einmal den Code an, der optimiert werden soll. Er stammt aus dem WordPress-Plugin WP-Smooth-Scroll und der zu optimierende Teil sieht wie folgt aus:

$(window).on( 'scroll', false, function() {
	var windowTop = $(window).scrollTop();
	if (windowTop > 200 ) {
		$( '#wp-smooth-scroll' ).slideDown('slow');
	} 
	else{
		$('#wp-smooth-scroll').slideUp('slow');
	}
});

Die Probleme im Codebeispiel

Wieso genau ist ein solcher Code problematisch. Schauen wir uns den Code doch mal genauer an. In der ersten Zeile wird das Window-Objekt mit jQuery selektiert. Anschließend wird darauf ein Event-Callback-Handler für das Scroll-Event registriert. Bis hierhin wäre der Code noch OK. Aber schon in der zweiten Zeile stoßen wir auf zwei problematische Funktionsaufrufe.

Wiederholte Selektion des Window-Objekts

Das erste Problem, das ich in Zeile zwei sehe ist die wiederholte Selektion des Window-Objekts. Bereits in der ersten Zeile haben wir diese Selektion bereits durchgeführt und könnten und daher eine weitere Selektion sparen, indem wir das Objekt zwischenspeichern. Eine erste Optimierung könnte also wie folgt aussehen:

// Schlecht: Wiederholte Selektion
$(window).on( 'scroll', false, function() {
	var windowTop = $(window).scrollTop();
	// ...
});

// Besser: Zwischenspeichern des Objekts
var $window = $(window);

$window.on( 'scroll', false, function() {
	var windowTop = $window.scrollTop();
	// ...
});

Ein einfacher Performancetest hat in Chrome gezeigt, dass das Caching der Selektion des Window-Objekts einen wiederholten Aufruf um etwa das Fünffache beschleunigen konnte. Da das Objekt bei jedem Scroll-Schritt benötigt wird, kann hier also sehr viel Zeit gespart werden.

Ermittlung der Scroll-Position

Es gibt aber in Zeile noch einen weiteren Funktionsaufruf, der optimiert werden kann. Um die Scroll-Position zu ermitteln wird die Funktion scrollTop() von jQuery verwendet. Diese Funktion ist ziemlich komplex, denn sie ermöglicht nicht nur das Ermitteln der Scroll-Position, sondern auch das scrollen des Fensters zu eben dieser. Es gibt allerdings auch native Browser-Funktionen, die eine Ermittlung der Scroll-Position ermöglichen. Da diese aber nicht für alle Browser gleich ist, benötigt man effektiv zwei Methoden. Die kann man allerdings sehr einfach in einer Zeile wie folgt kombinieren:

// Schlecht: Verwendung der komplexen jQuery-Funktion
var windowTop = $window.scrollTop();

// Besser: Verwendung der nativen Browser-Funktionen
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

Wiederholter Aufruf der Animation

Das nächste Problem ist nicht ganz so einfach zu sehen. Es befindet sich innerhalb der Bedingungen. Euch wird vermutlich schon aufgefallen sein, dass auch hier das Objekt nicht zwischengespeichert wurde. Aber das eigentliche Problem ist ein anderes. Könnt ihr es erkennen?

OK, ich löse es gerne auf. Die Bedingung prüft, ob der Browser mehr als 200 Pixel gescrollt ist. Falls das der Fall ist, wird der Pfeil zum Scrollen des Fensters eingeblendet. Wenn der Nutzer also bei der Scroll-Position 201 angelangt ist, wird die Animation zur Einblendung gestartet. Was aber passiert, wenn der Nutzer weiterscrollt? Richtig, die Animation wird nochmals gestartet! Obwohl also eine erneute Animation nicht notwendig ist, wird die Funktion slideUp() bei jeder Scroll-Position größer als 200 erneut aufgerufen. Zwar wird keine erneute Animation ausgeführt, aber natürlich nur, weil jQuery (in einer vermutlich recht komplexen Funktion) prüft, ob eine Animation notwendig ist. Wäre es nicht besser, wenn wir uns irgendwie sehr einfach merken könnten, ob eine Animation notwendig ist?

Der optimierte Code

Sehen wir uns also den Code mal mit allen Optimierungen, inklusive einer Lösung für das letzte Problem an:

window._isScrolled = false;

var wp_smooth_scroll = $( '#wp-smooth-scroll' );

$(window).on( 'scroll', false, function() {

	var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

	if ( (scrollTop > 200) && ! window._isScrolled ) {
		window._isScrolled = true;
		wp_smooth_scroll.slideDown('slow');
	} else if ( scrollTop <= 200 && window._isScrolled ) {
		window._isScrolled = false;
		wp_smooth_scroll.slideUp('slow');
	}
});

In der ersten Zeile setzen wir ein Flag auf das Window-Objekt. Dieses verwenden wir dann in den beiden Bedingungen in den Zeilen 9 und 12. Wir prüfen also nicht mehr nur, ob der Browser eine bestimmte Scroll-Position hat, sondern zusätzlich, ob er sich schon zuvor innerhalb des gewünschten Bereichs befunden hat. Ist dies der Fall ist keine Animation notwendig. Zusätzlich enthält der Code auch noch die anderen besprochenen Optimierungen. Durch die native Ermittlung der Scroll-Position, müssen wir nicht einmal mehr das Window-Objekt zwischenspeichern, da wir es nur einmal verwenden.

Visualisierung der Optimierung

Manche von euch werden sich jetzt vielleicht fragen, ob diese ganzen Optimierungen überhaupt notwendig sind. Wie oft wird die Bedingung denn beim Scrollen überhaupt ausgewertet? Und was passiert eigentlich, wenn man auf den Pfeil klickt und die Seite per Animation nach oben scrollt? Wird dann auch das Scroll-Event ausgelöst? Ja, das wird es, sehr häufig sogar. Um das zu verdeutlichen habe ich mal zwei Videos erstellt, die zeigen, wie oft die Funktionen für die Animation aufgerufen werden:

Vor der Optimierung

http://youtu.be/QRRrBF3jymM

Nach der Optimierung

http://youtu.be/CgJxD1tSE4g

Wie ihr im ersten Video sehen könnt, werden die beiden Funktionen für die Animation sehr häufig aufgerufen, sowohl beim normalen Scrollen, als auch nach dem Klick auf den Button. Nach der Optimierungen wird hingegen jede Funktion nur genau am Übergang zwischen den beiden Bereichen einmalig aufgerufen.

Fazit

Mit jQuery ist es wirklich sehr einfach, tolle Funktionen und Effekte auf einer Website umzusetzen. Leider vergessen viele dabei, welche Auswirkungen eine schnell umgesetzte Lösung haben kann. Ich hoffe ich konnte euch mit diesem Beitrag ein wenig für dieses wichtige Thema sensibilisieren und ihr achtet in Zukunft bei der Verwendung von Event-Handlern darauf, wie oft diese eventuell aufgerufen werden und wie komplex der Code darin ist. Eine Sache solltet ihr aber auf jeden Fall mitgenommen haben: Bitte immer eure Objekte zwischenspeichern! 🙂

Veröffentlicht von

Bernhard ist fest angestellter Webentwickler, entwickelt in seiner Freizeit Plugins, schreibt in seinem Blog über WordPress und andere Themen, treibt sich gerne bei den WP Meetups in Berlin und Potsdam herum und läuft nach Feierabend den ein oder anderen Halbmarathon.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert