HTML5 ist langsam, oder? Nein, in der Tat zeigt sich die grundlegende Geschwindigkeit von Cross-Plattform-HTML5-Anwendungen heutzutage in einem sehr positiven Licht. Es gibt aber doch ein paar Fallstricke, in denen sich der Entwickler schnell verfängt, wenn er nicht auf ein paar Punkte achtet. Der Vortrag geht zuerst auf die Vorgehensweise der Performancemessung ein. Anschließend werden die Top Performance-Tipps gezeigt, um nochmal einen ordentlichen Schub an Power herausholen zu können. Lassen Sie uns Gas geben!
Aber schnell! Top HTML5 Performance Tipps für Hybrid- und Web-Apps
1. Aber schnell!
Performanceaspekte in Cross-Plattform-HTML5-
Anwendungen
Gregor Biswanger | CEO von CleverSocial.de, Freier Dozent, Berater, Trainer und Autor
about.me/gregor.biswanger
3. Über mich
Gründer von CleverSocial.de
Freier Dozent, Berater und Trainer
Schwerpunkte .NET-Architektur, Agile Prozesse,
XAML, Web und Cloud
Technologieberater für die Intel Developer Zone
Sprecher auf Konferenzen und User Groups
Freier Autor für heise.de, dotnetpro,
WindowsDeveloper und viele weitere Fachmagazine
Video-Trainer bei video2brain und Microsoft
Gregor Biswanger
Microsoft MVP, Intel Black Belt &
Intel Software Innovator
dotnet-blog.net
about.me/gregor.biswanger
4. Unsere gemeinsame Reise
Performance messen
Welche Probleme gibt es bei Hybrid-Apps
Wie funktioniert der Browser
Zahlreiche Performance-Tipps für Web-Entwickler
8. 100 ms ist ein gutes und realistisches Ziel
Das ist unser Ziel!
9. Was ist Apache Cordova?
Wie Hybrid-App Entwicklung mit HTML5?
10. Die Lösung: Hybrid-Apps mit Apache Cordova
Cordova ist ein JavaScript-Framework für lokal installierbare WebApps
auf mobilen Endgeräten
Ist Open-Source und liegt auf GitHub
Unterstützte Plattformen: iOS, Android, LG webOS, Symbian OS,
BlackBerry, Tizen, Firefox OS, Windows Phone, Windows 8
Features
Zugriff auf Sensoren
Plattformspezifische Funktionen (Notifications)
Zugriff auf Kontakte
Zugriff auf lokale Dateien
Cordova bietet kein UI Framework!
cordova.apache.org
19. Das Crosswalk Project
Open-Source (BSD Lizenz)
Embedded Chrome Chromium
Für Android und Tizen
Ab Android Version 4
Ab Tizen Version 3
Nachteil: Paketgröße circa 15-20 MB
An Crosswalk Lite wird aktuell gearbeitet
Paketgröße circa 7 MB crosswalk-project.org
22. Einbinden ganz einfach via Cordova Plug-In
Über die Plugin ID:
com.telerik.plugins.wkwebview
Beim Intel XDK ganz einfach über den Plug-In Manager
23. Dann müssen wir analysieren, wie ein Browser unter
der Haube arbeitet…
30. Die richtige Reihenfolge der HTML-Struktur
CSS-Code immer beim Header unterbringen
Vermeide Embedded und Inline Styles
JavaScript-Code immer am Ende vom Body
Ab HTML5 Hilft der async-Tag für die
Verarbeitung zum richtigen Zeitpunkt
31. Und wieder BÖSE!
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css/bootstrap.css">
</head>
<body>
...
<script src="js/jquery.js"></script>
<script src="js/bootstrap.js"></script>
</body>
</html>
33. Minifying von Dateien
Das Laden der Daten ist kürzer
CSS-Styles werden schneller gerendert
App-Paket wird kompakter
34. Falls man Ressourcen nicht lokal ablegen kann, dann
lieber CDN nutzen…
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/...
...3.3.5/css/bootstrap.min.css">
</head>
<body>
...
<script src="https://ajax.googleapis.com/ajax/...
...libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/...
...bootstrap/3.3.5/js/bootstrap.min.js"></script>
</body>
</html>
35. Das Content Delivery Network (CDN)
Ressourcen werden automatisch gecached
jsdelivr.com
36. Quiz: Welcher Download ist schneller?
a) 10 x 1 KB Große Bilddateien
b) 1 x 10 KB Große Bilddatei
37. Die Lösung ist B
Die HTTP-Spezifikation kann nur wenige parallele
Downloadvorgänge ausführen.
38. CSS Sprite Bilder verwenden
Icons.png: 1 x 13 KB Große Datei
Alle nötigen Grafiken auf
einem Bild enthalten
Kommt ursprünglich aus
der Spieleentwicklung
Ein Download für alle
nötigen Grafiken
Austausch von Grafiken
ohne Verzögerung
(Flackern)
42. Quiz: Was ist besser? JPEG oder PNG?
a) JPEG
b) PNG
c) Je nach Anwendungsfall
43. Die Lösung ist C
JPEG für Fotografien: Landschaften oder Gesichter
PNG: Logo, Diagramme, Screenshots (verbraucht mehr
Memory und Decodierung)
Bitte vermeiden: GIF, TIFF, BMP, WebP, etc.
45. Okay, die Daten wurden geladen. Was steckt noch
hinter dem Browser?
46. Zwei „Threads“ teilen sich die Arbeit…
Main Thread
Er führt den JavaScript-Code aus
Berechnet HTML-Elemente
Gemeinsam mit CSS-Styles
(Layouten)
Verarbeitet die Elemente in Bitmaps
Composition Thread
Zeichnet Bitmaps mit der GPU
(Circa 60 mal die Sekunde)
Berechnung der Sichtbaren Elemente
Berechnung für die Bewegung von
Elementen (Scrollen)
Arbeit für dich
Fertig! Überprüf
mal bitte…
Hier mal bitte
Zeichnen…
NEIN
Nur wenn ich
gerade nichts
zu tun hab!
47. Web Runtime Architektur
Networking /
Cache
Parsers
1
2 7
43 8 9
5 6
DOM
Tree
Formatting Layout Painting
1
2 7
43 8 9
5 6
Display Tree
Compositing
DOM API
& Capabilities
JavaScript
CSS Cascade
53. Perfekt! So geht’s ab…
@keyframes drive {
from {
transform: translate(-400px, 0px);
}
to {
transform: translate(200px, 50px);
}
}
54. Animationen GPU beschleunigen mit CSS-Transitions
Compositor-Thread übernimmt die Animation einmalig,
der Rest wird von der GPU verarbeitet
Tipp: Anstatt display:none oder
visibility:hidden, mit Transitions die Elemente
außerhalb vom Screen ablegen. Das ist 3-5 x schneller.
57. Verwende Bilder anstatt CSS3 Gradients oder
Border Radius
Bilder werden immer direkt von der GPU verarbeitet
Am besten JPEG verwenden
58. EventListener können zu unangenehmen Events führen
function createElements() {
for (var i = 0; i < 100; ++i) {
var xBtn = document.createElement('button');
xBtn.setAttribute('value', 'AA');
xBtn.addEventListener('click', hi, false);
containerDiv.appendChild(xBtn);
xBtn = null;
}
}
function clearElements() {
containerDiv.innerHTML = "";
}
59. Unnötige EventListener wieder abbestellen
function createElements() {
for (var i = 0; i < 100; ++i) {
var xBtn = document.createElement('button');
xBtn.setAttribute('value', 'AA');
xBtn.addEventListener('click', hi, false);
containerDiv.appendChild(xBtn);
xBtn = null;
}
}
function clearElements() {
var els = containerDiv.childNodes;
for (var i = 0; i < els.length; i++) {
els[i].removeEventListener('click', hi, false);
containerDiv.removeChild(els[i]);
}
}
60. Zuviel des Guten…
for (i = 0; i < 100; i++){
img[i].addEventListener("click", function clickListener(e) {
var clickedItem = e.target.id;
alert("Hello " + clickedItem);
});
}
61. Besser: Bubbling Events nutzen
var theParent = document.querySelector("#theDude");
theParent.addEventListener("click", doSomething, false);
function doSomething(e) {
if (e.target !== e.currentTarget) {
var clickedItem = e.target.id;
alert("Hello " + clickedItem);
}
e.stopPropagation();
}
62. Best Practices für EventListener
Minimiere unnötige Events
Teile Events übergreifend wenn möglich
63. Der Timer vom Performanceteufel…
setInterval(function() {
// Mach irgendwas...
}, 2000);
64. Im Einklang, mit dem Powerengel…
var start = null;
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
// Mach irgendwas...
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
65. Am Rendering festhalten
Timer unterbricht den Standard Prozess und sorgt für einen Reflow
window.requestAnimationFrame hängt direkt am Render-
Prozess und ist somit im Einklang vom Standard Prozess
66. Quiz: Welche Variable bekommt den schnellsten
Zugriff?
var a = 1;
var b = "ich";
var c = 0.1;
var d = 0x1;
67. Richtig ist A
var a = 1;
var b = "ich";
var c = 0.1;
var d = 0x1;
STACK
0x00000003a:
0x005e4148b:
0x005e4160c:
String
“ich”
Number
0.1
Number
0x1
0x005e4170d:
HEAP
0x005e4148: 0…01001000
0x03 represents 1: 0…00000011
68. Number in JavaScript
Alle Zahlen in JavaScript sind IEEE 64-Bit floating numbers
Gute für den flexiblen Einsatz
Eine Performance Herausforderung
31bits
31-bit (tagged) Integers
1bit
1
31bits
Object pointer
1bit
0
32bits
32bits
Floats
32-bit Integers
STACK HEAP
FIXE LÄNGE, SCHNELLER ZUGRIFF VARIABLE LÄNGE, LANGSAMER ZUGRIFF
Boxed
69. Best Practices für Number
Verwende eine 31-Bit Zahl wenn möglich
Verwende nur floats wenn nötig
70. Böses Array! Aber warum?
var bad = new Array();
bad[0] = 1;
bad[1] = 5.6;
bad[2] = "Will nicht böse sein";
71. Es entstehen Kopien im Speicher und Konvertierungen
var bad = new Array();
bad[0] = 1;
bad[1] = 5.6;
bad[2] = "Will nicht böse sein";
Type: Int Array 1
Type: Float Array
Type: Var Array 1 2.3 “Will..”
1 5.6
72. Immer noch Schmerzen! Aber warum?
var bad = new Array();
bad[0] = 1;
bad[1] = 5.6;
bad[2] = "Will nicht böse sein";
73. Pre-allocate Arrays
var bad = new Array();
bad[0] = 1;
bad[1] = 5.6;
bad[2] = "Will nicht böse sein";
var bad = new Array(100);
bad[0] = 1;
bad[1] = 5.6;
bad[2] = "Will nicht böse sein";
0 ?
?+1 ??
Langsam Schnell
…0 100
74. Verwende Typed Arrays wenn möglich
var value = 5;
var a = new Float64Array(100);
a[0] = value; // 5.0 - no tagging required
a[1] = value / 2; // 2.5 - no boxing required
a[2] = "text"; // 0.0
var a = new Int32Array(100);
a[0] = value; // 5 - no tagging required
a[1] = value / 2; // 2 - no tagging required
a[2] = "text"; // 0
75. Tödliche Schleifen…
var a = new Array(100);
var total = 0;
for (var item in a) {
total += item;
};
a.forEach(function(item){
total += item;
});
for (var i = 0; i < a.length; i++) {
total += a[i];
}
76. Nutze eine Schleife effizient
var a = new Array(100);
var total = 0;
var cachedLength = a.length;
for (var i = 0; i < cachedLength; i++) {
total += a[i];
}
77. Best Practices für Arrays
Gebe eine fixe Größe mit
Verwende Typed Arrays wenn möglich
Nutze eine Schleife effizient
78. The DOOM of DOM
...
//for each rotation
document.getElementById("myDiv").classList.remove(oldClass)
document.getElementById("myDiv").classList.add(newClass)
...
JavaScript
DOM
79. DOM Kommunikation findet jetzt nur einmalig statt
var element = document.getElementById(elID).classList;
//for each rotation
element.remove(oldClass)
element.add(newClass)
...
JavaScript
DOM
80. DOM Kommunikation findet jetzt nur einmalig statt
var elems = ['img1.jpg', … 'img6.jpg'];
var fragment = document.createDocumentFragment();
for (var i=0; i < elems.length; i++) {
var newNode = document.createElement('img');
newNode.setAttribute('src', elems[i]);
fragment.appendChild(newNode);
}
target.appendChild(fragment);
JavaScript
DOM
81. Mhhh.. Was ist daran schlimm?
this.boardSize = document.getElementById("benchmarkBox").value;
for (var i = 0; i < this.boardSize; i++) {
//this.boardSize is “25”
for (var j = 0; j < this.boardSize; j++) {
//this.boardSize is “25”
...
}
}
82. DOM-Elemente sind immer ein String.
Ein direktes parseInt ist 25% schneller!
this.boardSize = parseInt(document.getElementById("benchmarkBox").value);
for (var i = 0; i < this.boardSize; i++) {
//this.boardSize is 25
for (var j = 0; j < this.boardSize; j++) {
//this.boardSize is 25
...
}
}
84. Touchverzögerung entfernen mit FastClick
Bei Klick oder Touch findet
eine 300 ms Verzögerung
statt
Entfernen mit FastClick
github.com/ftlabs/fastclick
86. Fazit 1 von 3
Die UI ist für den Benutzer schnell, wenn sie unter 100 ms reagiert
Setze moderne WebViews für die Hybrid-Apps ein
Crosswalk und WKWebView
Verwende eine saubere HTML-Struktur
Regeln für Head und Body beachten
Daten müssen schnell bereitstehen
Styles und Skripte direkt ins App-Paket legen
87. Fazit 2 von 3
Verwende die GPU so viel wie nur möglich
Bilder anstatt CSS Features
Animationen via transform: translate3D
Auf JQuery verzichten
Natives JavaScript nutzen
Gehe sparsam und durchdacht mit Events um
Hänge dich an den Render-Prozess für eine Timer-Funktion
window.requestAnimationFrame
88. Fazit 3 von 3
Erspare JavaScript unnötige Arbeit mit Konvertierungen
Nutze JavaScript Arrays effektiv
Verwende Schleifen
Gehe beachtlich mit dem DOM Zugriff um
Unnötige „Reflows“ ersparen
Touchverzögerung entfernen