Ladezeitenoptimierung
(Web Performance Optimization)

webpages have to be designed with speed in mind. In fact, speed must be the overriding design criterion.
Jakob Nielsen, 1. März 1997

Wirkung von Ladezeiten

Dauer Wirkung
0 - 1 s. gefühlt "unmittelbar", sehr angenehm
1 - 10 s. spürbar, aber im erträglichen Rahmen
10+ s. unerträglich!
Man verliert den Faden und die Lust an der Seite

Die Durchschnittserwartung liegt bei 4 Sekunden Ladezeit.

Quelle: Jakob Nielsen

Psychologische Hintergründe

Quelle: Jakob Nielsen

Ladezeit und Besucherverhalten

Ladezeit Pageviews Konversionsrate Abbruchquote
1 s. +/-0% +/-0% +/-0%
3 s. -22% -22% +50%
5 s. -35% -38% +105%
10 s. -46% -42% +135%
Quelle: Strangeloop

Prominente Erfolgsstories

80% of the end-user response time is spent on the front-end. Most of this time is tied up in downloading all the components in the page: images, stylesheets, scripts, Flash, etc.
Das Yahoo! Exceptional Performance Team

Leider...

Jahr Dateianzahl Dateigöße
1995 2,3 Dateien 14,1 KB
2010 75 Dateien 498 KB

Hallo Spiegel Online!

Breitband to the rescue?

Nope!

Quelle: Mike Belshe

Das Trio Infernale:
Die HTTP-Spezifikation + die Dateianzahl + die Latenz

A single-user client SHOULD NOT maintain more than 2 connections with any server
Die IETF in der Spezifikation des HTTP-1.1 Protokolls

Ergebnis:

Ein Wasserfall, je 2 Dateien parallel (IE6 & 7),
Extra-Latenzen addieren sich pro Stufe auf. Thank you, IETF.

Ergebnis in modernen Browsern:

Wasserfall, je 6 Dateien parallel (Chrome, Firefox & Co) Besser. Extra-Latenzen addieren sich dennoch auf.

4 × 1 Datei < 1 × 4 Dateien

Gut:

Schlecht:

Arbeitsschritte innerhalb eines Dateiabrufs

  1. Domain wird per Domain Name System aufgelöst
  2. TCP-Verbindung wird aufgebaut
  3. Bei SSL: Zertifikatsüberprüfung
  4. Anforderung reist zum Server *dummdidumm*
  5. Server erzeugt Antwort
  6. Anwort reist zurück *dummdidumm*

Latenzen sind größtenteils Reisezeiten

Latenz =

Entfernung in km / (0.66 x Lichtgeschwindigkeit)
+ ca. 10ms (wg. Signalwandler)
+ "letzte Meile":

Leitungstechnik Latenz
Kabelnetzwerk 1 ms
Drahtlosnetzwerk 5 ms
Fernsehkabel 5 ms
DSL 20 ms
ISDN 100 ms
UMTS 175 ms
GPRS / EDGE 425 ms

Hamburg – München – Hamburg
durch Latenz unproduktiv verbrachte Zeit:


via DSL + WLAN:
33 ms + 20 ms + 5 ms
= 58 ms


via UMTS:
33 ms + 175 ms
= 208 ms

Maßnahme Nummer 1:
Dateianzahl reduzieren!

Alle JavaScript-Dateien zusammenbacken

Nicht schön:

<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/jquery-fancybox.js"></script>
        

Besser:

<script src="js/jquery-jquery-ui-jquery-fancybox.js"></script>
        

Alle CSS-Dateien zusammenführen

Nicht schön:

<html>
<head>
<link rel="stylesheet" href="desktop.css" media="screen">
<link rel="stylesheet" href="mobile.css" 
media="screen and (max-width: 1024px)">
<link rel="stylesheet" href="tablet.css" 
media="screen and (max-width: 768px)">
<link rel="stylesheet" href="print.css" media="print">
<!--[if IE]> <link rel="stylesheet" href="ie.css"> <![endif]-->

Besser:

<!--[if !IE]><!--> <html> <!--<![endif]-->
<!--[if IE]> <html class="ie"> <![endif]-->
<head><link rel="stylesheet" href="styles.css">

styles.css

body { width: 1024px; }

.ie body { zoom: 1; }

@media print {
  body { width: auto; }
}

@media screen and (max-width: 1024px) {
  body { width: 768px; }
}

@media screen and (max-width: 768px) {
  body { width: 320px; }
}

PHP-Helferlein

Per PHP zusammenfassen:

<?php
header("Content-type: text/javascript");
echo file_get_contents("jquery.js")."\r\n";
echo file_get_contents("jquery-ui.js")."\r\n";
echo file_get_contents("jquery-fancybox.js")."\r\n";
?>

und als JavaScript dieses PHP einbinden:

<script src="js/scripte.php"></script>
@import url("reset.css");
@import url("page.css");
@import url("navi.css");
@import url("content.css");

besser:

<?php
header("Content-type: text/css");
echo file_get_contents("reset.css")."\r\n";
echo file_get_contents("page.css")."\r\n";
echo file_get_contents("navi.css")."\r\n";
echo file_get_contents("content.css")."\r\n";
?>
<link rel="stylesheet" href="styles.php">

Bildanzahl reduzieren via "CSS-Sprite"-Technik...

<a href="#"><img src="neu.png"></a>
<a href="#"><img src="oeffnen.png"></a>
<a href="#"><img src="speichern.png"></a>

Besser:

a { 
    display: inline-block; 
    width: 16px; 
    height: 16px; 
    background-image: url(sprite.png);
}
a.neu { background-position: 0 0; }
a.oeffnen { background-position: -16px 0; }
a.speichern { background-position: -32px 0; }

Die Sprite-Idee kommt aus der Spielebranche...

Spriting Helferlein…

Bildanzahl reduzieren via "Data URI"-Technik...

Vorher:

<img src="bild.gif">

Nachher:

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAOfo7AAAACH5BAAAAAAA
LAAAAAABAAEAAAICRAEAOw==">

WTF?!!?

Data URI Aufbau

data:

+ MIME-Typ der Datei, z.B.
image/gif
+
optional
;charset=

+ Zeichensatz des Inhalts, z.B.
utf-8
+
;base64,

+ Dateiinhalt in Base-64-Codierung


Data URI & IE8

IE8 kennt Data URI, belegt sie aber mit Einschränkungen:

Data URI Helferlein…

Aufgeblasene Bilder wegen Data URI

Aus 24KB Binärdaten werden 32KB Data URI Daten. Als Gegenmaßnahme ZIP-Kompression aktivieren:

<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE application/atom+xml \
    application/javascript application/json \
    application/rss+xml application/vnd.ms-fontobject \
    application/x-font-ttf application/xhtml+xml \
    application/xml font/opentype \
    image/svg+xml image/x-icon text/css text/html \
    text/plain text/x-component text/xml
</IfModule>
        

Eintrag in der .htaccess Datei

Maßnahme Nummer 2:
Cachen!

Einmal geladene Ressourcen sollen dauerhaft auf Platte gecached bleiben.

Alles wird 1 Monat gecached, außer: das Dokument, AJAX-Calls und eine mögliche AppCache-Datei.

<IfModule mod_expires.c>
  ExpiresActive on

  ExpiresDefault                          "access plus 1 month"

  ExpiresByType text/html                 "access plus 0 seconds"
  ExpiresByType text/xml                  "access plus 0 seconds"
  ExpiresByType application/xml           "access plus 0 seconds"
  ExpiresByType application/json          "access plus 0 seconds"
  ExpiresByType text/cache-manifest       "access plus 0 seconds"
</IfModule>
        

Eintrag in der .htaccess Datei

Nachträgliche Änderungen an gecachten Dateien

Um nachträglich geänderte Dateien an den Mann zu bringen, die im Browser gecached liegen, muss man die URL abändern. Am besten klappt das mit GET-Parametern, z.B. einer hochzählenden Versionierung:

<script src="script.js?v=2"><script>

...oder einem automatisierten Timestamp (etwas mehr Belastung für den Server):

<script src="script.js?timestamp=<?php echo filemtime('script.js') ?>"><script>

Maßnahme Nummer 3:
Anordnung optimieren!

Alle CSS-Dateien im Quelltext in den <head>-Bereich.

Grund:

  • Ältere IEs blockieren das erste Rendering der Seite, bis alle CSS-Dateien heruntergeladen sind.
  • In anderen Browsern erzeugt es einen Flush of unstyled Content (FOUC) und...
  • Es sorgt für überflüssige Dokumenten-Repaints

Einbindung von Schriften ins HTML in den <head>-Bereich.

Fehlende Schriften erzeugen eine Renderblockade.

Im HTML referenzieren spart gegenüber in externem CSS Latenz:

    <style>
    @font-face {
      font-family: 'Open Sans';
      font-style: normal;
      font-weight: 400;
      src: local('Open Sans'), local('OpenSans'), url(blabla.woff) format('woff');
    }
    </style>
    <link rel="stylesheet" href="...">
</head>

Alle JS-Dateien im Quelltext vor das schließende </body>.

JavaScript stoppt das progressive Rendern der Seite an der Stelle der Einbindung.

Beispiel: Testaufbau bei Cuzillion

JavaScript im Fuß der Seite bremst dort genauso, wird aber nicht bemerkt.

Sorge tragen, dass non-funktionale Elemente solange nicht als "kaputt" auffallen, bzw. dass sie ausgeblendet sind:

.interaktives_element { display: none; }
$(document).ready(function(){
	$('.interaktives_element').interaktivMach().fadeIn();
});

charset-Angabe als allererstes Element in den Head

Ansonsten beginnt der IE das Parsen der Seite nochmal neu, wenn er auf das Charset stößt:

<head>
<meta charset="utf-8">
<title>Foobar</title>

X-UA-Compatible-Angabe so früh wie möglich in den Head

Auch hier beginnt der IE das Parsen der Seite nochmal neu, wenn er auf die Metaangabe stößt:

<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=IE7">

Auch sollte kein JavaScript vorher kommen

Maßnahme Nummer 4:
Dateigrößen reduzieren

JavaScript und CSS durch "Minifizieren" verkleinern

Besonders relevant aufgrund des blockierenden Verhaltens dieser Dateitypen.

JavaScript und CSS Minifizierungstools

Name

Sprache

für…

Webservice

Yahoo! YUI Compressor

Java

JS + CSS

Ja

Google Closure Compiler

Java

JS

Ja

UglifyJS

Node.js

JS

Ja

Microsoft AJAX Minifier

.NET

JS + CSS

Nein

CSSTidy

PHP

CSS

Ja

JSMin+

PHP

JS

Nein


z.B. YUI Compressor

java -jar yuicompressor-2.4.6.jar -o "../css/datei.min.css" "../css/datei.css"

CSS Minifizieren

vorher:

.klasse { 
	font-style: normal;
	font-variant: normal;
	font-weight: bold;
	font-size: 1em;
	line-height: 1.7;
	font-family: Arial, Helvetica, sans-serif;
}

nachher:

.klasse{font:bold 1em/1.7 Arial,Helvetica,sans-serif;}

JavaScript Minifizieren

vorher:

if(a == 1){
	var x = 'eins';
}
else {
	var x = 'null';
}

nachher:

var x=a==1?'eins':'null';

jQuery minifiziert

jQuery versionNormal file size (in bytes)Minified file size (in bytes)Gzipped file size (in bytes)Minified and gzipped file size (in bytes)
1.4.4183,184 bytes (178.89 KB) 78,601 bytes (76.76 KB) 51,690 bytes (50.48 KB) 27,073 bytes (26.44 KB)
1.6.2236,202 bytes (230.67 KB) 91,556 bytes (89.41 KB) 67,775 bytes (66.19 KB) 32,065 bytes (31.31 KB)
1.8.1261,525 bytes (255.40 KB) 92,793 bytes (90.62 KB) 77,626 bytes (75.81 KB) 33,204 bytes (32.43 KB)

Das passende Bildformat wählen

 

SVG

7,57 KB

51,50 KB

4,50 KB

 

PNG 200px

6,50 KB

11,10 KB

4,18 KB

PNG 400px

11,80 KB

27,60 KB

8,32 KB

PNG 800px

18,50 KB

72,70 KB

14,80 KB

Fett = kleiner als SVG.

SVG gewinnt an Land, je mehr die Bildfläche zunimmt.

JPEGs eindampfen

Metadaten raus, Optimierung der bei der Kompression verwendeten sogenannten Huffman Tabellen, geringere Farbunterabtastung.

PNG 24/8 vermeiden

Stattdessen PNG 8/8 verwenden.

Erzeugung per Fireworks oder Umwandelung per PNGQuant auf der Kommandozeile: pngquant 256 *.png
Oder via PNGMini auf dem Mac

PNGs eindampfen

Metadaten und "Chunks" raus

SVGs eindampfen

Detailgrad der Formen reduzieren und Metadaten sowie Füllmaterial aus dem SVG-Quelltext entfernen

Scour

python scour.py -i bild.svg -o bild-opt.svg

Zeichenumfang der Schriften eindampfen

Wenn klar ist, dass nur Deutsch geschrieben wird, kann man sich viele länderspezifische Zeichen sparen. Zeichenreduktion z.B. via Font Squirrel @font-face Generator.

Schriftdateien zippen

Datei Ursprungsgröße, bytes gezippte Größe, bytes Ersparnis, %
AllerDisplay.ttf 95,616 47,771 50.04%
Aller_Bd.ttf 128,368 59,884 53.35%
Aller_BdIt.ttf 123,556 58,942 52.30%
Aller_It.ttf 120,876 58,459 51.64%
Aller_Lt.ttf 132,780 60,766 54.24%
Aller_LtIt.ttf 122,296 57,342 53.11%
Aller_Rg.ttf 134,436 63,379 52.86%
Sansation_Bold.ttf 19,644 10,467 46.72%
Sansation_Light.ttf 19,568 10,425 46.72%
Sansation_Regular.ttf 19,480 10,172 47.78%

Außer WOFF-Dateien. Die sind eh schon gezippt.

Maßnahme Nummer 5:
Lazyloading und Preloading

Lazyloaden, also Nachladen von Bildern

Bilder erst beim Ins-Bild-Scrollen einladen, z.B. via Lazy Load Plugin for jQuery:

<img src="blank.gif" data-original="blubb.jpg" width="640" heigh="480">
$(document).ready(function(){
    $("img").lazyload();
});

Beispiel

Lazyloaden von nicht-kritischen Bibliotheken

In jQuery via getScript-Methode. z.B. vorher:

$(window).load(function() {
    $('.flexslider').flexslider();
});

nachher:

$(window).load(function() {
    window.setTimeout(function(){
        $.getScript('js/jquery.flexslider.js', function(){
            $('.flexslider').flexslider();
        });
    }, 2000);
});

Lazyloaden von nicht-kritischen Bibliotheken

oder sowas:

$('#dings').mouseenter(function({
  $.getScript('js/dingsbumms.js', function(){
    $('#dings a').click(function(){
      machwasschoenes();
    });
  });
});

$.getScript Caching beibringen

getScript-Methode cached von Haus aus nicht, weil sie für AJAX-Calls gedacht ist. Und die sollen nicht gecached werden. So baut man es um:

$.getScript = function(url, callback){
	$.ajax({
			type: "GET",
			url: url,
			success: callback,
			dataType: "script",
			cache: true
	});
};

Prefetching

In HTML5 gibt es <link rel="prefetch">.

Für ganze Seiten:

<link rel="prefetch" href="http://goatse.com">

für einzelne Ressourcen:

<link rel="prefetch" href="http://goatse.com/tubgirl.jpg">

Unterstützt von Firefox

Prerendering

Chrome unterstützt <link rel="prerender">:

<link rel="prerender" href="http://goatse.com">

Aber nur einmal im Dokument. Chrome lädt UND rendert dann dieses Dokument schon einmal im Hintergrund vor, so dass es, wenn es annavigiert wird, sofort bereitsteht.

Messwerkzeuge

Online-Messwerkzeuge

Offline-Messwerkzeuge

  • Google PageSpeed Plugin
  • Yahoo! YSlow Plugin / Bookmarklet
  • HTTP Watch für IE

Das war's für jetzt. Es gibt noch viel mehr Optimierungsmöglichkeiten. Über die können wir jetzt im Nachgang sprechen. Merci :)