Leistung von HTML5-Apps verbessern

Einleitung

HTML5 bietet uns großartige Tools, um das visuelle Erscheinungsbild von Webanwendungen zu verbessern. Dies gilt insbesondere für Animationen. Diese neue Fähigkeit bringt jedoch auch neue Herausforderungen mit sich. Diese Herausforderungen sind nicht wirklich neu und es könnte sinnvoll sein, Ihre freundliche Nachbarin, den Flash-Programmierer, zu fragen, wie sie in der Vergangenheit ähnliche Probleme überwunden hat.

Bei der Arbeit in Animationen ist es jedoch enorm wichtig, dass die Nutzenden diese Animationen als flüssig wahrnehmen. Wir müssen uns aber bewusst sein, dass eine flüssige Wiedergabe bei Animationen nicht einfach dadurch erreicht werden kann, dass die Anzahl der Bilder pro Sekunde über jeden kognitiven Schwellenwert hinaus erhöht wird. Unser Gehirn ist leider schlauer. Dabei erfahren Sie, dass echte 30-Frames pro Sekunde (fps) viel besser sind als 60 fps, bei denen nur ein paar Frames in der Mitte ausgelassen werden. Unvollständige Dynamik lassen sich nicht finden.

In diesem Artikel werden Tools und Techniken vorgestellt, mit denen Sie Ihre eigene Anwendung optimieren können.

Die Strategie

Wir möchten Sie auf keinen Fall davon abraten, großartige und visuell beeindruckende Apps mit HTML5 zu entwickeln.

Wenn Sie dann feststellen, dass die Leistung etwas besser sein könnte, kehren Sie hierher zurück und lesen Sie, wie Sie die Elemente Ihrer Anwendung verbessern können. Natürlich kann es helfen, einige Dinge von vornherein richtig zu machen, aber lassen Sie sich davon nicht ablenken, wenn Sie produktiv arbeiten möchten.

Visuelle Fidelity++ mit HTML5

Hardwarebeschleunigung

Die Hardwarebeschleunigung ist ein wichtiger Meilenstein für die allgemeine Rendering-Leistung im Browser. Das allgemeine Schema besteht darin, Aufgaben, die sonst von der Haupt-CPU berechnet würden, auf den Grafikprozessor (GPU) in der Grafikkarte Ihres Computers auszulagern. Das kann zu erheblichen Leistungssteigerungen und einem geringeren Ressourcenverbrauch auf Mobilgeräten führen.

Diese Aspekte Ihres Dokuments können von der GPU beschleunigt werden,

  • Allgemeine Layouterstellung
  • CSS3-Übergänge
  • CSS3-3D-Transformationen
  • Canvas-Zeichnung
  • WebGL-3D-Zeichnung

Während die Beschleunigung von Canvas und WebGL spezielle Funktionen sind, die für Ihre App möglicherweise nicht zutreffen, können die ersten drei Aspekte so ziemlich jede App beschleunigen.

Was kann beschleunigt werden?

Die GPU-Beschleunigung erfolgt durch Auslagerung klar definierter und spezifischer Aufgaben auf spezielle Hardware. Das allgemeine Schema ist, dass Ihr Dokument in mehrere „Ebenen“ unterteilt ist, die von den Aspekten Ihrer Seite, die beschleunigt werden, unveränderlich sind. Diese Ebenen werden mithilfe der herkömmlichen Rendering-Pipeline gerendert. Die GPU wird dann verwendet, um die Schichten auf einer einzigen Seite zusammenzusetzen und die „Effekte“ anzuwenden, die direkt beschleunigt werden können. Unter Umständen ist für ein auf dem Bildschirm animiertes Objekt während der Animation kein einziges „Neulayout“ der Seite erforderlich.

Was Sie daraus lernen müssen, ist, dass die Rendering-Engine leicht erkennen muss, wann sie ihre GPU-Beschleunigungsmagie anwenden kann. Dazu ein Beispiel:

Obwohl dies funktioniert, weiß der Browser nicht wirklich, dass Sie etwas ausführen, das von einem Menschen als flüssige Animation wahrgenommen werden sollte. Überlegen Sie, was passiert, wenn Sie stattdessen dieselbe visuelle Darstellung mithilfe von CSS3-Übergängen erhalten:

Die Art und Weise, wie der Browser diese Animation implementiert, bleibt für den Entwickler vollständig verborgen. Dies wiederum bedeutet, dass der Browser Tricks wie die GPU-Beschleunigung anwenden kann, um das festgelegte Ziel zu erreichen.

Es gibt zwei nützliche Befehlszeilen-Flags für Chrome zum Debugging der GPU-Beschleunigung:

  1. --show-composited-layer-borders hat einen roten Rahmen um Elemente, die auf GPU-Ebene bearbeitet werden. Gut, um zu überprüfen, ob Ihre Änderungen innerhalb der GPU-Ebene erfolgen.
  2. --show-paint-rects werden alle Nicht-GPU-Änderungen dargestellt und alle Bereiche, die neu gezeichnet werden, werden mit einem hellen Rahmen versehen. Sie können sehen, wie der Browser die Farbbereiche optimiert.

Für Safari gibt es ähnliche Laufzeit-Flags, die hier beschrieben werden.

CSS3-Übergänge

Mit CSS-Übergängen wird die Stilanimation für alle einfach, aber sie sind auch eine intelligente Funktion zur Leistungsoptimierung. Da ein CSS-Übergang vom Browser verwaltet wird, kann die Animationsqualität deutlich verbessert und in vielen Fällen hardwarebeschleunigt werden. Derzeit verfügt WebKit (Chrome, Safari, iOS) über hardwarebeschleunigte CSS-Transformationen, wird aber bald auch für andere Browser und Plattformen eingeführt.

Du kannst transitionEnd-Ereignisse verwenden, um daraus leistungsstarke Kombinationen zu erstellen. Wenn du allerdings alle unterstützten Endereignisse für den Übergang erfassst, musst du dir webkitTransitionEnd transitionend oTransitionEnd ansehen.

Viele Bibliotheken haben jetzt Animations-APIs eingeführt, die Übergänge nutzen, sofern vorhanden, und andernfalls auf die Standardanimation im DOM-Stil zurückgreifen. scripty2, YUI vorübergehend, jQuery animate Enhanced.

CSS3-Übersetzung

Sie haben bestimmt schon einmal die x/y-Position eines Elements über die Seite animiert. Sie haben wahrscheinlich die left- und top-Eigenschaften des Inline-Stils geändert. Bei 2D-Transformationen können wir die Funktion translate() verwenden, um dieses Verhalten zu replizieren.

Wir können dies mit der DOM-Animation kombinieren,

<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 Funktionstests für CSS-2D-Transformationen und CSS-Übergänge zu testen. Wenn ja, verwenden wir Translate, um die Position zu verschieben. Wenn die Animation mithilfe eines Übergangs animiert wird, ist die Wahrscheinlichkeit hoch, dass der Browser die Hardwarebeschleunigung beschleunigt. Um dem Browser einen weiteren Schubs in die richtige Richtung zu geben, verwenden wir den "magischen CSS-Aufzählungspunkt" von oben.

Wenn unser Browser weniger leistungsfähig ist, verwenden wir jQuery, um das Element zu verschieben. Mit dem Polyfill-Plug-in für jQuery-Transformation von Louis-Remi Babe können Sie diesen Vorgang automatisch ausführen.

window.requestAnimationFrame

requestAnimationFrame wurde von Mozilla eingeführt und von WebKit überarbeitet, um Ihnen eine native API zum Ausführen von Animationen bereitzustellen, unabhängig davon, ob sie auf DOM/CSS, auf <canvas> oder WebGL basieren. Der Browser kann gleichzeitig mehrere Animationen gemeinsam in einem einzigen Reflow- und Darstellungszyklus optimieren, was zu einer höheren Qualität der Animation führt. Zum Beispiel JS-basierte Animationen, die mit CSS-Übergängen oder SVG SMIL synchronisiert werden. Wenn Sie die Animationsschleife auf einem Tab ausführen, der nicht sichtbar ist, wird sie vom Browser nicht weiter ausgeführt. Das bedeutet weniger CPU-, GPU- und Arbeitsspeichernutzung, was zu einer deutlich längeren Akkulaufzeit führt.

Weitere Informationen dazu, wie und warum Sie requestAnimationFrame verwenden sollten, finden Sie im Artikel requestAnimationFrame for smart animating von Paul Irish.

Profilerstellung

Wenn Sie feststellen, dass die Geschwindigkeit Ihrer Anwendung verbessert werden kann, ist es an der Zeit, sich mit der Profilerstellung zu befassen, um herauszufinden, wo Optimierungen den größten Nutzen bringen können. Optimierungen wirken sich häufig negativ auf die Verwaltbarkeit Ihres Quellcodes aus und sollten daher nur bei Bedarf angewendet werden. Durch die Profilerstellung erfahren Sie, welche Teile Ihres Codes im Falle einer Leistungsverbesserung die größten Vorteile bringen würden.

JavaScript-Profilerstellung

JavaScript-Profiler geben Ihnen einen Überblick über die Leistung Ihrer Anwendung auf der JavaScript-Funktionsebene, indem sie die Zeit messen, die für die Ausführung jeder einzelnen Funktion von Anfang bis Ende benötigt wird.

Die Bruttoausführungszeit einer Funktion ist die Gesamtzeit, die benötigt wird, um sie von oben nach unten auszuführen. Die Nettoausführungszeit ist die Bruttoausführungszeit abzüglich der Zeit, die zum Ausführen der von der Funktion aufgerufenen Funktionen benötigt wurde.

Einige Funktionen werden häufiger aufgerufen als andere. Profiler geben normalerweise 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 zur Profilerstellung in den Chrome-Entwicklertools.

Das DOM

Die Leistung von JavaScript hat einen großen Einfluss darauf, wie flüssig und reaktionsschnell Ihre Anwendung anfühlt. JavaScript-Profiler messen zwar die Ausführungszeit Ihres JavaScript, aber auch indirekt die für DOM-Vorgänge aufgewendete Zeit. Diese DOM-Vorgänge bilden oft den Kern der Leistungsprobleme.

function drawArray(array) {
  for(var i = 0; i < array.length; i++) {
    document.getElementById('test').innerHTML += array[i]; // No good :(
  }
}

Zum Beispiel wird im obigen Code fast keine Zeit für die Ausführung von JavaScript benötigt. Es ist immer noch sehr wahrscheinlich, dass diedrawArray-Funktion in Ihren Profilen auftaucht, da sie auf überflüssige Weise mit dem DOM interagiert.

Tipps und Tricks

Anonyme Funktionen

Für anonyme Funktionen ist es nicht einfach, ein Profil zu erstellen, da sie grundsätzlich keinen Namen haben, unter dem sie im Profiler angezeigt werden könnten. Es gibt zwei Möglichkeiten, dieses Problem zu umgehen:

$('.stuff').each(function() { ... });

umschreiben in:

$('.stuff').each(function workOnStuff() { ... });

Es ist nicht allgemein bekannt, dass JavaScript die Benennung von Funktionsausdrücken unterstützt. Dadurch werden sie perfekt im Profiler angezeigt. Bei dieser Lösung gibt es ein Problem: Der benannte Ausdruck platziert den Funktionsnamen tatsächlich in den aktuellen lexikalischen Bereich. Seien Sie also vorsichtig, da dadurch andere Symbole überschrieben werden können.

Profilerstellung für lange Funktionen

Stellen Sie sich vor, 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, um herauszufinden, welcher Teil das Problem ist:

  1. Die richtige Methode: Refaktorieren Sie Ihren Code so, dass er keine langen Funktionen enthält.
  2. Die Methode der Abschaffung eines Bösen: Fügen Sie Ihrem Code Anweisungen in Form benannter selbst aufrufender Funktionen hinzu. Wenn Sie vorsichtig sind, ändert sich dadurch nicht die Semantik und Teile der Funktion werden im Profiler als einzelne Funktionen angezeigt: js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... } Vergessen Sie nicht, diese zusätzlichen Funktionen nach Abschluss der Profilerstellung zu entfernen. Sie können sie sogar als Ausgangspunkt für die Refaktorierung des Codes verwenden.

DOM-Profilerstellung

Die neuesten Chrome Web Inspector-Entwicklungstools enthalten das neue Zeitachsendiagramm, das eine Zeitachse der vom Browser ausgeführten Aktionen auf unterer Ebene zeigt. Anhand dieser Informationen können Sie Ihre DOM-Vorgänge optimieren. Sie sollten versuchen, die Anzahl der "Aktionen" zu reduzieren, die der Browser während der Codeausführung ausführen muss.

Die Zeitachsenansicht kann eine riesige Menge an Informationen liefern. Sie sollten daher versuchen, minimale Testläufe zu erstellen, die Sie unabhängig ausführen können.

DOM-Profilerstellung

Das Bild oben zeigt die Ausgabe der Zeitachsenansicht für ein sehr einfaches Skript. Der linke Bereich zeigt die vom Browser ausgeführten Vorgänge in chronischer Reihenfolge, während die Zeitachse im rechten Bereich die tatsächliche Zeit zeigt, die von einem einzelnen Vorgang beansprucht wird.

Weitere Informationen zur Zeitachse Ein alternatives Tool für die Profilerstellung in Internet Explorer ist DynaTrace Ajax Edition.

Strategien für die Profilerstellung

Aspekte hervorheben

Wenn Sie ein Profil für Ihre Anwendung erstellen möchten, versuchen Sie, die Aspekte ihrer Funktionalität zu identifizieren, die zu einer Verlangsamung führen könnten. Versuchen Sie dann, eine Profilausführung durchzuführen, die nur Teile Ihres Codes ausführt, die für diese Aspekte Ihrer Anwendung relevant sind. Dies erleichtert die Interpretation der Profildaten, da sie nicht mit Codepfaden vermischt sind, die nicht mit Ihrem tatsächlichen Problem zusammenhängen. Gute Beispiele für einzelne Aspekte Ihrer Anwendung:

  1. Startzeit (Profiler aktivieren, Anwendung neu laden, auf Abschluss der Initialisierung warten, Profiler beenden)
  2. Klicken auf eine Schaltfläche und die nachfolgende Animation (Profiler starten, auf Schaltfläche klicken, bis die Animation abwarten, Profiler beenden)
GUI-Profilerstellung

In einem GUI-Programm kann es schwieriger sein, nur den richtigen Teil Ihrer Anwendung auszuführen, als beispielsweise den Raytracer Ihrer 3D-Engine zu optimieren. Wenn Sie beispielsweise ein Profil für die Vorgänge erstellen möchten, die beim Klicken auf eine Schaltfläche auftreten, können irrelevante Mouseover-Ereignisse auf dem Weg ausgelöst werden, sodass Ihre Ergebnisse weniger schlüssig sind. Das solltest du vermeiden :)

Programmgesteuerte Schnittstelle

Es gibt auch eine programmgesteuerte Schnittstelle, über die der Debugger aktiviert wird. Dadurch können Sie genau steuern, wann die Profilerstellung beginnt und wann sie endet.

Starten Sie eine Profilerstellung mit:

console.profile()

Beenden Sie die Profilerstellung mit:

console.profileEnd()

Wiederholbarkeit

Achten Sie bei der Profilerstellung darauf, dass Sie Ihre Ergebnisse tatsächlich reproduzieren können. Nur so können Sie beurteilen, ob Ihre Optimierungen tatsächlich zu Verbesserungen geführt haben. Außerdem erfolgt die Profilerstellung auf Funktionsebene im Kontext des gesamten Computers. Es ist keine exakte Wissenschaft. Die Ausführung einzelner Profile kann durch viele andere Vorgänge auf Ihrem Computer beeinflusst werden:

  1. Ein unabhängiger Timer in Ihrer eigenen Anwendung, der ausgelöst wird, während Sie etwas anderes messen.
  2. Die automatische Speicherbereinigung wird ausgeführt.
  3. Ein anderer Tab in Ihrem Browser, der harte Arbeit im selben Thread erledigt.
  4. Ein anderes Programm auf Ihrem Computer verbraucht die CPU und verlangsamt Ihre Anwendung dadurch.
  5. Plötzliche Veränderungen im Gravitationsfeld der Erde.

Es ist außerdem sinnvoll, denselben Codepfad mehrmals in einer Profilerstellungssitzung auszuführen. Auf diese Weise verringern Sie den Einfluss der oben genannten Faktoren und die langsamen Teile können noch deutlicher hervorstechen.

Analysieren, verbessern, messen

Wenn Sie eine langsame Stelle in Ihrem Programm identifiziert haben, versuchen Sie nach Möglichkeiten, das Ausführungsverhalten zu verbessern. Nachdem Sie den Code geändert haben, erstellen Sie noch einmal ein Profil. Wenn Sie mit dem Ergebnis zufrieden sind, fahren Sie fort. Wenn Sie keine Verbesserung feststellen, sollten Sie Ihre Änderung wahrscheinlich rückgängig machen und die Änderung nicht im Status „Weil es nicht schaden kann“ belassen.

Optimierungsstrategien

DOM-Interaktion minimieren

Ein gemeinsames Thema bei der Verbesserung der Geschwindigkeit von Webclient-Anwendungen besteht darin, die DOM-Interaktion zu minimieren. Während die Geschwindigkeit der JavaScript-Engines um ein Vielfaches zugenommen hat, ist der Zugriff auf das DOM nicht in gleicher Weise schneller geworden. Das wird auch aus sehr praktischen Gründen nie vorkommen (etwa das Layout oder das Zeichnen von Dingen auf einem Bildschirm brauchen Zeit).

DOM-Knoten im Cache speichern

Wenn Sie einen Knoten oder eine Liste von Knoten aus dem DOM abrufen, überlegen Sie immer, ob Sie diese in einer späteren Berechnung (oder sogar nur bei der nächsten Schleifeniteration) wiederverwenden können. Solange Sie im relevanten Bereich keine Knoten hinzufügen oder löschen, ist dies oft der Fall.

Vorher:

function getElements() {
  return $('.my-class');
}

Nachher:

var cachedElements;
function getElements() {
  if (cachedElements) {
    return cachedElements;
  }
  cachedElements = $('.my-class');
  return cachedElements;
}

Attributwerte im Cache speichern

Auf die gleiche Weise, wie Sie DOM-Knoten im Cache speichern, können Sie auch die Werte von Attributen im Cache speichern. Angenommen, Sie animieren ein Attribut eines Knotenstils. Wenn Sie wissen, dass Sie (wie in diesem Teil des Codes) die einzige Person sind, die jemals mit dem Attribut in Berührung kommt, 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 verschieben

Schleifen sind oft Hotspots für die Optimierung. Überlegen Sie, wie Sie die eigentlichen Berechnungen von Daten von der Arbeit mit dem DOM entkoppeln können. Häufig ist es möglich, eine Berechnung durchzuführen und anschließend 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 Reflows

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 kürzlich etwas zugehöriges im DOM geändert hat. Daher sollte eine Kombination aus Lese- und Schreibzugriff auf das DOM vermieden werden. Idealerweise sollte Ihr Code immer in zwei Phasen unterteilt sein:

  • Phase 1: Für Ihren Code erforderliche DOM-Werte lesen
  • Phase 2: DOM ändern

Versuchen Sie, folgendes Muster nicht zu programmieren:

  • Phase 1: DOM-Werte lesen
  • Phase 2: DOM ändern
  • Phase 3: Weitere Informationen
  • Phase 4: DOM an einer anderen 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 Hinweis sollte für Aktionen in einem JavaScript-Ausführungskontext berücksichtigt werden. (z.B. in einem Ereignis-Handler, Intervall-Handler oder bei der Verarbeitung einer Ajax-Antwort.)

Mit der obigen Funktion paintSlow() wird dieses Image erstellt:

paintSlow()

Ein Wechsel zur schnelleren Implementierung ergibt dieses Bild:

Schnellere Implementierung

Diese Bilder zeigen, dass eine Neuanordnung der Art und Weise, wie Ihr Code auf das DOM zugreift, die Rendering-Leistung erheblich verbessern kann. In diesem Fall muss der ursprüngliche Code die Stile und das Layout der Seite zweimal neu berechnen, um das gleiche Ergebnis zu erzielen. Eine ähnliche Optimierung kann praktisch auf den gesamten „realen“ Code angewendet werden und führt zu sehr beeindruckenden Ergebnissen.

Weitere Informationen: Rendering: repaint, reflow/relayout, restyle von Stoyan Stefanov

Neuzeichnungen und die Ereignisschleife

Die JavaScript-Ausführung im Browser folgt einem Ereignisschleifenmodell. Standardmäßig befindet sich der Browser im Leerlauf. Dieser Status kann durch Ereignisse wie Nutzerinteraktionen oder JavaScript-Timer oder Ajax-Callbacks unterbrochen werden. Immer wenn ein JavaScript-Code an einem solchen Unterbrechungspunkt ausgeführt wird, wartet der Browser in der Regel, bis er fertig ist, bis er den Bildschirm neu malt. Ausnahmen können bei sehr lang andauernden JavaScripts oder in Fällen wie Benachrichtigungsfeldern auftreten, die die JavaScript-Ausführung effektiv unterbrechen.

Konsequenzen

  1. 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 JavaScript-Ausführung keine Darstellungsaktualisierung durchführt. Wenn Sie damit rechnen, auch Nutzerereignisse abzuwickeln, müssen Sie viel schneller sein.
  2. Manchmal ist es praktisch, einige JavaScript-Aktionen kurz zu verzögern. Beispiel: setTimeout(function() { ... }, 0) Dadurch wird der Browser angewiesen, den Callback auszuführen, sobald die Ereignisschleife wieder inaktiv ist. Einige Browser warten also mindestens 10 ms. Beachten Sie, dass dadurch zwei JavaScript-Ausführungszyklen erstellt werden, die zeitlich sehr nah beieinander liegen. Beide können eine Aktualisierung des Bildschirms auslösen, wodurch sich die Gesamtzeit verdoppeln kann, die mit dem Streichen des Bildes verbracht wurde. Ob dies tatsächlich Two Paints auslöst, hängt von der Heuristik im Browser ab.

Reguläre Version:

function paintFast() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  $('#otherThing2').css('height', '20px');
}
Neuzeichnungen und die Ereignisschleife

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)
}
Verzögerung

Die verzögerte Version zeigt, dass der Browser zweimal aktualisiert wird, obwohl die beiden Änderungen an der Seite nur Hundertstelsekunden ausmachen.

Verzögerte Initialisierung

Nutzer wünschen sich Web-Apps, die schnell geladen werden und schnell reagieren. Allerdings haben Nutzer je nach Aktion, die sie als langsam empfinden, unterschiedliche Grenzwerte. Beispielsweise sollte eine App bei einem Mouseover-Ereignis nie viele Berechnungen für ein Mouseover-Ereignis ausführen, da dies die Nutzererfahrung beeinträchtigen kann, während der Nutzer die Maus weiterbewegt. Es ist jedoch an die Nutzenden gewöhnt, eine kurze Verzögerung nach dem Klicken auf eine Schaltfläche zu akzeptieren.

Daher kann es sinnvoll sein, den Initialisierungscode so zu verschieben, dass er so spät wie möglich ausgeführt wird (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 Verteilen von Event-Handlern auf eine Seite kann relativ lange dauern und auch mühsam sein, sobald Elemente dynamisch ersetzt werden, bei denen dann wieder Event-Handler an die neuen Elemente angehängt werden müssen.

Die Lösung ist in diesem Fall die Verwendung einer Technik, die als Ereignisdelegierung bezeichnet wird. Anstatt einzelne Event-Handler an Elemente anzuhängen, wird die Bubbling-Struktur vieler Browser-Ereignisse genutzt, indem der Event-Handler tatsächlich an einen übergeordneten Knoten angehängt und der Zielknoten des Ereignisses daraufhin überprüft wird, ob das Ereignis von Interesse ist.

In jQuery kann dies ganz einfach so ausgedrückt werden:

$('#parentNode').delegate('.button', 'click', function() { ... });

Wann die Ereignisdelegierung nicht verwendet werden sollte

Manchmal trifft das Gegenteil zu: Sie verwenden die Ereignisdelegierung und haben ein Leistungsproblem. Im Grunde genommen ermöglicht die Ereignisdelegierung eine Initialisierungszeit von konstanter Komplexität. Allerdings muss der Preis für die Überprüfung, ob ein Ereignis von Interesse ist, für jeden Aufruf dieses Ereignisses bezahlt werden. Dies kann teuer werden, insbesondere bei häufig vorkommenden Ereignissen wie „mouseover“ oder „mousemove“.

Typische Probleme und Lösungen

Das, was ich in $(document).ready mache, dauert sehr lange

Maltes persönlicher Tipp: Du solltest in $(document).ready niemals etwas tun. Versuchen Sie, Ihr Dokument in der endgültigen Form bereitzustellen. Sie dürfen Event-Listener registrieren, aber nur mithilfe des ID-Selektors und/oder der Ereignisdelegierung. Verzögern Sie bei teuren Ereignissen wie „mousemove“ die Registrierung, bis sie benötigt werden (Mouseover-Ereignis für das relevante Element).

Und wenn Sie wirklich etwas tun müssen, z. B. eine Ajax-Anfrage stellen, um echte Daten abzurufen, und dann eine schöne Animation zeigen; könnte die Animation als Daten-URI enthalten sein, wenn es sich um eine animierte GIF-Datei oder Ähnliches handelt.

Seitdem ich einen Flash-Film hinzugefügt habe, ist alles sehr langsam.

Das Hinzufügen von Flash zu einer Seite verlangsamt das Rendering immer ein wenig, da das endgültige Layout des Fensters zwischen dem Browser und dem Flash-Plug-in "ausgehandelt" werden muss. Wenn Sie die Platzierung von Flash auf Ihren Seiten nicht vollständig vermeiden können, stellen Sie den Flash-Parameter "wmode" auf den Wert "window" (Standardwert) ein. Dadurch wird die Möglichkeit deaktiviert, HTML- und Flash-Elemente zusammenzusetzen. (Ein HTML-Element, das über dem Flash-Film liegt, ist nicht sichtbar und Ihr Flash-Film darf nicht transparent sein.) Das kann Ihnen Unannehmlichkeiten bereiten, aber Ihre Leistung wird erheblich verbessert. Auf youtube.com zum Beispiel vermeidet es, Ebenen über dem primären Videoplayer zu platzieren.

Ich speichere Inhalte im lokalen Speicher, jetzt ruckelt meine Anwendung auf

Das Schreiben in localStorage ist ein synchroner Vorgang, bei dem die Festplatte gestartet wird. Sie sollten nie lang andauernde synchrone Vorgänge während der Animation ausführen. Verschieben Sie den Zugriff auf localStorage an eine Stelle im Code, an der Sie sicher sind, dass der Nutzer inaktiv ist und keine Animationen stattfinden.

Die Profilerstellung verweist auf einen sehr langsamen jQuery-Selektor

Zuerst möchten Sie sicherstellen, dass Ihr Selektor mit document.querySelectorAll ausgeführt werden kann. Das kannst du in der JavaScript-Konsole testen. Ändern Sie im Fall einer Ausnahme die Auswahl so, dass keine spezielle Erweiterung Ihres JavaScript-Frameworks verwendet wird. Dadurch wird die Auswahl in modernen Browsern um ein Vielfaches beschleunigt.

Wenn das Problem dadurch nicht behoben wird oder wenn Sie möchten, dass Ihre Website in modernen Browsern schnell geladen wird, befolgen Sie diese Richtlinien:

  • Machen Sie auf der rechten Seite des Selektors möglichst genaue Angaben.
  • Verwenden Sie für den Selektorteil ganz rechts einen Tag-Namen, den Sie nicht oft verwenden.
  • Wenn nichts hilft, sollten Sie überlegen, alles umzuschreiben, damit Sie einen ID-Selektor verwenden können.

Alle diese DOM-Manipulationen nehmen viel Zeit in Anspruch.

Das Einfügen, Entfernen und Aktualisieren mehrerer DOM-Knoten kann sehr lange dauern. Sie können dies im Allgemeinen optimieren, indem Sie einen großen HTML-String generieren und den alten Inhalt mit domNode.innerHTML = newHTML ersetzen. Dies kann die Verwaltbarkeit beeinträchtigen und Speicherlinks im IE erstellen. Seien Sie also vorsichtig.

Ein weiteres häufiges Problem besteht darin, dass Ihr Initialisierungscode möglicherweise sehr viel HTML erstellt. Zum Beispiel ein jQuery-Plug-in, das ein Auswahlfeld in eine Reihe von div-Elementen umwandelt, weil dies das Gewünschte war, ohne die UX-Best Practices zu berücksichtigen. Wenn Sie möchten, dass Ihre Seite wirklich schnell ist, sollten Sie das niemals tun. Stelle stattdessen das gesamte Markup von der Serverseite in der endgültigen Form bereit. Auch hier gibt es viele Probleme. Überlegen Sie sich also, ob sich die Geschwindigkeit lohnt.

Tools

  1. JSPerf – Benchmark von kleinen JavaScript-Snippets
  2. Firebug – für die Profilerstellung in Firefox
  3. Google Chrome-Entwicklertools (verfügbar als WebInspector in Safari)
  4. DOM Monster – zur Optimierung der DOM-Leistung
  5. DynaTrace Ajax Edition – Für die Profilerstellung und Paint-Optimierung in Internet Explorer

Weitere Informationen

  1. Geschwindigkeit von Google
  2. Paul Irish über jQuery Performance
  3. Extreme JavaScript Performance (Präsentation)