Einführung
HTML5 bietet hervorragende Tools, um das visuelle Erscheinungsbild von Webanwendungen zu verbessern. Das gilt insbesondere für Animationen. Doch mit dieser neuen Macht kommen auch neue Herausforderungen. Diese Herausforderungen sind eigentlich gar nicht so neu und es kann manchmal sinnvoll sein, Ihre freundliche Büronachbarin, die Flash-Programmiererin, zu fragen, wie sie ähnliche Probleme in der Vergangenheit gemeistert hat.
Bei der Arbeit mit Animationen ist es jedoch äußerst wichtig, dass Nutzer diese Animationen als flüssig wahrnehmen. Wir müssen uns darüber im Klaren sein, dass flüssige Animationen nicht einfach durch eine Erhöhung der Frames pro Sekunde über eine kognitive Schwelle hinaus erreicht werden können. Unser Gehirn ist leider schlauer als das. Sie werden feststellen, dass 30 echte Frames pro Sekunde (fps) viel besser sind als 60 fps, bei denen in der Mitte nur wenige Frames fehlen. Menschen mögen keine eckigen Formen.
In diesem Artikel erfahren Sie, mit welchen Tools und Techniken Sie die Nutzerfreundlichkeit Ihrer eigenen Anwendung verbessern können.
Die Strategie
Wir möchten Sie keineswegs davon abhalten, mit HTML5 tolle, visuell ansprechende Apps zu erstellen.
Wenn Sie feststellen, dass die Leistung noch etwas besser sein könnte, kehren Sie hierher zurück und lesen Sie nach, wie Sie die Elemente Ihrer Anwendung verbessern können. Es kann natürlich hilfreich sein, einige Dinge von Anfang an richtig zu machen, aber lassen Sie sich davon nicht davon abhalten, produktiv zu sein.
Visual Fidelity++ mit HTML5
Hardwarebeschleunigung
Die Hardwarebeschleunigung ist ein wichtiger Meilenstein für die Gesamtleistung des Renderings im Browser. Im Allgemeinen werden Aufgaben, die sonst von der Haupt-CPU berechnet würden, an die GPU (Graphics Processing Unit) im Grafikadapter des Computers weitergeleitet. Dies kann zu enormen Leistungssteigerungen und zu einer geringeren Ressourcennutzung auf Mobilgeräten führen.
Diese Aspekte Ihres Dokuments können durch die GPU beschleunigt werden
- Allgemeines Layout-Compositing
- CSS3-Übergänge
- 3D-Transformationen in CSS3
- Canvas-Zeichnung
- WebGL-3D-Zeichnung
Die Beschleunigung von Canvas und WebGL sind zwar Funktionen mit speziellem Zweck, die möglicherweise nicht für Ihre spezifische Anwendung gelten, die ersten drei Aspekte können jedoch dazu beitragen, dass nahezu jede App schneller wird.
Was kann beschleunigt werden?
Bei der GPU-Beschleunigung werden gut definierte und spezifische Aufgaben an spezielle Hardware ausgelagert. Im Allgemeinen wird Ihr Dokument in mehrere „Ebenen“ unterteilt, die unabhängig von den beschleunigten Aspekten Ihrer Seite sind. Diese Ebenen werden mit der herkömmlichen Renderpipeline gerendert. Die GPU wird dann verwendet, um die Ebenen auf einer einzelnen Seite zusammenzuführen und die Effekte anzuwenden, die „on the fly“ beschleunigt werden können. Ein mögliches Ergebnis ist, dass ein Objekt, das auf dem Bildschirm animiert wird, während der Animation nicht einmal neu angeordnet werden muss.
Sie müssen der Rendering-Engine also so einfach wie möglich machen, zu erkennen, wann sie die GPU-Beschleunigung anwenden kann. Dazu ein Beispiel:
Das funktioniert zwar, aber der Browser weiß nicht wirklich, dass Sie etwas tun, das von einem Menschen als flüssige Animation wahrgenommen werden soll. Sehen wir uns an, was passiert, wenn Sie dasselbe visuelle Erscheinungsbild mit CSS3-Übergängen erzielen:
Wie der Browser diese Animation implementiert, ist für den Entwickler nicht sichtbar. Das bedeutet wiederum, dass der Browser Tricks wie die GPU-Beschleunigung anwenden kann, um das definierte Ziel zu erreichen.
Es gibt zwei nützliche Befehlszeilen-Flags für Chrome, die beim Debuggen der GPU-Beschleunigung helfen:
--show-composited-layer-borders
zeigt einen roten Rahmen um Elemente, die auf GPU-Ebene manipuliert werden. Hilft dabei, zu bestätigen, dass Ihre Manipulationen in der GPU-Ebene erfolgen.--show-paint-rects
Alle Änderungen, die nicht von der GPU stammen, werden gerendert. Dadurch wird ein heller Rahmen um alle Bereiche gezogen, die neu gerendert werden. Sie sehen, wie der Browser die Malbereiche optimiert.
Safari hat ähnliche Laufzeit-Flags, die hier beschrieben werden.
CSS3-Übergänge
Mit CSS-Übergängen können Sie Stilanimationen ganz einfach erstellen. Sie sind aber auch eine intelligente Leistungsfunktion. Da eine CSS-Übergang vom Browser verwaltet wird, kann die Genauigkeit der Animation erheblich verbessert und in vielen Fällen hardwarebeschleunigt werden. Derzeit gibt es hardwaregestützte CSS-Transformationen nur in WebKit (Chrome, Safari, iOS), aber sie werden bald auch in anderen Browsern und auf anderen Plattformen eingeführt.
Du kannst transitionEnd
-Ereignisse verwenden, um leistungsstarke Kombinationen zu erstellen. Derzeit bedeutet das Erfassen aller unterstützten Ereignisse zum Ende von Übergängen, webkitTransitionEnd transitionend oTransitionEnd
zu beobachten.
Viele Bibliotheken haben jetzt Animation APIs eingeführt, die Übergänge nutzen, sofern vorhanden, und andernfalls auf die Standard-DOM-Animation zurückgreifen. Beispiele: scripty2, YUI transition, jQuery animate enhanced.
CSS3 Translate
Sicher haben Sie schon einmal die X‑/Y‑Position eines Elements auf der Seite animiert. Wahrscheinlich haben Sie die Eigenschaften „left“ und „top“ des Inline-Stils manipuliert. Bei 2D-Transformationen können wir dieses Verhalten mit der Funktion translate()
nachahmen.
Wir können das mit DOM-Animation kombinieren, um das Beste aus beiden Welten zu nutzen.
<div style="position:relative; height:120px;" class="hwaccel">
<div style="padding:5px; width:100px; height:100px; background:papayaWhip;
position:absolute;" id="box">
</div>
</div>
<script>
document.querySelector('#box').addEventListener('click', moveIt, false);
function moveIt(evt) {
var elem = evt.target;
if (Modernizr.csstransforms && Modernizr.csstransitions) {
// vendor prefixes omitted here for brevity
elem.style.transition = 'all 3s ease-out';
elem.style.transform = 'translateX(600px)';
} else {
// if an older browser, fall back to jQuery animate
jQuery(elem).animate({ 'left': '600px'}, 3000);
}
}
</script>
Wir verwenden Modernizr, um CSS-2D-Transformationen und CSS-Übergänge zu testen. Falls diese Funktionen unterstützt werden, verwenden wir „translate“, um die Position zu ändern. Wenn dies mit einem Übergang animiert wird, kann die Hardwarebeschleunigung des Browsers mit hoher Wahrscheinlichkeit genutzt werden. Um dem Browser einen weiteren Schubs in die richtige Richtung zu geben, verwenden wir die „magische CSS-Kugel“ von oben.
Wenn der Browser weniger leistungsfähig ist, verwenden wir jQuery, um das Element zu verschieben. Mit dem jQuery Transform-Polyfill-Plug-in von Louis-Remi Babe können Sie das Ganze automatisieren.
window.requestAnimationFrame
requestAnimationFrame
wurde von Mozilla eingeführt und von WebKit weiterentwickelt. Ziel war es, eine native API für das Ausführen von Animationen bereitzustellen, unabhängig davon, ob sie DOM-/CSS-basiert oder auf <canvas>
oder WebGL basieren. Der Browser kann gleichzeitige Animationen in einem einzigen Reflow- und Neumalzyklus optimieren, was zu einer realistischeren Animation führt. Dazu gehören beispielsweise JS-basierte Animationen, die mit CSS-Übergängen oder SVG-SMIL synchronisiert sind. Wenn Sie die Animation in einem Tab ausführen, der nicht sichtbar ist, wird sie vom Browser nicht weiter ausgeführt. Das bedeutet eine geringere CPU-, GPU- und Arbeitsspeichernutzung und eine viel längere Akkulaufzeit.
Weitere Informationen zur Verwendung von requestAnimationFrame
finden Sie im Artikel requestAnimationFrame für intelligente Animationen von Paul Irish.
Profilerstellung
Wenn Sie feststellen, dass die Geschwindigkeit Ihrer Anwendung verbessert werden kann, ist es an der Zeit, sich mit dem Profiling zu befassen, um herauszufinden, wo Optimierungen den größten Nutzen bringen könnten. Optimierungen wirken sich häufig negativ auf die Wartbarkeit Ihres Quellcodes aus und sollten daher nur bei Bedarf angewendet werden. Mithilfe des Profilings können Sie feststellen, welche Teile Ihres Codes am meisten von einer Leistungssteigerung profitieren würden.
JavaScript-Profiling
JavaScript-Profiler geben Ihnen einen Überblick über die Leistung Ihrer Anwendung auf JavaScript-Funktionsebene, indem sie die Zeit messen, die für die Ausführung jeder einzelnen Funktion vom Start bis zum Ende benötigt wird.
Die Bruttoausführungszeit einer Funktion ist die Gesamtzeit, die für die Ausführung von oben nach unten benötigt wird. Die Nettoausführungszeit ist die Bruttoausführungszeit abzüglich der Zeit, die für die Ausführung der Funktionen benötigt wurde, die von der Funktion aufgerufen wurden.
Einige Funktionen werden häufiger aufgerufen als andere. Profiler geben in der Regel die Zeit an, die für die Ausführung aller Aufrufe benötigt wurde, sowie die durchschnittliche, minimale und maximale Ausführungszeit.
Weitere Informationen finden Sie in der Dokumentation zu Chrome DevTools zum Thema Profiling.
Das DOM
Die Leistung von JavaScript hat einen großen Einfluss darauf, wie flüssig und reaktionsschnell Ihre Anwendung ist. JavaScript-Profiler messen nicht nur die Ausführungszeit Ihres JavaScript-Codes, sondern auch indirekt die Zeit, die für DOM-Vorgänge aufgewendet wird. Diese DOM-Vorgänge sind oft die Ursache für Leistungsprobleme.
function drawArray(array) {
for(var i = 0; i < array.length; i++) {
document.getElementById('test').innerHTML += array[i]; // No good :(
}
}
Im Code oben wird beispielsweise fast keine Zeit für die Ausführung von JavaScript aufgewendet. Es ist dennoch sehr wahrscheinlich, dass die drawArray-Funktion in Ihren Profilen angezeigt wird, da sie sehr ineffizient mit dem DOM interagiert.
Tipps und Tricks
Anononyme Funktionen
Anonyme Funktionen lassen sich nicht einfach profilieren, da sie keinen Namen haben, unter dem sie im Profiler angezeigt werden könnten. Es gibt zwei Möglichkeiten, dieses Problem zu umgehen:
$('.stuff').each(function() { ... });
rewrite an:
$('.stuff').each(function workOnStuff() { ... });
Es ist nicht allgemein bekannt, dass JavaScript Funktionsausdrücke unterstützt. Dadurch werden sie im Profiler perfekt dargestellt. Bei dieser Lösung gibt es ein Problem: Der benannte Ausdruck fügt den Funktionsnamen in den aktuellen lexikalischen Bereich ein. Das kann andere Symbole überlagern. Seien Sie also vorsichtig.
Lange Funktionen profilern
Angenommen, Sie haben eine lange Funktion und vermuten, dass ein kleiner Teil davon der Grund für Ihre Leistungsprobleme sein könnte. Es gibt zwei Möglichkeiten, herauszufinden, welcher Teil das Problem verursacht:
- Die richtige Methode: Refaktorisieren Sie Ihren Code, damit er keine langen Funktionen enthält.
- Die böse Methode, um Dinge zu erledigen: Fügen Sie Ihrem Code Anweisungen in Form von benannten Funktionen hinzu, die sich selbst aufrufen. Wenn Sie ein wenig vorsichtig vorgehen, ändert sich dadurch die Semantik nicht und Teile Ihrer Funktion werden im Profiler als einzelne Funktionen angezeigt:
js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... }
Denken Sie daran, diese zusätzlichen Funktionen nach dem Profiling zu entfernen. Sie können sie auch als Ausgangspunkt verwenden, um Ihren Code zu überarbeiten.
DOM-Profiling
Die neuesten Chrome Web Inspector-Entwicklertools enthalten die neue „Zeitachse“, die eine Zeitachse der Low-Level-Aktionen des Browsers zeigt. Anhand dieser Informationen können Sie Ihre DOM-Vorgänge optimieren. Sie sollten die Anzahl der „Aktionen“ reduzieren, die der Browser ausführen muss, während Ihr Code ausgeführt wird.
Die Zeitachse kann eine enorme Menge an Informationen enthalten. Sie sollten daher versuchen, minimale Testfälle zu erstellen, die Sie unabhängig voneinander ausführen können.
Das Bild oben zeigt die Ausgabe der Zeitachse für ein sehr einfaches Script. Im linken Bereich werden die vom Browser ausgeführten Vorgänge in chronologischer Reihenfolge angezeigt. Die Zeitleiste im rechten Bereich zeigt die tatsächliche Zeit an, die für einen einzelnen Vorgang benötigt wurde.
Weitere Informationen zur Zeitachse Ein alternatives Tool zum Profiling im Internet Explorer ist DynaTrace Ajax Edition.
Strategien zur Profilerstellung
Aspekte herausstellen
Wenn Sie Ihre Anwendung profilieren möchten, versuchen Sie, die Aspekte ihrer Funktionalität, die zu einer Verlangsamung führen könnten, so genau wie möglich zu ermitteln. Führen Sie dann einen Profillauf aus, bei dem nur Teile Ihres Codes ausgeführt werden, die für diese Aspekte Ihrer Anwendung relevant sind. So lassen sich die Profilierungsdaten leichter interpretieren, da sie nicht mit Codepfaden vermischt sind, die nicht mit dem tatsächlichen Problem zusammenhängen. Gute Beispiele für einzelne Aspekte Ihrer Anwendung:
- Startzeit (Profiler aktivieren, Anwendung neu laden, warten, bis die Initialisierung abgeschlossen ist, Profiler beenden)
- Klicken Sie auf eine Schaltfläche und die nachfolgende Animation (Profiler starten, auf Schaltfläche klicken, warten, bis die Animation abgeschlossen ist, Profiler beenden).
GUI-Profiling
Es kann schwieriger sein, in einem GUI-Programm nur den richtigen Teil der Anwendung auszuführen, als wenn Sie beispielsweise den Raytracer Ihrer 3D-Engine optimieren. Wenn Sie beispielsweise die Ereignisse erfassen möchten, die beim Klicken auf eine Schaltfläche auftreten, lösen Sie möglicherweise nebensächliche Mouseover-Ereignisse aus, die Ihre Ergebnisse weniger aussagekräftig machen. Versuchen Sie, das zu vermeiden.
Programmatic Interface
Es gibt auch eine programmatische Schnittstelle, um den Debugger zu aktivieren. So können Sie genau steuern, wann das Profiling beginnt und wann es endet.
Starten Sie ein Profiling mit:
console.profile()
Profiling mit folgenden Tools beenden:
console.profileEnd()
Wiederholbarkeit
Achten Sie beim Profiling darauf, dass Sie Ihre Ergebnisse tatsächlich reproduzieren können. Nur dann können Sie feststellen, ob sich Ihre Optimierungen tatsächlich positiv ausgewirkt haben. Außerdem wird das Profiling auf Funktionsebene im Kontext des gesamten Computers durchgeführt. Es ist keine exakte Wissenschaft. Die Ausführung einzelner Profile kann von vielen anderen Faktoren auf Ihrem Computer beeinflusst werden:
- Ein unabhängiger Timer in Ihrer eigenen Anwendung, der ausgelöst wird, während Sie etwas anderes messen.
- Der Garbage Collector bei der Arbeit
- Ein anderer Tab in Ihrem Browser, der im selben Betriebs-Thread viel Arbeit erledigt.
- Ein anderes Programm auf Ihrem Computer belegt die CPU und verlangsamt so Ihre Anwendung.
- Plötzliche Änderungen des Erdgravitationsfeldes.
Es ist auch sinnvoll, denselben Codepfad in einer Profiling-Sitzung mehrmals auszuführen. So verringern Sie den Einfluss der oben genannten Faktoren und die langsamen Teile können noch deutlicher hervorstechen.
Messen, optimieren, messen
Wenn Sie einen langsamen Bereich in Ihrem Programm gefunden haben, überlegen Sie, wie Sie das Ausführungsverhalten verbessern können. Nachdem Sie den Code geändert haben, müssen Sie sich noch einmal anmelden. Wenn Sie mit dem Ergebnis zufrieden sind, fahren Sie fort. Wenn Sie keine Verbesserung feststellen, sollten Sie die Änderung rückgängig machen und nicht einfach so lassen, weil „es ja nicht schaden kann“.
Optimierungsstrategien
DOM-Interaktion minimieren
Eine gängige Methode zur Verbesserung der Geschwindigkeit von Webanwendungsclients besteht darin, die DOM-Interaktion zu minimieren. Die Geschwindigkeit von JavaScript-Engines hat sich zwar um ein Vielfaches erhöht, der Zugriff auf das DOM ist jedoch nicht im selben Maße schneller geworden. Das wird auch aus sehr praktischen Gründen nie passieren, da Dinge wie das Layout und das Zeichnen auf einem Bildschirm einfach Zeit in Anspruch nehmen.
DOM-Knoten im Cache speichern
Wenn Sie einen Knoten oder eine Liste von Knoten aus dem DOM abrufen, überlegen Sie, ob Sie sie bei einer späteren Berechnung (oder auch nur bei der nächsten Schleifeniteration) wiederverwenden können. Das ist oft der Fall, solange Sie im entsprechenden Bereich keine Knoten hinzufügen oder löschen.
Vorher:
function getElements() {
return $('.my-class');
}
Nachher:
var cachedElements;
function getElements() {
if (cachedElements) {
return cachedElements;
}
cachedElements = $('.my-class');
return cachedElements;
}
Cache-Attributwerte
Genauso wie Sie DOM-Knoten im Cache speichern können, können Sie auch die Werte von Attributen im Cache speichern. Angenommen, Sie animieren ein Attribut des Stils eines Knotens. Wenn Sie wissen, dass Sie (wie in diesem Teil des Codes) der Einzige sind, der dieses Attribut jemals verwendet, können Sie den letzten Wert bei jeder Iteration im Cache speichern, damit Sie ihn nicht wiederholt lesen müssen.
Vorher:
setInterval(function() {
var ele = $('#element');
var left = parseInt(ele.css('left'), 10);
ele.css('left', (left + 5) + 'px');
}, 1000 / 30);
Nachher:
js
var ele = $('#element');
var left = parseInt(ele.css('left'), 10);
setInterval(function() {
left += 5;
ele.css('left', left + 'px');
}, 1000 / 30);
DOM-Manipulation aus Schleifen entfernen
Schleifen sind oft Hotspots für die Optimierung. Überlegen Sie, wie Sie die eigentliche Zahlenverarbeitung von der Arbeit mit dem DOM trennen können. Häufig ist es möglich, eine Berechnung durchzuführen und dann alle Ergebnisse auf einmal anzuwenden.
Vorher:
document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
var val = doSomething(array[i]);
document.getElementById('target').innerHTML += val;
}
Nachher:
var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
var val = doSomething(array[i]);
stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');
Neuzeichnungen und Neuformatierungen
Wie bereits erwähnt, ist der Zugriff auf das DOM relativ langsam. Es wird sehr langsam, wenn Ihr Code einen Wert liest, der neu berechnet werden muss, weil Ihr Code vor Kurzem etwas im DOM geändert hat, das damit zusammenhängt. Daher sollte es vermieden werden, Lese- und Schreibzugriff auf das DOM zu mischen. Idealerweise sollte Ihr Code immer in zwei Phasen gruppiert sein:
- Phase 1: DOM-Werte lesen, die für deinen Code erforderlich sind
- Phase 2: DOM ändern
Programmieren Sie keine Muster wie:
- Phase 1: DOM-Werte lesen
- Phase 2: DOM ändern
- Phase 3: Weitere Informationen einholen
- Phase 4: DOM an anderer Stelle ändern
Vorher:
function paintSlow() {
var left1 = $('#thing1').css('left');
$('#otherThing1').css('left', left);
var left2 = $('#thing2').css('left');
$('#otherThing2').css('left', left);
}
Nachher:
function paintFast() {
var left1 = $('#thing1').css('left');
var left2 = $('#thing2').css('left');
$('#otherThing1').css('left', left);
$('#otherThing2').css('left', left);
}
Dieser Rat sollte für Aktionen berücksichtigt werden, die innerhalb eines JavaScript-Ausführungskontexts stattfinden. (z. B. in einem Ereignis-Handler, in einem Intervall-Handler oder beim Umgang mit einer Ajax-Antwort)
Wenn Sie die Funktion paintSlow()
oben ausführen, wird dieses Bild erstellt:
Wenn Sie zur schnelleren Implementierung wechseln, sehen Sie folgendes Bild:
Diese Bilder zeigen, dass die Leistung beim Rendern erheblich gesteigert werden kann, wenn die Reihenfolge der Zugriffe Ihres Codes auf das DOM neu angeordnet wird. In diesem Fall muss der ursprüngliche Code die Stile neu berechnen und die Seite zweimal layouten, um dasselbe Ergebnis zu erzielen. Eine ähnliche Optimierung kann auf praktisch allen „echten“ Code angewendet werden und zu wirklich beeindruckenden Ergebnissen führen.
Weitere Informationen: Rendering: repaint, reflow/relayout, restyle von Stoyan Stefanov
Neuzeichnungen und die Ereignisschleife
Die JavaScript-Ausführung im Browser folgt einem „Ereignisschleifen“-Modell. Standardmäßig befindet sich der Browser im Inaktivitätsstatus. Dieser Status kann durch Ereignisse von Nutzerinteraktionen oder durch JavaScript-Timer oder Ajax-Callbacks unterbrochen werden. Wenn ein JavaScript an einem solchen Unterbrechungspunkt ausgeführt wird, wartet der Browser in der Regel, bis es beendet ist, bevor er den Bildschirm neu zeichnet. Es kann Ausnahmen für extrem lange ausgeführte JavaScripts oder Fälle wie Warnfelder geben, die die JavaScript-Ausführung effektiv unterbrechen.
Konsequenzen
- Wenn die Ausführung Ihrer JavaScript-Animationszyklen länger als 1/30 Sekunden dauert, können Sie keine flüssigen Animationen erstellen, da der Browser während der JS-Ausführung nicht neu gezeichnet wird. Wenn Sie auch Nutzerereignisse verarbeiten möchten, müssen Sie viel schneller sein.
- Manchmal ist es praktisch, einige JavaScript-Aktionen etwas später auszuführen.
Beispiel:
setTimeout(function() { ... }, 0)
Damit wird dem Browser mitgeteilt, den Rückruf auszuführen, sobald die Ereignisschleife wieder inaktiv ist. Einige Browser warten dabei mindestens 10 ms. Beachten Sie, dass dadurch zwei JavaScript-Ausführungszyklen erstellt werden, die zeitlich sehr nah beieinander liegen. Beides kann ein Neuzeichnen des Bildschirms auslösen, was die Gesamtzeit für das Malen verdoppeln kann. Ob dadurch tatsächlich zwei Mal gerendert wird, hängt von den Heuristiken im Browser ab.
Normale Version:
function paintFast() {
var height1 = $('#thing1').css('height');
var height2 = $('#thing2').css('height');
$('#otherThing1').css('height', '20px');
$('#otherThing2').css('height', '20px');
}
Fügen wir eine Verzögerung hinzu:
function paintALittleLater() {
var height1 = $('#thing1').css('height');
var height2 = $('#thing2').css('height');
$('#otherThing1').css('height', '20px');
setTimeout(function() {
$('#otherThing2').css('height', '20px');
}, 10)
}
In der verzögerten Version ist zu sehen, dass der Browser zweimal anzeigt, obwohl die beiden Änderungen an der Seite nur 1/100 Sekunde auseinanderliegen.
Lazy Initialization
Nutzer wünschen sich Web-Apps, die schnell laden und reaktionsschnell sind. Je nach Aktion haben Nutzer jedoch unterschiedliche Schwellenwerte dafür, was sie als langsam empfinden. Beispielsweise sollte eine App nie viele Berechnungen bei einem Mauszeiger-Ereignis ausführen, da dies zu einer schlechten Nutzererfahrung führen kann, während der Nutzer die Maus weiter bewegt. Nutzer sind jedoch daran gewöhnt, dass es nach dem Klicken auf eine Schaltfläche eine kleine Verzögerung gibt.
Daher kann es sinnvoll sein, den Initialisierungscode so spät wie möglich auszuführen, z.B. wenn der Nutzer auf eine Schaltfläche klickt, die eine bestimmte Komponente Ihrer Anwendung aktiviert.
Vorher:
js
var things = $('.ele > .other * div.className');
$('#button').click(function() { things.show() });
Nachher:
js
$('#button').click(function() { $('.ele > .other * div.className').show() });
Ereignisdelegierung
Das Platzieren von Event-Handlern auf einer Seite kann relativ lange dauern und auch mühsam sein, wenn Elemente dynamisch ersetzt werden, wodurch die Event-Handler dann an die neuen Elemente angehängt werden müssen.
Die Lösung in diesem Fall besteht darin, ein Verfahren namens Ereignisdelegierung zu verwenden. Anstatt einzelne Ereignishandler an Elemente anzuhängen, wird die Aufwärtsweitergabe vieler Browserereignisse genutzt. Dazu wird der Ereignishandler an einen übergeordneten Knoten angehängt und der Zielknoten des Ereignisses wird geprüft, um festzustellen, ob das Ereignis von Interesse ist.
In jQuery kann das ganz einfach so ausgedrückt werden:
$('#parentNode').delegate('.button', 'click', function() { ... });
Wann die Ereignisdelegierung nicht verwendet werden sollte
Manchmal ist das Gegenteil der Fall: Sie verwenden die Ereignisdelegierung und haben ein Leistungsproblem. Die Ereignisdelegierung ermöglicht eine konstante Komplexität der Initialisierungszeit. Die Prüfung, ob ein Ereignis von Interesse ist, muss jedoch bei jeder Aufrufung dieses Ereignisses erfolgen. Das kann teuer werden, insbesondere bei häufigen Ereignissen wie „mouseover“ oder „mousemove“.
Typische Probleme und Lösungen
Die Aufgaben, die ich in $(document).ready
erledige, dauern sehr lange
Maltes persönlicher Rat: Tun Sie niemals etwas in $(document).ready
. Reichen Sie Ihr Dokument in seiner endgültigen Form ein. OK, Sie dürfen Ereignis-Listener registrieren, aber nur mithilfe von ID-Selektoren und/oder Ereignisdelegierung. Bei ressourcenintensiven Ereignissen wie „mousemove“ sollten Sie die Registrierung verzögern, bis sie benötigt wird (z. B. mouseover-Ereignis auf dem entsprechenden Element).
Wenn Sie wirklich etwas tun müssen, z. B. eine Ajax-Anfrage stellen, um tatsächliche Daten abzurufen, zeigen Sie eine schöne Animation. Wenn es sich um ein animiertes GIF oder ähnliches handelt, können Sie die Animation als Daten-URI einfügen.
Seit ich der Seite einen Flash-Film hinzugefügt habe, ist alles sehr langsam.
Wenn Sie einer Seite Flash hinzufügen, wird das Rendering immer etwas verlangsamt, da das endgültige Layout des Fensters zwischen dem Browser und dem Flash-Plug-in „verhandelt“ werden muss. Wenn Sie Flash auf Ihren Seiten nicht vollständig vermeiden können, müssen Sie den Flash-Parameter „wmode“ auf den Standardwert „window“ festlegen. Dadurch wird die Zusammensetzung von HTML- und Flash-Elementen deaktiviert. Sie können dann kein HTML-Element sehen, das sich über dem Flash-Film befindet, und der Flash-Film kann nicht transparent sein. Das ist zwar etwas umständlich, aber es verbessert die Leistung erheblich. Sehen Sie sich beispielsweise an, wie auf youtube.com sorgfältig darauf geachtet wird, keine Ebenen über dem Hauptfilmplayer zu platzieren.
Ich speichere Daten im lokalen Speicher, jetzt ruckelt meine Anwendung
Das Schreiben in den lokalen Speicher ist ein synchroner Vorgang, bei dem die Festplatte gestartet wird. Während einer Animation sollten Sie niemals synchrone Vorgänge ausführen, die lange dauern. Verschieben Sie den Zugriff auf localStorage an eine Stelle in Ihrem Code, an der Sie sicher sind, dass der Nutzer inaktiv ist und keine Animationen ausgeführt werden.
Das Profiling weist darauf hin, dass ein jQuery-Selector sehr langsam ist
Prüfen Sie zuerst, ob Ihr Selektor mit document.querySelectorAll ausgeführt werden kann. Sie können das in der JavaScript-Konsole testen. Wenn es eine Ausnahme gibt, überarbeiten Sie den Selector so, dass keine spezielle Erweiterung Ihres JavaScript-Frameworks verwendet wird. Dadurch wird die Auswahl in modernen Browsern um ein Vielfaches beschleunigt.
Wenn das nicht hilft oder du auch in modernen Browsern schnell sein möchtest, beachte die folgenden Richtlinien:
- Geben Sie auf der rechten Seite der Auswahl möglichst genaue Angaben ein.
- Verwenden Sie als äußersten Auswahlteil einen Tag-Namen, den Sie nicht oft verwenden.
- Wenn nichts hilft, sollten Sie die Dinge so umschreiben, dass Sie einen ID-Sellektor verwenden können.
All diese DOM-Manipulationen dauern sehr lange.
Das Einfügen, Entfernen und Aktualisieren vieler DOM-Knoten kann sehr langsam sein. Das lässt sich in der Regel optimieren, indem ein großer HTML-String generiert und der alte Inhalt durch domNode.innerHTML = newHTML
ersetzt wird. Beachten Sie, dass dies die Wartbarkeit beeinträchtigen und Speicherverknüpfungen im IE verursachen kann. Seien Sie also vorsichtig.
Ein weiteres häufiges Problem ist, dass Ihr Initialisierungscode möglicherweise viel HTML generiert. Beispiel: Ein jQuery-Plug-in, das ein Auswahlfeld in eine Reihe von Divs umwandelt, weil das die Designfachkräfte wollten, ohne die Best Practices für UX zu kennen. Wenn Sie möchten, dass Ihre Seite schnell ist, sollten Sie das auf keinen Fall tun. Senden Sie stattdessen das gesamte Markup von der Serverseite in seiner endgültigen Form. Dies hat wiederum viele Probleme. Überlegen Sie daher genau, ob die Geschwindigkeit den Trade-off wert ist.
Tools
- JSPerf – Benchmark für kleine JavaScript-Snippets
- Firebug – zum Profiling in Firefox
- Google Chrome-Entwicklertools (in Safari als WebInspector verfügbar)
- DOM Monster – zur Optimierung der DOM-Leistung
- DynaTrace Ajax Edition – für Profiling und Darstellungsoptimierungen im Internet Explorer