Medienabfragen sind toll, aber…
Media Queries sind eine tolle Sache und ein Geschenk des Himmels für Website-Entwickler, die kleine Änderungen an ihren Stylesheets vornehmen möchten, um Nutzern auf Geräten unterschiedlicher Größe eine bessere Nutzererfahrung zu bieten. Mit Medienabfragen können Sie das CSS Ihrer Website je nach Bildschirmgröße anpassen. Bevor Sie diesen Artikel lesen, sollten Sie sich mit responsivem Design vertraut machen. Einige gute Beispiele für die Verwendung von Media-Queries finden Sie unter mediaqueri.es.
Wie Brad Frost in einem früheren Artikel betont, ist die Änderung des Erscheinungsbilds nur einer von vielen Aspekten, die beim Erstellen von Websites für das mobile Web berücksichtigt werden müssen. Wenn Sie beim Erstellen Ihrer mobilen Website nur das Layout mit Media-Anfragen anpassen, ergibt sich Folgendes:
- Alle Geräte erhalten dasselbe JavaScript, CSS und dieselben Assets (Bilder, Videos), was zu unnötig langen Ladezeiten führt.
- Alle Geräte erhalten dasselbe ursprüngliche DOM, was Entwickler möglicherweise dazu zwingt, übermäßig kompliziertes CSS zu schreiben.
- Es gibt nur wenig Flexibilität, um benutzerdefinierte Interaktionen für die einzelnen Geräte festzulegen.
Web-Apps benötigen mehr als Media-Queries
Versteh mich nicht falsch. Ich habe nichts gegen responsives Design über Media-Queries und denke, dass es definitiv seinen Platz in der Welt hat. Außerdem lassen sich einige der oben genannten Probleme mit Ansätzen wie responsiven Bildern oder dynamischem Script-Laden beheben. Ab einem bestimmten Punkt kann es jedoch sein, dass Sie zu viele inkrementelle Anpassungen vornehmen und es besser ist, verschiedene Versionen bereitzustellen.
Wenn die von Ihnen erstellten UIs komplexer werden und Sie sich für Single-Page-Web-Apps entscheiden, müssen Sie mehr tun, um UIs für jeden Gerätetyp anzupassen. In diesem Artikel erfahren Sie, wie Sie diese Anpassungen mit minimalem Aufwand vornehmen können. Dabei wird das Gerät des Besuchers in die richtige Geräteklasse eingeteilt und die entsprechende Version auf dem Gerät bereitgestellt. Gleichzeitig wird die Wiederverwendung von Code zwischen den Versionen maximiert.
Welche Geräteklassen sind für Sie relevant?
Es gibt unzählige internetfähige Geräte und fast alle haben einen Browser. Die Komplikation liegt in ihrer Vielfalt: Mac-Laptops, Windows-Arbeitsstationen, iPhones, iPads, Android-Smartphones mit Touch-Eingabe, Scrollräder, Tastaturen, Spracheingabe, Geräte mit Druckempfindlichkeit, Smartwatches, Toaster und Kühlschränke und viele mehr. Einige dieser Geräte sind weit verbreitet, andere sehr selten.
Um eine gute Nutzererfahrung zu schaffen, müssen Sie wissen, wer Ihre Nutzer sind und welche Geräte sie verwenden. Wenn Sie eine Benutzeroberfläche für einen Desktopnutzer mit Maus und Tastatur entwickeln und sie einem Smartphone-Nutzer zur Verfügung stellen, wird dieser frustriert sein, da die Oberfläche für eine andere Bildschirmgröße und eine andere Eingabemethode konzipiert ist.
Es gibt zwei extreme Ansätze:
Eine Version erstellen, die auf allen Geräten funktioniert. Die Nutzerfreundlichkeit leidet darunter, da für verschiedene Geräte unterschiedliche Designüberlegungen gelten.
Erstellen Sie eine Version für jedes Gerät, das Sie unterstützen möchten. Das wird ewig dauern, weil Sie zu viele Versionen Ihrer Anwendung erstellen. Außerdem müssen Sie bei jedem neuen Smartphone, das auf den Markt kommt (was ungefähr wöchentlich passiert), eine weitere Version erstellen.
Hier gibt es einen grundlegenden Kompromiss: Je mehr Geräteklassen Sie haben, desto besser ist die User Experience, die Sie bieten können. Allerdings ist auch der Aufwand für Design, Implementierung und Wartung höher.
Aus Leistungsgründen oder wenn sich die Versionen, die Sie für verschiedene Geräteklassen bereitstellen möchten, stark unterscheiden, kann es sinnvoll sein, für jede Geräteklasse eine separate Version zu erstellen. Andernfalls ist responsives Webdesign ein durchaus sinnvoller Ansatz.
Mögliche Lösung
Ein Kompromiss: Geräte in Kategorien einteilen und für jede Kategorie die bestmögliche Nutzerfreundlichkeit entwickeln. Welche Kategorien Sie auswählen, hängt von Ihrem Produkt und Ihrer Zielgruppe ab. Hier ist ein Beispiel für eine Klassifizierung, die die beliebten webfähigen Geräte abdeckt, die es heute gibt.
- Kleine Bildschirme + Touch (hauptsächlich Smartphones)
- große Displays + Touch (hauptsächlich Tablets)
- Große Bildschirme + Tastatur/Maus (meist Desktop-Computer/Laptops)
Dies ist nur eine von vielen möglichen Aufschlüsselungen, aber eine, die zum Zeitpunkt des Schreibens sehr sinnvoll ist. In der obigen Liste fehlen Mobilgeräte ohne Touchscreen, z. B. einfache Mobiltelefone und einige E-Book-Reader. Die meisten dieser Geräte haben jedoch eine Tastaturnavigation oder eine Screenreader-Software installiert, die gut funktioniert, wenn Sie Ihre Website mit Blick auf die Barrierefreiheit erstellen.
Beispiele für formfaktorspezifische Web-Apps
Es gibt viele Beispiele für Web-Properties, die für verschiedene Formfaktoren völlig unterschiedliche Versionen bereitstellen. Das ist bei der Google Suche und bei Facebook der Fall. Dazu gehören sowohl die Leistung (Abrufen von Assets, Rendern von Seiten) als auch die allgemeine Nutzerfreundlichkeit.
Viele Entwickler passen ihre nativen Apps an eine bestimmte Geräteklasse an. Beispielsweise hat Flipboard für das iPad eine ganz andere Benutzeroberfläche als Flipboard auf dem iPhone. Die Tablet-Version ist für die Verwendung mit zwei Händen und das horizontale Umblättern optimiert, während die Smartphone-Version für die Bedienung mit einer Hand und das vertikale Umblättern vorgesehen ist. Viele andere iOS-Anwendungen bieten ebenfalls deutlich unterschiedliche Smartphone- und Tablet-Versionen, z. B. Things (Aufgabenliste) und Showyou (soziale Videos), die unten dargestellt sind:
Ansatz 1: Serverseitige Erkennung
Auf dem Server haben wir ein viel eingeschränkteres Verständnis des Geräts, mit dem wir es zu tun haben. Der User-Agent-String ist wahrscheinlich der nützlichste Hinweis, der verfügbar ist. Er wird bei jeder Anfrage über den User-Agent-Header angegeben. Daher funktioniert hier derselbe UA-Sniffing-Ansatz. Die Projekte DeviceAtlas und WURFL tun dies bereits und liefern viele zusätzliche Informationen zum Gerät.
Leider birgt jede dieser Optionen eigene Herausforderungen. WURFL ist sehr groß und enthält 20 MB XML-Code. Das kann bei jeder Anfrage zu einem erheblichen serverseitigen Aufwand führen. Es gibt Projekte, bei denen das XML aus Leistungsgründen aufgeteilt wird. DeviceAtlas ist nicht Open Source und erfordert eine kostenpflichtige Lizenz.
Es gibt auch einfachere, kostenlose Alternativen wie das Projekt Detect Mobile Browsers. Der Nachteil ist natürlich, dass die Geräteerkennung zwangsläufig weniger umfassend ist. Außerdem wird nur zwischen Mobilgeräten und anderen Geräten unterschieden. Tablets werden nur durch eine Ad-hoc-Anpassung unterstützt.
Ansatz 2: Clientseitige Erkennung
Mithilfe der Funktionserkennung können wir viel über den Browser und das Gerät des Nutzers erfahren. Wir müssen hauptsächlich feststellen, ob das Gerät über eine Touch-Funktion verfügt und ob es sich um einen großen oder kleinen Bildschirm handelt.
Wir müssen irgendwo eine Grenze ziehen, um zwischen kleinen und großen Touch-Geräten zu unterscheiden. Was ist mit Grenzfallgeräten wie dem 5‑Zoll-Galaxy Note? Die folgende Abbildung zeigt eine Reihe beliebter Android- und iOS-Geräte mit den entsprechenden Bildschirmauflösungen. Das Sternchen gibt an, dass das Gerät mit doppelter Dichte geliefert wird oder werden kann. Obwohl sich die Pixeldichte verdoppelt, werden in CSS weiterhin dieselben Größen angegeben.
Kurzer Exkurs zu Pixeln in CSS: CSS-Pixel im mobilen Web sind nicht dasselbe wie Bildschirm-Pixel. Mit iOS-Retina-Geräten wurde die Praxis eingeführt, die Pixeldichte zu verdoppeln (z. B. iPhone 3GS im Vergleich zu iPhone 4, iPad 2 im Vergleich zu iPad 3). Die Retina-UAs für Mobile Safari melden weiterhin dieselbe Gerätebreite, um das Web nicht zu beschädigen. Auf anderen Geräten (z. B. Android-Geräte mit Displays mit höherer Auflösung verwenden denselben Trick mit „device-width“.
Diese Entscheidung wird jedoch dadurch erschwert, dass sowohl das Hoch- als auch das Querformat berücksichtigt werden müssen. Wir möchten die Seite nicht neu laden oder zusätzliche Skripts laden, wenn wir das Gerät neu ausrichten. Möglicherweise möchten wir die Seite jedoch anders rendern.
Im folgenden Diagramm stellen die Quadrate die maximalen Abmessungen der einzelnen Geräte dar, die sich aus der Überlagerung der Hoch- und Querformatkonturen ergeben (und durch Vervollständigung des Quadrats):
Wenn wir den Schwellenwert auf 650px festlegen, klassifizieren wir iPhone und Galaxy Nexus als „smalltouch“ und iPad und Galaxy Tab als „tablet“. Das androgynische Galaxy Note wird in diesem Fall als „Smartphone“ klassifiziert und erhält das Smartphone-Layout.
Eine sinnvolle Strategie könnte so aussehen:
if (hasTouch) {
if (isSmall) {
device = PHONE;
} else {
device = TABLET;
}
} else {
device = DESKTOP;
}
Minimales Beispiel für die Funktionserkennung
Alternativ können Sie den Gerätetyp auch mithilfe des User-Agent-Sniffings erkennen. Sie erstellen eine Reihe von Heuristiken und gleichen sie mit den navigator.userAgent Ihrer Nutzer ab. Pseudocode sieht in etwa so aus:
var ua = navigator.userAgent;
for (var re in RULES) {
if (ua.match(re)) {
device = RULES[re];
return;
}
}
Hinweis zum clientseitigen Laden
Wenn Sie die UA-Erkennung auf Ihrem Server durchführen, können Sie entscheiden, welches CSS, JavaScript und DOM bei einer neuen Anfrage bereitgestellt werden soll. Wenn Sie die Erkennung jedoch clientseitig durchführen, ist die Situation komplexer. Sie haben mehrere Möglichkeiten:
- Weiterleitung zu einer gerätetypspezifischen URL, die die Version für diesen Gerätetyp enthält.
- Gerätetypspezifische Assets dynamisch laden
Der erste Ansatz ist unkompliziert und erfordert eine Umleitung wie window.location.href = '/tablet'. An den Standort werden jetzt jedoch Informationen zum Gerätetyp angehängt. Möglicherweise möchten Sie die History API verwenden, um die URL zu bereinigen. Leider ist bei diesem Ansatz eine Weiterleitung erforderlich, die insbesondere auf Mobilgeräten langsam sein kann.
Der zweite Ansatz ist wesentlich komplexer zu implementieren. Sie benötigen einen Mechanismus zum dynamischen Laden von CSS und JS. Je nach Browser können Sie möglicherweise nicht <meta viewport> anpassen. Da es keine Weiterleitung gibt, bleibt Ihnen nur das ursprüngliche HTML, das bereitgestellt wurde. Sie können das Element natürlich mit JavaScript bearbeiten, aber das kann je nach Anwendung langsam und/oder unübersichtlich sein.
Client oder Server auswählen
Hier sind die Vor- und Nachteile der einzelnen Ansätze:
Pro-Client:
- Zukunftssicherer, da auf Bildschirmgrößen/-funktionen und nicht auf UA basiert.
- Die Liste der User-Agents muss nicht ständig aktualisiert werden.
Pro-Server:
- Vollständige Kontrolle darüber, welche Version auf welchen Geräten bereitgestellt wird.
- Bessere Leistung: Clientweiterleitungen oder dynamisches Laden sind nicht erforderlich.
Ich persönlich ziehe es vor, mit device.js und der clientseitigen Erkennung zu beginnen. Wenn Sie im Laufe der Entwicklung Ihrer Anwendung feststellen, dass die clientseitige Weiterleitung einen erheblichen Leistungsnachteil darstellt, können Sie das device.js-Script einfach entfernen und die UA-Erkennung auf dem Server implementieren.
Einführung von device.js
Device.js ist ein guter Ausgangspunkt für die semantische, auf Media-Queries basierende Geräteerkennung, ohne dass eine spezielle serverseitige Konfiguration erforderlich ist. So sparen Sie Zeit und Aufwand für das Parsen von User-Agent-Strings.
Sie stellen oben in Ihrem <head> ein suchmaschinenfreundliches Markup (link rel=alternate) bereit, das angibt, welche Versionen Ihrer Website Sie zur Verfügung stellen möchten.
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
Als Nächstes können Sie entweder die serverseitige UA-Erkennung durchführen und die Versionsweiterleitung selbst verarbeiten oder das device.js-Script für die funktionsbasierte clientseitige Weiterleitung verwenden.
Weitere Informationen finden Sie auf der Projektseite für device.js sowie in einer Beispielanwendung, in der device.js für die clientseitige Weiterleitung verwendet wird.
Empfehlung: MVC mit formfaktorspezifischen Ansichten
Wahrscheinlich denken Sie jetzt, dass ich Ihnen rate, drei völlig separate Apps zu entwickeln, eine für jeden Gerätetyp. Nein! Die Codefreigabe ist der Schlüssel.
Hoffentlich haben Sie ein MVC-ähnliches Framework wie Backbone oder Ember verwendet. Wenn ja, sind Sie mit dem Prinzip der Trennung von Belangen vertraut, insbesondere damit, dass Ihre Benutzeroberfläche (Ansichtsebene) von Ihrer Logik (Modellebene) entkoppelt sein sollte. Wenn Sie noch nicht mit MVC vertraut sind, können Sie sich diese Ressourcen zu MVC und MVC in JavaScript ansehen.
Die geräteübergreifende Geschichte passt gut in Ihr vorhandenes MVC-Framework. Sie können Ihre Ansichten ganz einfach in separate Dateien verschieben und für jeden Gerätetyp eine benutzerdefinierte Ansicht erstellen. Anschließend können Sie denselben Code für alle Geräte bereitstellen, mit Ausnahme der Ansichtsebene.
Ihr Projekt könnte die folgende Struktur haben. Natürlich können Sie die Struktur auswählen, die für Ihre Anwendung am sinnvollsten ist:
models/ (gemeinsame Modelle) item.js item-collection.js
controllers/ (gemeinsame Controller) item-controller.js
versions/ (gerätespezifische Inhalte) tablet/ desktop/ phone/ (smartphonespezifischer Code) style.css index.html views/ item.js item-list.js
Mit dieser Struktur können Sie genau festlegen, welche Assets für die einzelnen Versionen geladen werden, da Sie für jedes Gerät benutzerdefiniertes HTML, CSS und JavaScript haben. Das ist sehr leistungsstark und kann dazu führen, dass Sie für das geräteübergreifende Web am effizientesten und leistungsstärksten entwickeln können, ohne auf Tricks wie adaptive Bilder zurückgreifen zu müssen.
Nachdem Sie Ihr bevorzugtes Build-Tool ausgeführt haben, werden alle Ihre JavaScript- und CSS-Dateien für ein schnelleres Laden in einzelnen Dateien zusammengefasst und minimiert. Das HTML für die Produktion sieht dann in etwa so aus (für Smartphones mit device.js):
<!doctype html>
<head>
<title>Mobile Web Rocks! (Phone Edition)</title>
<!-- Every version of your webapp should include a list of all
versions. -->
<link rel="alternate" href="http://foo.com" id="desktop"
media="only screen and (touch-enabled: 0)">
<link rel="alternate" href="http://m.foo.com" id="phone"
media="only screen and (max-device-width: 650px)">
<link rel="alternate" href="http://tablet.foo.com" id="tablet"
media="only screen and (min-device-width: 650px)">
<!-- Viewport is very important, since it affects results of media
query matching. -->
<meta name="viewport" content="width=device-width">
<!-- Include device.js in each version for redirection. -->
<script src="device.js"></script>
<link rel="style" href="phone.min.css">
</head>
<body>
<script src="phone.min.js"></script>
</body>
Die Media-Query (touch-enabled: 0) ist nicht standardisiert (nur in Firefox hinter dem Anbieterpräfix moz implementiert), wird aber von device.js korrekt verarbeitet (dank Modernizr.touch).
Versionsüberschreibung
Die Geräteerkennung kann manchmal fehlerhaft sein. In einigen Fällen bevorzugen Nutzer möglicherweise das Tablet-Layout auf ihrem Smartphone (vielleicht verwenden sie ein Galaxy Note). Daher ist es wichtig, Nutzern die Möglichkeit zu geben, manuell festzulegen, welche Version Ihrer Website sie verwenden möchten.
Normalerweise wird in der mobilen Version ein Link zur Desktopversion bereitgestellt. Das ist einfach genug zu implementieren, aber device.js unterstützt diese Funktion mit dem GET-Parameter device.
Abschließend
Zusammenfassend lässt sich sagen: Wenn Sie geräteübergreifende Single-Page-UIs erstellen, die nicht in das Konzept des responsiven Designs passen, gehen Sie so vor:
- Wählen Sie eine Reihe von Geräteklassen aus, die unterstützt werden sollen, und Kriterien, nach denen Geräte in Klassen eingeteilt werden.
- Erstellen Sie Ihre MVC-App mit einer klaren Trennung der Belange und trennen Sie die Ansichten vom restlichen Code.
- Verwenden Sie device.js für die clientseitige Erkennung der Geräteklasse.
- Wenn Sie bereit sind, verpacken Sie Ihr Skript und Ihre Stylesheets in jeweils eines pro Geräteklasse.
- Wenn die Leistung der clientseitigen Weiterleitung ein Problem darstellt, sollten Sie device.js nicht mehr verwenden und stattdessen auf die serverseitige UA-Erkennung umstellen.