Blog von Thomas Puppe, Web Developer.

Browser APIs und CSS Media Queries

In der Regel denkt man bei Media Queries an nicht viel mehr als min-width oder device-pixel-ratio und bei Browser APIs an navigator.userAgent. Moderne Browser können natürlich viel mehr.

Kürzlich sah ich in einer interessanten Präsentation über Progressive Web Apps dies:

@media (display-mode: standalone), (display-mode: fullscreen) {
    .backButton {
        display: block;
    }
}

Mit dieser Media Query stellt man fest, ob die Website (bzw Web App) das Browser-GUI (und damit den Back-Button des Browsers ) zur Verfügung hat, oder nicht. Und kann dann einen eigenen Button einblenden.

Das war für mich ein Anlass, mal zu schauen, welche Media/Feature Queries und Browser APIs heutzutage zur Verfügung stehen.

(1) Browser APIs

Eigentlich ganz simpel, kannte ich aber bis heute noch nicht. Diese Abfrage ist natürlich der Methode, ein Cookie zu setzen und dann auszulesen, zu bevorzugen. Sehr gute Browser-Unterstützung.

In diesem Browser:

Zeigt an, ob im Browser "Do not track" gesetzt wurde -- und auf welchen Wert. Ist in vielen Browsern implementiert, aber der Rückgabewert ist unterschiedlich und muss geparsed werden.

In diesem Browser:

Gibt die Zahl der verfügbaren Prozessoren zurück. Das ist ein grober Indikator dafür, ob der Browser auf einem ein starken neuen Gerät läuft, oder einer alten Möhre. (Vielleicht kann man auch auf die sinnvolle Anzahl an Service Workern oder Web Workern schließen? Gibt ein Browser-Tab, das in einem Prozess läuft, solche Möglichkeiten her?)

In diesem Browser:

Selbsterklärend. Neben der aktuellen Position kann man auch Positions (oder Genauigkeits-) Änderungen als Event-Handler empfangen. Was macht man mit der Position des Users? Man füttert sie in eine Routenplanung, oder nutzt sie, um Ort und Land des Besuchers herauszufinden.

navigator.geolocation.getCurrentPosition(function(position) {
  console.log(position.coords.latitude, position.coords.longitude);
});

In diesem Browser:

Geolocation hat eine sehr gute Browser-Unterstützung, erfordert aber die Berechtigung durch den User und ist im Chrome Browser nur unter SSL verfügbar.

In diesem Browser:

Selbsterklärend. Die Änderung des online-Status lässt sich über ein Event abfangen:

window.addEventListener('online', function(e) {
    console.log("You are online");
}, false);

window.addEventListener('offline', function(e) {
    console.log("You are offline");
}, false);

Mehr Details zur Internetverbindung des Users (cellular, wifi).

In diesem Browser:

navigator.connection ist nur im Firefox Mobile und auf Android verfügbar.

Der Batteriestand des Devices kann abgefragt werden! Traurige Berühmtheit erlangte das Feature, als Uber denjenigen Kunden, deren Smartphone nur noch wenig Saft hatte, die Preise erhöht hat -- weil sie schnell das erstbeste Fahrzeug buchen würden.

In diesem Browser: waiting...

Gute Menschen setzen das ein, um bei Telefonen mit schwachem Akku auf aufwändige Animationen und allen überflüssigen Quatsch zu verzichten, der die CPU belastet.

navigator.getBattery().then(function(battery) {
   console.log(battery.level*100 + '%');
   console.log(battery.chargingTime);
   console.log(battery.dischargingTime);
});

Für die Änderung der Werte kann man auch wieder Event Handler registrieren, damit die Website/App informiert wird.

Eine schöne Einführung findet man bei MDN. Das Feature wird unterstützt im Firefox (nur in "privilegiertem Code" -- diesen Term recherchiere ich jetzt nicht), Chrome und Opera.

Mit dieser Funktion kann man per JS die Sharing-Funktion auslösen, die sonst aus dem Menü des Browsers getriggert wird. Der Vorteil gegenüber individuellen Sharing-Buttons: die Leute bekommen genau die Dienste angeboten, die sie zur Verfügung haben -- und nicht die der Seitenbetreiber für wichtig hielt.

navigator.share({
    title: document.title,
    url: window.location.href
})

Alle Parameter müssen Strings sein. Ob diese aus JavaScript kommen oder hardcoded sind, ist egal. Test oder URL können weggelasssen werden, eines von beiden muss vorhanden sein.

Zurückgegeben wird eine Promise. Man kann den User also fürs Sharen belohnen oder Abbruchraten tracken.

Ein paar Einschränkungen hat die Technik: Sie ist nur auf HTTPS-Seiten verfügbar, und kann nur durch User-Interaktion getriggert werden (nicht etwa onLoad oder onScroll -- Sorry liebe "User Engager"). Navigator.share() ist derzeit nur im Chrome (ab Version 55) verfügbar. Wie bei allen aktuellen Features wird der Entwickler also progressively enhancen.

In diesem Browser:

Kann genutzt werden, um kleine Datenmengen asynchron an einen Server zu senden. Das Verfahren ist für Event Tracking und Monitoring gedacht. Vor allem das Rumeiern mit Requests bei window.unload soll erleichtert werden.

navigator.sendBeacon("/log", data);

Das data können Form Objekte, Arrays oder normale JS Objekte sein.

Der Browser-Support ist durchwachsen und das Verfahren ist als experimentell eingestuft. Auf GitHub liegt eine clevere kleine Testseite bereit.

Lässt das Gerät vibrieren, wenn das verfügbar ist. Als Parameter nimmt die Funktion einen Integer (einmalige Vibration für x Millisekunden) oder ein Array (Pattern von Vibration und Pause) entgegen. navigator.vibrate([100,200,300]) vibriert also 100 ms, pausiert 200 ms, vibriert 300 ms. Verfügbar in modernen mobilen Browsern.

In diesem Browser:

Die Funktion steht auch in nicht-vibrierfähigen Geräten zur Verfügung und man kann nicht prüfen, ob sie tatsächlich etwas tun wird. Zur Erkennung müsste man also über die Device-Detection gehen.

(2) Media Queries

Die am häufigsten eingesetzten Media Queries dürften (min/max-)(device-)width sein, die für responsive Layouts genutzt werden. Das device macht den wichtigen Unterschied zwischen der Größe des Gerätes und des eigentlichen Viewports (also Fensters oder Split Screens).

Besonders auf großen Bildschirmen, wo man im Split Screen arbeitet (was mittlerweile auch auf Tablets kein Problem mehr ist), kann das Fenster komplett anders als das Gerät sein.

Doch Media Queries können viel mehr:

(device-)aspect-ratio

... ist das Seitenverhältnis des geräts (bzw Viewports). Damit kann man im simpelsten Fall quer- von Hochformat unterscheiden, oder beispielsweise Panorama-Bildschirmen spezielel Stile oder Inhalte ausliefern.

@media screen and (device-aspect-ratio: 16/9), screen and (device-aspect-ratio: 16/10) { ... }

orientation

Eine noch simplere Variante der aspect-ratio. Nimmt die Werte landscape oder portrait an.

@media all and (orientation: portrait) { ... }

In diesem Browser: undefined portrait landscape

resolution

Erkennt die Pixeldichte auf einem Gerät, und wird vor Allem genutzt, um Retina-optimierte Bilder auszuliefern (min-resolution: 300dpi).

display-mode

Mit dieser Media Query stellt man fest, ob die Website (bzw Web App) das Browser-GUI (und damit den Back-Button des Browsers ) zur Verfügung hat. Mögliche Werte: fullscreen, standalone, minimal-ui und browser.

light-level

Beschreibt die Lichtverhältnisse in der Umgebung, und nimmt die Werte dim (gedämpft), normal und washed (sehr hell) an. Eigentlich praktisch für so etwas wie den Nacht-Lese-Modus. Andererseits sehe ich hier die Gefahr, dass man mit seinen Queries die Nutzereinstellungen oder automatische Helligkeitsanpassung des Smartphones überschreibt -- "das Gegenteil von gut ist gut gemeint". Unterstützt wird light-level nur im Edge und im Firefox für OS X.

supports

Erst diese Woche in einem Vortrag meines Kollegen Nico Brünjes gesehen. Außerhalb leider selten. Mit dieser Feature Query prüft man, ob der Browser bestimmte CSS-Eigenschaften unterstützt. Zum Beispiel @supports(blink) oder @supports (display: grid).

In den meisten Fällen lässt sich die Abfrage bzw. das progressive Enhancement direkt in die CSS-Regeln einbauen. Zum Beispiel bei Schriftgrößen via font-size:16px; font-size: 1rem; für den IE8. Der ignoriert die zweite Angabe, mit der er nicht klarkommt, und nutzt die erste. Moderne Browser überschreiben die erste mit der zweiten.

Die Query @supports sorgt aber für Klarheit, wenn man größere Blöcke umstylen will, sobald eine Technik verfügbar ist. Oder aber, um eine ganz andere CSS-Datei zu laden.

<link rel="stylesheet" media="all" href="basic.css" />
<link rel="stylesheet" media="screen and (min-width: 5in) and (display: flex)" href="shiny.css" />

Der IE bis inklusive Version 11 unterstützt @supports nicht.

Der Support von Features lässt sich übrigens auch via JavaScript über die CSS-API des Browsers abfragen:

var canuiseCSSGrid = CSS.supports("(display: grid)");

Exotische Queries

Der Vollständigkeit halber noch ein paar Media Features, die eher selten genutzt werden.

scan unterscheidet in der Art des Bildaufbaus am Bildschirm (interlace vs progressive), und ist relevant bei schnellen Bewegungen.

color und color-index geben an, wie viele Farben verfügbar sind und wie sie gespeichert werden, monochrome erkennt Geräte die nur mit Graustufen arbeiten (und mit wie vielen). grid spricht auf Monospace-Font Displays an, also alle die feste Plätze und Breiten für jedes Zeichen haben. Theoretisch ist das cool, um reduzierte Bilder (oder auf Kontrast optimiertes CSS) an Geräte wie den Kindle auszuliefern. Weil diese Mikrooptimierung aber kaum jemand betreibt, machen die Geräte selbst was Gutes aus dem CSS -- weshalb wiederum niemand diese Mikrooptimierung betreibt.

Im Firefox kann man Windows-Versionen und Betriebssystem-Stile erkennen und mit seienm Styling darauf reagieren. Feature-Queries wie -moz-os-version:windows-win10,-moz-mac-graphite-themeund-moz-windows-theme:aero` regeln das.

Media Queries Level 4

Noch im Draft-Status sind Media Queries Level 4. Sie versprechen Features wie hover (kann ich über Elemente hovern?), pointer (wie genau kann ich Elemente treffen -- Mauszeiger vs Wurstfinger), update (Schnelligkeit beim Bildaufbau, quasi frames-per-second) und mehr.

device-width, device-height, und damit auch device-aspect-ratio` sind deprecated.

(3) Was mir fehlt

<script media="screen and not (doNotTrack)" src="https://google.com/analytics.js" defer integrity="abc">
<script media="screen and (bandwidth:high)" src="/immersiveEffects.js" defer integrity="xyz">