On Github Schepp / HTML5-WPO-Slides
(20)
<script src="independantscript.js" defer></script>
Das defer-Attribut ist eine Erfindung von Microsoft und kann an script-Elemente angeheftet werden.
Dieses Script wird nicht mehr an Ort und Stelle ausgeführt, sondern erst wenn es heruntergeladen und das HTML-Dokument fertiggeparsed ist. Es blockiert damit nicht mehr den HTML-Parser.
Normalerweise blockiert der Parser, denn es könnte ja ein document.write im Script vorkommen. Bei defer wird jegliches document.write ignoriert.
<script src="independantscript.js" defer></script>
defer funktioniert nur für extern referenziertes JavaScript. Inline-JavaScript wird immer an Ort und Stelle ausgeführt.
defer kann nicht mit dynamisch per JavaScript ins DOM eingehängten Scripten benutzt werden.
Ein in HTML mit defer ausgestattetes Script verzögert wie gehabt das DOMContentLoaded-Event und auch das globale load-Event.
Der Browser führt alle mit defer markierten Scripte in der im Markup definierten Reihenfolge aus. Sie dürfen also Abhängigkeiten untereinander aufweisen:
<script src="jquery.js" defer></script> <script src="jquery-plugin.js" defer></script>
Ein Mischmasch ist jedoch keine so gute Idee:
<script src="jquery.js" defer></script> <script src="jquery-plugin.js"></script>
Desktop:
✔ ✔ ✔ 4+*/10+*= bei IE < 10 noch hier und da nicht ganz ideal, weil die Reihenfolge "aufbricht", sobald ein Script frühzeitig anfängt, das DOM zu manipulieren.
Presto-Opera unterstützt das defer-Attribut nicht, Blink-Opera (15+) jedoch schon.
Mobile:
3+ ✔ ✔ ✔ 10+Presto-Opera unterstützt das defer-Attribut nicht, Blink-Opera (16+) jedoch schon.
<script src="independantscript.js" async></script>
Das async-Attribut ist eine Erfindung von Mozilla und kann ebenfalls an script-Elemente angeheftet werden.
Dieses Script muss nicht mehr an Ort und Stelle, und auch nicht "in Reihe" ausgeführt werden, sondern kann ausgeführt werden, sobald es heruntergeladen ist. Es blockiert damit weder den HTML-Parser, noch anderes Javascript.
Normalerweise werden Scripte zwar durchaus parallel geladen, dann aber in Reihe ausgeführt, da untereinander Abhängigkeiten bestehen könnten.
<script src="independantscript.js" async></script>
async funtioniert nur für extern referenziertes JavaScript. Inline-JavaScript wird immer in Reihe ausgeführt!
Ein in HTML mit async ausgestattetes Script verzögert nicht mehr das DOMContentLoaded-Event, wohl aber das globale load-Event.
Keine gute Idee:
<script src="jquery.js" async></script> <script src="jquery-plugin.js" async></script>
Da jquery-plugin.js sehr wahrscheinlich kleiner ist als jquery.js wäre es vorher runtergeladen und würde als erstes ausgeführt. Es würde Schiffbruch erleiden.
Besser:
<script src="jquery-und-alle-jquery-plugins.js" async></script> <script src="von-jquery-vollkommen-unabhaengiges-script.js" async></script>
Die Chancen für async steigen mit dem Grad, in dem wir uns von jQuery unabhängig machen (mehr zu dem Thema später).
Scripte werden von Haus asynchron ausgeführt, wenn Sie dynamisch ins DOM gehängt werden:
(function() { var script = document.createElement('script'); script.src = "file.js"; document.body.appendChild(script); })()
Firefox 3.6 und brauchte aufgrund eines Bugs die explizite Anweisung script.async = true;.
Soll ein dynamisch erzeugtes Script explizit nicht asynchron ausgeführt werden:
(function() { var script = document.createElement('script'); script.src = "file.js"; script.async = false; document.body.appendChild(script); })()
Vorsicht bei async und folgenden Konstruktionen:
document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded'); }); window.addEventListener('load', function() { console.log('load'); });
Besser abfragen, ob das wir das Event nicht vielleicht schon verpasst haben:
if ( document.readyState === 'interactive' ) { console.log('DOMContentLoaded'); } else { document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded'); }); } if ( document.readyState === 'complete' ) { console.log('load'); } else { window.addEventListener('load', function() { console.log('load'); }); }
(oder jQuerys $(document).ready() & Co nutzen)
Desktop:
✔ ✔ ✔ 10+Presto-Opera unterstützt das async-Attribut weder in HTML noch in dynamisch erzeugten Scripten. Blink-Opera (15+) unterstützt es.
Mobile:
3+ ✔ ✔ ✔ 10+Presto-Opera unterstützt das async-Attribut weder in HTML noch in dynamisch erzeugten Scripten. Blink-Opera (16+) unterstützt es.
Beim dynamischen Einfügen von Scripten ins DOM wird ein Script normalerweise erst in folgendem Augenblick heruntergeladen:
document.body.appendChild(script);
IE (6+) lädt das Script allerdings schon in diesem Moment:
script.src = "file.js";
Auf die Weise lässt sich in IE ein Script schon vorzeitig laden, so dass es bei Bedarf schneller zu Hand ist.
Der geplante EcmaScript 6 Module Loader wird im Browser asynchron arbeiten (anders als z.B. in Node.js) und dabei trotzdem Modulabhängigkeiten sicherstellen, ähnlich wie es require.js & Co heute schon tun:
/* mymodule.js */ export class q { constructor() { console.log('this is an es6 class!'); } }
<script type="module"> // loads the 'q' export from 'mymodule.js' in the same path as the page import { q } from 'mymodule'; new q(); // -> 'this is an es6 class!'; </script>
Siehe ES6 Modules Polyfill
Die Web Components bringen ein Feature namens HTML Imports mit, das ebenfalls asynchron sein, und Modulabhängigkeiten sicherstellen kann:
<head> <link rel="import" href="flexslider.html" async> <link rel="import" href="fancybox.html" async> </head>
<!-- flexslider.html --> <link rel="import" href="jquery.html"> <script src="js/flexslider.js"></script>
<!-- fancybox.html --> <link rel="import" href="jquery.html"> <script src="js/jquery.fancybox.js"></script>
<!-- jquery.html --> <script src="js/jquery.js"></script>
(32)
<link rel="next" href="./gallery.html"> <link rel="prefetch" href="./js/image-carousel.js"> <link rel="prefetch" href="./images/picture-1.jpg"> <link rel="prefetch" href="./images/picture-2.jpg">
Desktop:
✔* ✘ ✔ 11+*= Aktuell nur via --prerender=enabled Start-Parameter Soll demnächst offiziell freigeschaltet werden
Mobile:
4+ ✘ ✘ 24+ 11+? (WP8.1)<link rel="dns-prefetch" href="//ajax.googleapis.com">
Für durchschnittliche DNS-Zeiten, siehe chrome://histograms/DNS -> Sektion DNS.PrefetchResolution
Gut zum Vorbereiten des Browsers auf externe JavaScript-Libraries:
<head> <link rel="dns-prefetch" href="//ajax.googleapis.com"> </head> <body> ...blah...blah... <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> </body>
Hilfreich bei externen Webfonts, sofern diese erst im Stylesheet referenziert werden:
<head> <link rel="stylesheet" href="styles.css"> <link rel="dns-prefetch" href="//fonts.googleapis.com"> </head>
@import url(//fonts.googleapis.com/css?family=Open+Sans);
(Noch besser ist es, Webfonts gar nicht via CSS zu importieren)
Hilfreich bei weiterleitungen:
<link rel="dns-prefetch" href="//mobil.zeit.de">
http://www.zeit.de/index -> Weiterleitung -> http://mobil.zeit.de/index
(Noch besser ist es, auf Weiterleitungen zu verzichten)
Desktop:
✔ ✘ ✔ 11+Mobile:
✘ 29+ ✘ 24+ 11+? (WP8.1)<head> <link rel="subresource" href="veryimportant.js"> </head> <body> ...blah...blah... <script src="veryimportant.js"></script> </body>
Desktop:
✔ ✘ ✘ 11+Mobile:
✘ ✔ ✘ ✘ 11+? (WP8.1)<link rel="prerender" href="probable-next-page.html">
Aktives Prerender wird im Chrome Task Manager (Shift + Esc) angezeigt:
Folgende Bedingungen müssen erfüllt sein:
Achtung beim Zählen von Seitenaufrufen!
if (document.visibilityState == 'prerender') { document.addEventListener('visibilitychange', handleVisibilityChange, false); }
Wird von Google Analytics & Co schon lange beachtet
Achtung bei zeitbasierten JavaScript-Methoden!
Diese Techniken werden in Hintergrund-Tabs pausiert und fressen dann keine Ressourcen.
Desktop:
✔ ✘ ✘ 11+Mobile:
✘ ✘ ✘ ✘ 11+? (WP8.1)Bei Mobilgeräten gibt es den Interessenskonflikt, dass man gleichzeitig den Datenverbrauch gering halten möchte. Dementsprechend bleibt prerender in Chrome abgeschaltet.
Alle <link>-Anweisungen sollen sich zukünftig auch in HTTP-Header verlagern lassen:
HTTP/1.1 200 OK Date: Thu, 26 Jul 2012 22:27:21 GMT Server: Apache Content-Location: foo.html Vary: negotiate,Accept-Encoding Last-Modified: Thu, 26 Jul 2012 20:55:56 GMT Accept-Ranges: bytes Content-Length: 675 Expires: Thu, 02 Aug 2012 22:27:21 GMT Link: <js/nextpage.js>; rel=prefetch Link: <js/logic.js>; rel=subresource Content-Type: text/html; charset=utf-8
Wird aktuell nur von Firefox unterstützt.
<head> <link rel="stylesheet" src="styles.css"> <link rel="stylesheet" src="animations.css" lazyload> </head> <body> <img src="logo.png"> <img src="header.png"> <img src="additionalImages1.png" lazyload> <img src="additionalImages2.png" lazyload> </body>
Desktop:
✘ ✘ ✘ 11+Mobile:
✘ ✘ ✘ ✘ 11+? (WP8.1)<img src="image.png" postpone>
Eine mit postpone ausgezeichnete Ressource wird erst geladen, sobald sie sichtbar wird:
Eine mit postpone ausgezeichnete Ressource blockiert ebenfalls nicht mehr das globale load-Event
Desktop:
✘ ✘ ✘ ✘Mobile:
✘ ✘ ✘ ✘ ✘lazyload und postpone können an folgende Element-Typen angeheftet werden:
Nope! XHR-Calls sollten nicht für das Vorladen kritischer Ressourcen genutzt werden, da sie eine deutlich geringere Priorität genießen als via <link> deklarierte Ressourcen.
Für die nahe Zukunft sind folgende zusätzliche Link-Anweisungen geplant:
Siehe Diskussion auf lists.w3.org
Der geplante EcmaScript 6 Module Loader wird dem Browser möglicherweise ebenfalls Hinweise bzgl. zu ladender JavaScript-Module geben:
<script type="module"> // loads the 'q' export from 'mymodule.js' in the same path as the page import { q } from 'mymodule'; </script>
Siehe ES6 Modules Polyfill
(21)
var el = $('selector');
var $ = document.querySelectorAll.bind(document); var el = $('#id')[0], els = $('.class'); els.forEach( function(el, index) { el.innerHTML = 'Number ' + (index + 1); });
$('selector').on('click', handleEvent);
Element.prototype.on = Element.prototype.addEventListener; $('selector')[0].on('click', handleEvent);
$('selector').on('click', 'a[href="#"]', handleEvent);
Element.prototype.on = function() { var args = arguments; if ( args.length <= 2 ) { this.addEventListener(args[0], args[1]); } else { this.addEventListener(args[0], function(e) { if ( e.target.matches(args[1] ) { args[2](e); } }); } }; $('selector')[0].on('click', 'a[href="#"]', handleEvent);
$('selector').on('touchstart click', 'a[href="#"]', handleEvent);
Element.prototype.on = function() { var args = arguments, that = this; args[0].split(' ').forEach( function(eventName) { if ( args.length <= 2 ) { that.addEventListener(eventName, args[1]); } else { that.addEventListener(eventName, function(e) { if ( e.target.matches(args[1] ) { args[2](e); } }); } }); }; $('selector')[0].on('touchstart click', 'a[href="#"]', handleEvent);
Desktop:
✔ ✔ ✔ 9+element.matches()/matchesSelector() Doku & Supporttable
Mobile:
2.2+ ✔ ✔ ✔ 9+$.getJSON('/my/url', function(data) {});
var $getJSON = function( url, callback ) { var request = new XMLHttpRequest; request.open('GET', url, true); request.onload = function() { if ( request.status >= 200 && request.status < 400 ){ callback(JSON.parse(request.responseText)); } else { console.log('serverside error fetching ' + url); } }; request.onerror = function() { console.log('clientside error fetching ' + url); }; request.send(); } $getJSON('/my/url', function(data) {});
Desktop:
✔ ✔ ✔ 8+Mobile:
2.1+ ✔ ✔ ✔ 9+$('selector').addClass('classA classB');
['add', 'remove', 'toggle'].forEach(function (mode) { Element.prototype[mode + 'Class'] = function (classes) { var that = this; classes.split(' ').forEach(function (currentclass) { that.classList[mode](currentclass); }); }; }); var el = $('selector')[0]; el.addClass('classA classB');
Desktop:
✔ ✔ ✔ 10+Can I use classList
Mobile:
3+ ✔ ✔ ✔ 10+*= Android 2.x - 3.x können prinzipiell CSS Animationen, haben aber zahlreiche Bugs
Can I use classList
$(el).slideDown();
el { display: none; } .slide { display: block; transition: max-height 300ms; overflow: hidden; } .slideDown { max-height: 500px; }
Element.prototype.slideDown = function() { this.style.display = ''; this.addClass('slide slideDown'); }; el.slideDown();
$(el).slideUp();
.slide { display: block; transition: max-height 300ms; overflow: hidden; } .slideUp { max-height: 0; }
Element.prototype.slideUp = function() { this.on('transitionEnd', function(e) { e.target.style.display = 'none'; }); this.addClass('slide slideUp'); }; el.slideUp();
$(el).fadeIn();
el { display: none; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .fadeIn { display: block; animation: fadeIn 300ms; }
Element.prototype.fadeIn = function() { this.addClass('fadeIn'); }; el.fadeIn();
$('<el/>').appendTo('body').fadeIn();
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } el { animation: fadeIn 300ms; }
var el = document.createElement('el'); el.innerHTML('Elementinhalt'); document.body.appendChild(el);
$(el).fadeOut(300, function() { $(this).remove(); });
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fadeOut { animation: fadeOut 300ms; }
Element.prototype.remove = function() { this.parentNode.removeChild(this); }; Element.prototype.fadeOut = function() { this.on('animationend', function(e) { e.target.remove(); }); this.classList.add('fadeOut'); }; el.fadeOut();
Desktop:
✔ ✔ ✔ 10+Can I use CSS Animation / CSS Transition / classList
Mobile:
4+* ✔ ✔ ✔ 10+*= Android 2.x - 3.x können prinzipiell CSS Animationen, haben aber zahlreiche Bugs
Can I use CSS Animation / CSS Transition / classList
(47)
"Cache Pinning"
OS Browser Max Persistent Cache Size iOS 4.3 Mobile Safari 0 iOS 5.1.1 Mobile Safari 0 iOS 5.1.1 Chrome for IOS 200 MB+ Android 2.2 Android Browser 4 MB Android 2.3 Android Browser 4 MB Android 3.0 Android Browser 20 MB Android 4.0 – 4.1 Chrome for Android 85 MB Android 4.0 – 4.1 Android Browser 85 MB Android 4.1 Firefox Beta 75 MB Blackberry OS 6 Browser 25 MB Blackberry OS 7 Browser 85 MBDesktop:
✔ ✔ ✔ 10+Mobile:
2.1+ ✔ ✔ ✔ 10+Leider das Enfant Terrible von HTML5:
The Application Cache has skills we need, but if you asked him to paint your bathroom he'd somehow manage to flood your kitchen and break your TV in the process, and he wouldn't care.
<html manifest="offline.appcache">
offline.appcache:
CACHE MANIFEST http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js assets/script/main.js assets/style/main.css assets/style/fonts/font.woff assets/style/images/sprite.png
Die aufgelisteten Ressourcen werden alle offline gespeichert und ab dem nächsten Aufruf der referenzierenden HTML-Seite von Platte bezogen.
<html manifest="offline.appcache">
Leider wird immer auch die referenzierende HTML-Datei offline gespeichert!
Der Browser stellt also in Zukunft immer dieselben Inhalte dar, sofern diese wie üblich im HTML stecken.
WTF?
Weil die HTML-Datei mitgespeichert wird, bringt es auch nichts, wenn man mit klassischen Cachebusting-Methoden Updates erzwingen will:
<html manifest="offline.appcache?v=2">
Die Änderungen passiert online und nicht offline beim User. Bringt also nichts!
Die einzige Möglichkeit, den Browser zum Aktualisieren von Ressourcen zu bewegen, ist die Manifestdatei inhaltlich zu verändern, z.B. durch eine integrierte Versionsnummer:
CACHE MANIFEST # v2 http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js assets/script/main.js assets/style/main.css assets/style/fonts/font.woff assets/style/images/sprite.png
Achtung: Niemals Far-Future Cache Header für die Manifest-Datei setzen, weil man sonst keine Möglichkeit mehr hat, Dateien zu aktualisieren.
Der Besucher bliebe in der Vergangenheit gefangen.
Apropos Cache-Header: Der Application Cache ist eine zweite Cache-Layer über dem üblichen clientseitigen Cache. Daher ist es weiterhin sinnvoll, alle Ressourcen mit Far-Future Cache Headern auszustatten, so dass die Browser nur die wirklich aktualisierten Dateien beim Updaten des Manifests vom Server lädt.
CACHE MANIFEST # v2 http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js assets/script/main.js assets/style/main.css assets/style/fonts/font.woff assets/style/images/sprite.png
Der Browser checkt und verarbeitet das Manifest immer erst nach Ende des Seitenladens. Sprich, neue Dateien manifestieren sich erst beim übernächsten Aufruf.
Ressourcen, die nicht im Manifest stehen, im HTML/CSS/JS aber referenziert werden, werden bei aktivem Offline-Modus nicht mehr geladen - auch dann nicht, wenn eine Onlineverbindung zu ihnen besteht. Abhilfe schafft der NETWORK-Eintrag:
CACHE MANIFEST # v1 assets/foo.bar NETWORK: *
CACHE MANIFEST # v1 assets/foo.bar NETWORK: *
Das funktioniert dummerweise nur mit der HTML-Datei selbst nicht!!! Sie ist der sogenannte "Masterindex", args...
Lösung: Ein nahezu leeres HTML Grundgerüst verwenden und die Inhalte nachladen:
<!DOCTYPE html> <html manifest="offline.appcache"> <head></head> <body> <script>(function(){ var request = new XMLHttpRequest; request.open('GET', 'import.html', true); request.onload = function() { document.body.innerHTML = request.responseText; }; request.send(); }());</script> </body> </html>
(function(){ var request = new XMLHttpRequest; request.open('GET', 'import.html', true); if(content = localStorage.getItem('import.html')) { document.body.innerHTML = content; request.onload = function() { localStorage.setItem('import.html', request.responseText); }; } else { request.onload = function() { localStorage.setItem('import.html', request.responseText); document.body.innerHTML = request.responseText; }; } request.send(); }());
<!DOCTYPE html> <html manifest="offline.appcache"> <head> <link rel="import" href="import.html"> </head> <body></body> </html>
<div id="content"><p>Hello World!</p></div> <script>(function(){ var importDoc = document.currentScript.ownerDocument, content = importDoc.querySelector('#content').innerHTML; if (document.readyState === 'interactive') { document.body.innerHTML = content; } else { document.addEventListener('DOMContentLoaded', function(){ document.body.innerHTML = content; }) } }());</script>
<!DOCTYPE html> <html manifest="offline.appcache"> <head> <link rel="import" href="import.html"> </head> <body> <script>(function(){ if(!('import' in document.createElement('link'))){ var request = new XMLHttpRequest; ... } }());</script> </body> </html>
Desktop:
2014* ✘ 2014 ?*= ab Chrome 31 in about:flags als Eintrag Enable HTML Imports freischaltbar
Mobile:
? 2014 ✘ 2014 ?Nachteile von AppCache:
Noch was? Jepp! Firefox jagt dem Benutzer Angst ein, indem er beim ersten Besuch danach fragt, ob die Seite Daten offline speichern darf:
Einzige Abhilfe: Serverseitiges Useragent-Sniffing :(
Weiterführende Literatur:
Synchrone API zum Speichern von Key/Value-Paaren:
localStorage.setItem('key','value'); var value = localStorage.getItem('key'); localStorage.removeItem('key'); localStorage.clear(); // Leert ihn komplett
Verarbeitet eigentlich nur Strings...
Kann via JSON-Codierung auch Arrays oder Objekte speichern:
var arr = [0, 1, 2], obj = { 'key1': 0, 'key2': 0 }; localStorage.setItem('arr',JSON.stringify(arr)); localStorage.setItem('obj',JSON.stringify(obj)); arr = JSON.parse(localStorage.getItem('arr')); obj = JSON.parse(localStorage.getItem('obj'));
Desktop:
✔ ✔ ✔ 8+Mobile:
✔ ✔ ✔ ✔ 9+Kann via base64-Codierung auch Binärdaten speichern, z.B. von Bildern:
var image = new Image(); image.onload = function() { var canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; canvas.getContext('2d').drawImage(image, 0, 0); localStorage.setItem('image',canvas.toDataURL('image/jpeg')); var secondImage = new Image(); secondImage.src = localStorage.getItem('image'); } image.src = 'picture.jpg';
Desktop:
✔ ✔ ✔ 9+Mobile:
✔ ✔ ✔ ✔ 9+Nicht nur nützlich fürs "Cache Pinning", sondern ideal zum Verringern von HTTP-Requests durch bedingtes Inlinen.
Es werden alle Dateien als <style>- und <script>-Blöcke ge-inlined
<style data-id="a6g7h89f" data-modified="123456789">...</style>
Nach dem Seitenladen liest ein Script alle Blöcke einzeln aus
speichert sie im localStorage mit data-id als Key und dem Inhalt als Value setzt einen Cookie mit data-id als Key und data-modified als Valuedocument.addEventListener('DOMContentLoaded', function() { var items = document.querySelectorAll('style[data-id],script[data-id]'); items = Array.prototype.slice.call(items,0); items.forEach(function (item) { var key = item.getAttribute('data-id'), timestamp = item.getAttribute('data-modified'), value = item.textContent; localStorage.removeItem(key); localStorage.setItem(key, value); if (localStorage.getItem(key)) { document.cookie = key + '=' + timestamp; console.log('Added/updated ' + key + ' to localStorage'); } }); });
foreach($files as $file) { $id = md5($file); if(!isset($_COOKIE[$id]) || $_COOKIE[$id] != filemtime($file)) { echo '<script data-id="'.$id.'" data-modified="'.filemtime($file).'"> '.file_get_contents($file).' </script>'; } else { echo '<script> var script = document.createElement("script"); script.textContent = localStorage.getItem("'.$id.'"); document.body.appendChild(script); </script>'; } }
Vorteile:
Tipp: localForage ist ein Tool von Mozilla, das die localStorage-API aufgreift und:
Lust die Demo zu erweitern:
The Service Worker Specification
ETA: Anfang 2015
(52)
Ergänzend zum Hauptprozessor, der Central Processing Unit, gibt es die Graphics Processing Unit, den Grafikprozessor. Manchmal auch General Processing Unit genannt, weil man der GPU auch andere Dinge als nur Grafik berechnen kann, z.B. via WebCL (siehe auch).
Einmal erledigt bereitet der Browser diejenigen Bereiche vor, die bei einem Scrollvorgang als nächstes in den sichtbaren Bildausschnitt rücken würden.
Wie viel er vorbereitet hängt von der freien Speicherkapazität des jeweiligen Geräts ab:
Geht der Speicher zur Neige, verwirft der Browser entfernt liegende Kacheln zuerst, z.B. weggescrollte.
Die GPU arbeitet intern mit texturierten 3D-Flächen
Das Scrolling übernimmt nun komplett die GPU, in dem Sie die 3D-Fläche herumschiebt.
Vorteile
Nachteile auf Mobilgeräten
Kuriosum: Fixed positionierte Animated GIFs werden weiterhin animiert!
*= Chrome auf Android wählt einen Mittelweg und updated zwischendurch.
Da die CPU außen vor bleibt, empfängt sie keinerlei onscroll-Events von der GPU
Bei "klebenden" Menüs schafft die neue Eigenschaft position: sticky Abhilfe (vorher/nachher). Unterstützen bisher nur die Safaris.
Menü ausschließlich mit transform: translateX bewegen
Durch transform: translateX kann es bewegt werden, ohne für den Rest der Seite einen "Reflow" auszulösen.
Startet eine CSS Animation, die auf transform, opacity oder filter (außer dem Blur-Filter) abzielt, löst der Browser das Element von der aktuellen Zeichenebene aus, und erzeugt in der GPU eine zweite Ebene mit dem Element. Sie wird "in eine eigene Layer promoted".
Nach der Animation werden die Ebenen wieder vereint.
Eine promotete Ebene kann einzeln sehr effizient in der GPU transformiert oder durchsichtig gemacht werden, ohne dass der Rest der Seite angefasst werden muss. Und ohne dass die CPU etwas damit zu tun hat (siehe vorher/nachher.
Compositing (englisch für Zusammensetzung, Mischung) ist ein Begriff aus der Video- und Filmtechnik und findet in der Postproduktion eines Filmes als visueller Effekt Anwendung. Im Compositing werden zwei oder mehr voneinander getrennt aufgenommene oder erstellte Elemente zu einem Bild zusammengeführt. In der Computergrafik versteht man unter Compositing das Zusammenfügen mehrerer hintereinanderliegender Schichten eines Volumens.
backface-visibility: hidden und transform: translateZ(0) zwingen ein Element immer in eine eigene Ebene.
Sinnvoll, wenn eine Layer später sowieso promoted wird.
Eine promotete Ebene kann einzeln sehr effizient in der GPU transformiert [...] werden, [...] ohne dass die CPU etwas damit zu tun hat.
Gut bei Blockaden des UI-Threads (z.B. durch JavaScript)! (siehe vorher/nachher)
Eine promotete Ebene kann einzeln sehr effizient in der GPU transformiert [...] werden, [...] ohne dass die CPU etwas damit zu tun hat.
Auch ein gutes Mittel gegen sogenannte "Paint Storms".
Beispielseite mit vielen "Paint Storms": thenextweb.com
Nachteil: Jede promotete Layer verbraucht zusätzlichen Speicher, und zwar mindestens Pixel x Pixel x 4 Bytes (bei Retina 4 mal so viel)
*= in Chrome nur auf HiDPI-Displays, wegen Schriftglättung
You might wonder why we don’t automatically promote fixed position elements. The answer is: we do for high DPI screens, but we don’t for low DPI because we lose sub-pixel antialiasing on text, and that’s not something we want to by default. On high DPI screens you can’t tell, so it’s safe there.
Wenn der Chrome-Browser mehrere fixed positionierte Elemente auf einer Seite antrifft, fasst er sie ohne extra Eingriff alle zu einer Layer zusammen.
Das kann dann sehr schlecht enden, wenn die Elemente weit auseinander stehen. Explizite Layer Promotion hilft.
*, *:before, *:after { backface-visibility: hidden; }
Niemals!
Reflows kosten richtig viel Zeit, da das gesamte Layout neu berechnet und gepainted werden muss.
Diese DOM-Methoden, angewendet auf die angegebenen Elemente erzeugen einen Reflow:
// Read var h1 = element1.clientHeight; // Write (invalidates layout) element1.style.height = (h1 * 2) + 'px'; // Read (triggers layout) var h2 = element2.clientHeight; // Write (invalidates layout) element2.style.height = (h2 * 2) + 'px'; // Read (triggers layout) var h3 = element3.clientHeight; // Write (invalidates layout) element3.style.height = (h3 * 2) + 'px';
Schlecht!
// Read var h1 = element1.clientHeight; var h2 = element2.clientHeight; var h3 = element3.clientHeight; // Write (invalidates layout) element1.style.height = (h1 * 2) + 'px'; element2.style.height = (h2 * 2) + 'px'; element3.style.height = (h3 * 2) + 'px';
Reads und Writes batchen: Perfekt!
// Read var h1 = element1.clientHeight; // Write requestAnimationFrame(function() { element1.style.height = (h1 * 2) + 'px'; }); // Read var h2 = element2.clientHeight; // Write requestAnimationFrame(function() { element2.style.height = (h2 * 2) + 'px'; });
Writes per requestAnimationFrame in die Zukunft verlegen: <3!
Reflow lassen sich mit Hilfe von "Layout Boundaries" begrenzen.
Layout Boundaries sind Elemente mit unverrückbarer Größe, die eine Reflow-Welle ähnlich wie ein Deich stoppen.
Eingebettete SVGs bilden Layout Boundaries oder Elemente...
Tool zum Highlighten von Elementen mit "Layout Boundaries"-Potential:
Nicht zu viele CSS3-Effekte einsetzen! Speziell in Kombination werden sie teuer.
Auch wenn es anachronistisch klingt: Im Zweifel besser Bilder verwenden.
Die Browserhersteller arbeiten an einer neuen CSS-Eigenschaft namens will-change, die den Browsern signalisiert, dass eine Eigenschaft sich um Lauf des Lebenszyklus' ändern wird:
html { will-change: scroll-position; } .itemlist { will-change: contents, scroll-position; } .animated { will-change: transform, opacity; }
Wie würdet Ihr diese Animation ruckelfreier machen?
Es gibt zwei Lösungen. Bonuspunkt für den, der beide Lösungen findet!
(17)
Der Client kann bestimmte Ressourcen mit höherer Priorität anfordern
Google observed an ~88% reduction in the size of request headers and an ~85% reduction in the size of response headers after enabling compression. This amounted to a saving of between 45 and 1142 ms in the overall page load time.
Die Browser supporten nur verschlüsseltes SPDY:
Läuft vollautomatisch!
GET /page HTTP/1.1 Host: server.example.com Connection: Upgrade, HTTP2-Settings Upgrade: HTTP/2.0 HTTP2-Settings: (SETTINGS payload in Base64)
HTTP/1.1 200 OK 3 Content-length: 243 Content-type: text/html (... HTTP 1.1 response ...)
HTTP/1.1 101 Switching Protocols 4 Connection: Upgrade Upgrade: HTTP/2.0 (... HTTP 2.0 response ...)
SPDY clients consume one worker instead of six
Desktop:
✔ ✘ ✔ 11+**= nur IE11 auf Windows 8+
Mobile:
3+ 33+ ✘ 24+ 11+? (WP8.1)Serverunterstützung
(9)
Chrome & Firefox:
<meta name="viewport" content="width=device-width">
Alle anderen Browser:
<meta name="viewport" content="width=device-width, user-scalable=no">