Fragen zu HTML5 und Co beantwortet 19 - Flexbox-Umbrüche, magische Body-Backgrounds, ES6-Promises, Main-Element

Veröffentlicht am 10. März 2015

Lange keine Fragen mehr beantwortet! Das lag nicht an einem Mangel an Input, sondern vielmehr daran, dass ich vor lauter JavaScript-Schulungen kaum zum Schreiben (von Blogposts, nicht von Antworten auf E-Mails) gekommen bin. Mit dieser Sammlung aus Problemen rund um HTML, CSS und JS hat das zum Glück ein Ende. Wenn auch ihr eine Frage zu Frontend-Technologien habt, nur her damit: eine E-Mail oder ein Tweet genügen!

Flexbox-Umbrüche erzwingen

Kann man Flexboxen gezielt umbrechen lassen um zum Beispiel nach jedem dritten Element einen Umbruch zu erzwingen?

Kurze Antwort: in der Theorie sollte es ganz einfach gehen, in der Praxis funktioniert es nicht ganz so gut. Die Flexbox-Spezifikationen sehen Umbrüche mit den ganz normalen CSS-Umbruch-Eingenschaften vor:

A break is forced wherever the CSS2.1 page-break-before/page-break-after or the CSS3 break-before/break-after properties specify a fragmentation break.

Ein Umbruch nach jedem dritten Kindelement sollte demnach kein Problem sein:

.container > :nth-child(3n) {
  break-after: always;
  page-break-after: always;
}

Der Haken an der Sache ist, dass einerseits break-after in so gut wie keinem Browser funktioniert, andererseits page-break-after in Chrome und Internet Explorer keinen Einfluss auf Flexbox zu haben scheint. Nur im Firefox funktionieren die Umbrüche wie vorgeschrieben.

ES6-Promises inspizieren?

Die Promises von ECMAScript 6 bieten offenbar keine Möglichkeit, ihren Status auszulesen. Wie kann ich trotzdem herausfinden, ob ein Promise resolved oder rejected ist?

Fast alle denkbaren Features von Promises lassen sich mit Hilfe der guten alten then()-Methode umsetzen. Die einfachste Lösung für das Inspektions-Problem besteht darin, eine Wrapperfunktion für Promise() zu bauen. Diese gibt ein ganz normales Promise zurück, das aber durch die Wrapper-Funktion mit Eigenschaften wie isResolved und isRejected ausgestattet ist:

// http://jsbin.com/funepe/2/edit?js,console
function SuperPromise(executor){
  var promise = new Promise(executor);
  promise.isResolved = false;
  promise.isRejected = false;
  promise.then(function(){
    promise.isResolved = true;
  }, function(){
    promise.isRejected = true;
  });
  return promise;
}

Die Wrapper-Funktion sorgt mit ihren then()-Callbacks dafür, dass bei Statusänderungen die Eigenschaften isResolved und isRejected ein Update bekommen:

var myPromise = new SuperPromise(function(resolve){
  setTimeout(resolve, 1000);
});

console.log(myPromise.isResolved); // > false

myPromise.then(function(){
  console.log(myPromise.isResolved); // > true
});

setTimeout(function(){
  console.log(myPromise.isResolved); // > true
}, 1000);

Das ist die einfachste Lösung. Wer besonders fancy sein möchte, kann sich auch einen Promise-Proxy bauen (Firefox) oder, sobald Browser ES6-Klassen unterstützen, eine class SuperPromise extends Promise basteln, aber das Grundprinzip bleibt gleich.

Magische Body-Backgrounds?

Wie kann dieser Effekt sein? Ich gebe dem 0 Pixel hohen Body-Element einen Farbverlauf und die ganze Seite wird mit einem sich wiederholenden, 8 Pixel hohem Verlauf überzogen? Was geht da vor sich?

Bei diesem Effekt handelt es sich um eine Kombination aus verschiedenen CSS-Sonderregeln für ganz bestimmte Elemente. Wenn ein <html>-Element keinen eigenen CSS-Hintergrund hat, übernimmt es den Hintergrund vom <body>-Element (genau genommen die computed values). Das <body>-Element hat seinerseits einen Standard-Margin von 8 Pixeln, was (durch collapsing marging) auch dafür sorgt, dass das <html>-Element auch ohne jeden Inhalt 8 Pixel hoch ist.

Unser <html>-Element hat jetzt den Hintergrund vom <body>-Element sowie 8 Pixel Höhe durch den Margin des <body> erhalten; 100% Breite hat es als Block-Element sowieso. Der Farbverlauf wiederholt sich nun bis zum Ende der Seite, weil der Hintergrund des Wurzelelements einer Seite (im Falle von HTML <html>) als Hintergrund für die gesamte Renderingfläche verwendet wird. Dabei wird der Hintergrund jedoch immer noch so gerendert, als wäre er für für sein eigentliches Element (<html>) bestimmt und ggf. wiederholt, bis die ganze Renderingfläche bedeckt ist.

<main> oder nicht <main>, das ist hier die Frage

Vor dem Aufbau des HTML-Gerüsts meiner neuen Webseite habe ich mir den Quelltext vieler anderer Seiten angeschaut. Inzwischen wird kräftig Gebrauch von <header>, <section>, <artice>, <aside> und <footer> gemacht, aber kaum jemand nutzt <main>! Da wird fast überall immer noch ein <div>-Wrapper verwendet oder das <section>- oder <article>-Element per ID und WAI-ARIA-Role zum <main>-Element umgebaut. Kannst du mir mal erklären wieso man überwiegend so verfährt und nicht einfach <main> nutzt?

Ich nehme an, dass der Grund für den spärlichen <main>-Einsatz bei allen möglichen Webseiten (inklusive meiner eigenen Seite) der gleiche ist: das Element ist vergleichsweise neu! Während es <header>, <footer>, <section>, <artice>, <nav> und <aside> schon seit Anbeginn der Zeiten (und damit auch während des großen HTML5-Hypes) gab, ist <main> erst später dazugekommen und daher weniger verbreitet und weniger bekannt.

Das <main>-Element ist aber tatsächlich das semantisch korrekte Element für den Hauptinhalt, funktioniert in jedem Browser, ist seit HTML 5.0 offizieller Webstandard, hat eingebaute WAI-ARIA-Features und sorgt, verglichen mit einem umgebogenen <section>-Element für besser lesbaren Quelltext. Bei einem neuen Projekt würde ich es also auf jeden Fall einsetzen. Andererseits sind die wirlich messbaren Vorteile, die man durch den Einsatz von <main> erlangt, eher überschaubar, weswegen bestehende Seiten (wie auch meine eigene) es eher selten nachträglich einbauen. Es gilt die alte Informatiker-Faustregel: never change a running system.

Weitere Fragen?

Auch eure Fragen zu HTML5, JavaScript und anderen Webtechnologien beantworte ich gerne! Einfach eine E-Mail schreiben oder Twitter bemühen und ein bisschen Geduld mit der Antwort haben. Ansonsten kann man mich natürlich auch als Erklärbär zu sich kommen lassen.

Video: Talk über Web Components bei WWNRW

Veröffentlicht am 26. Februar 2015

Als ich zum Ende des letzten Jahres nicht so viel zu tun hatte, bin ich mit einem Talk über Web Components durch mehrere Webworker-Usergroups getingelt. Beim Treffen von Webworker NRW im November hat sich der Schepp die Mühe gemacht, den Vortrag zu filmen. Und nicht nur das: fast die gesamte letzte Woche hat Schepp seinen altehrwürdigen PC mit nichts als dem finalen Rendering des Videos blockiert. Alles nur, damit euch der Talk nicht entgeht!

Thema sind sowohl die Webstandards hinter Web Components als auch konkrete Anwendungsbeispiele für selbstdefinierte HTML-Elemente mit der Polymer-Library.

Kurz ausprobiert: Flow (statischer Typchecker für JavaScript)

Veröffentlicht am 20. Januar 2015

Ich probiere häufiger mal ein Frontend-Tool aus und kippe meine Eindrücke und Erkenntnisse auf Twitter ab. Das werde ich auch in Zukunft noch machen, aber ergänzend gibt ab jetzt (hoffentlich) auch jedes Mal einen Blogpost in der neuen Rubrik „kurz ausprobiert“.

Warum geht es? Flow ist ein Open-Source-Tool aus dem Hause Facebook. Es handelt sich um einen statischen Typchecker für JavaScript-Code, der Alarm schlägt, wenn man in einem JS-Programm versehentlich Datentypen durcheinanderbringt. Ein ganz simples Beispiel:

function f (a, b) {
  // b ist undefined
  // irgendwas geteilt durch undefined ist NaN
  // böse Fehlerquelle!
  return a / b;
}

f(42); // > NaN

Mit einem Typechecker wie Flow passiert diese Form des Fehlers nicht mehr, denn im Laufes des Build-Schrittes analysiert das Tool den Code und weist lautstark auf derartige Fehler(quellen) hin.

Was ist gut? Flow ist smart. Man kann seinen JS-Code mit allerlei Typannotationen „verschönern“, die Flow einliest und nutzt, um Problemquellen zu identifizieren:

function f (a:number, b:number) {
  return a / b;
}

f(42);
// > test.js:5:1,5: function call
// > Too few arguments (expected default/rest parameters in function)

f(42, undefined);
// > test.js:9:7,15: undefined
// > This type is incompatible with
// > test.js:1:25,30: number

Aber das Tool ist dank Typinferenz auch in der Lage, ohne Annotationen viele solcher Fehler zu finden:

function f (a, b) {
  return a / b;
}

f(42);
// > test.js:5:1,5: function call
// > Too few arguments (expected default/rest parameters in function)

f(42, undefined);
// > test.js:9:7,15: undefined
// > This type is incompatible with
// > test.js:1:25,30: number

Man kann also, wenn man möchte, Typannotationen nach und nach in seinen Code einfließen lassen. Flow kann auch mit Annotationen dazu bewegt werden, nur bestimmte Teile des Codes zu prüfen und andere links liegen zu lassen.

Was ist nicht so gut? Das Tool ist offensichtlich einerseits sehr neu und kann andererseits noch längst nicht alles, was in der modernen JavaScript-Welt möglich ist. Um wirklich zu funktionieren ohne false positives zu liefern, muss Flow alle JavaScript-Standardfunktionen- und Datentypen kennen, die es so gibt – und das ist noch nicht der Fall. Meine persönlichen Experimente mit Flow haben geendet, als ich es nicht hinbekommen habe, Flow die Existenz von Object.setPrototypeOf() (ES6) sowie document.registerElement() (Custom Elements) einzutrichtern, aber anscheinend fehlt es selbst an vergleichsweise etablierten Dingen wie TypedArrays. Außerdem war die Installation verglichen mit dem sonst üblichen npm install x etwas mühsam und die Dokumentation beantwortet noch nicht alle Fragen.

Wie fällt das Urteil aus? Flow ist offensichtlich noch ziemlich neu. Der generelle Ansatz ist extrem vielversprechend und die Basics funktionieren sehr gut. Es fehlt aber noch an Unterstützung für neuere und exotischere Features von JavaScript und DOM, es gibt richtig viele offene Bugs und es ist einfach noch viel zu tun. Wenn das Projekt etwas nachgereift ist, sollte man auf jeden Fall noch einen Blick darauf werfen.

Fragen zu HTML5 und Co beantwortet 18 - Auf- und Abrunden in CSS, Web Components vs. SEO, verschachtelte Web Worker, Sprungmarken

Veröffentlicht am 13. Januar 2015

Ein neues Jahr, ein paar neue Fragen zu CSS, HTML5 und diversen komischen Vorhängen unter der Haube der Browser-Engine. Ich tüftele auch bereits an der Beantwortung einiger weiterer CSS-Fragen, die teilweise ausgesprochen knifflig sind. Falls ihr mir noch mehr Arbeit aufbürden möchtet, seid ihr dazu herzlich eingeladen! Schreibt mir einfach eine E-Mail oder einen Tweet und ich grabe mich für euch durch Specs oder schaue in meine Glaskugel.

CSS: Auf- und Abrunden bei Maßangaben

Browser interpretieren die Angabe border-width: 0.5px unterschiedlich. Firefox, Safari und IE zeigen einen Rahmen an, Chrome nicht. Ist die Angabe 0.5px valides CSS? Welcher Browser interpretiert es richtig?

Die Maßangabe 0.5px ist gültig und alle Browser machen es richtig! Für border-width kann eine beliebige length angegeben werden und nirgends wird verboten, Kommazahlen und Pixel zu kombinieren. Allerdings sagen die Specs auch:

In cases where the used length cannot be supported, user agents must approximate it in the actual value.

Die Used Length bzw. Used Value ist der Wert, den der Browser unter Berücksichtigung der Kaskade und aller anderen Faktoren als theoretisch endgültigen Wert für eine CSS-Eigenschaft errechnet. Wirklich endgültig ist dieser Wert aber nur, wenn sich der Browser in der Lage sieht, ihn auch 1:1 wie gefordert darzustellen. Ist das nicht möglich (weil z.B. eine 0,5 Pixel dicke Border bestellt wurde), muss der Browser aus der Used Value eine Actual Value errechnen, mit der er arbeiten kann. Und wie genau dieses Errechnen passiert, ist nicht festgelegt! Diese Freiheit wird den Browsern gelassen, da ggf. nicht jeder Browser auf jeder Plattform die gleichen Dinge leisten kann. So rundet sich jeder Browser nach Gutdünken die Welt zurecht.

Ich persönlich kann ganz gut nachvollziehen, warum Chrome im Zweifelsfall lieber ab- als aufrundet. Beim Abrunden fehlt im schlimmsten Fall irgendwo ein Rahmen oder es entsteht eine 1 Pixel breite Lücke im Layout. Beim Aufrunden könnte es hässlichen Umbrüchen in Float-Layouts kommen, so dass der optische Schaden viel viel größer wird. Würde ich einen Browser programmieren, würde ich auch lieber abrunden. Aber auch aufrunden ist laut CSS-Spezifikationen erlaubt: die Browser müssen selbst entscheiden können, wie sie einen theoretischen Wert mit ihren jeweils gegebenen Möglichkeiten in die Praxis umsetzen.

Web Components und SEO

Wie sieht es aus mit Web Components und SEO? Wird das ein Problem? Werden die Suchmaschinen headless Browser brauchen um an idizierbaren Content zu kommen?

Ich würde nicht davon ausgehen, dass Web Components ein SEO-Problem werden. Zum Einen sind Web Components eher etwas für Webapps und weniger für frei durchsuchbare Seiten – in vielen Fällen stellt sich das Problem also gar nicht erst. Und selbst wenn: alle nennenswerten Suchmaschinen sind bereits heute in der Lage, JS und CSS zu verstehen – sonst könnten sie ja nicht die diversen Black-Hat-Techniken (weißer Text auf weißem Grund etc.) erkennen.

Zum Anderen sollte eine gut gebaute Komponente von Haus aus auch für den einfachsten Crawler indizierbar sein. Man muss stets bedenken: das Shadow DOM ist für Deko- und UI-Markup gedacht, nicht für Content. Der eigentliche Inhalt wird sich in den meisten Fällen im Markup zwischen den Tags neu erfundener Elemente wiederfinden:

<custom-element foo="bar">
  <h1>Überschrift!</h1>
  <p>Inhalt!</p>
</custom-element>

Das wäre selbst für eine Suchmaschine ohne Kenntnis von Web Components kein Problem. Natürlich kann man auch Komponenten bauen, die ein mögliches SEO-Problem sein könnten, z.B. indem man per Ajax in der Komponente Daten abfragt und sie ins Shadow DOM schreibt. Derartige Konstruktionen sind aber auch bereits heutzutage möglich und wer auf SEO achtet, der macht sowas einfach nicht. Zusammengefasst kann man sagen: es ändert sich für SEO wenig bis gar nichts durch die Einführung von Web Components.

Workaround für verschachtelte Web Worker

Seit einiger Zeit kann man in Chrome keine Web Worker mehr in Web Workern erzeugen. Was tun?

Man kann zwar keine Web Worker mehr in Web Workern erzeugen, aber man kann einen Message-Proxy auf einen Web Worker in einen anderen Worker hineinstecken! Cross Document Messages sind nicht nur ein Feature, das andere APIs wie Worker und Window implementieren (postMessage(), onmessage) sondern es gibt auch einen MessageChannel()-Constructor. Dieser produziert zwei verknüpfte Message Ports, von denen man einen mit einem Worker verbinden, einen anderen in einen Worker hineinstecken kann. Klingt kompliziert, ist aber eigentlich ganz einfach. Nehmen wir uns mal folgende Aufgabe zur Brust:

  1. Es gibt zwei Web Worker, A und B
  2. A erhält eine Nachricht aus der Webseite und gibt sie an B weiter
  3. B sendet seine Antwort an A
  4. A reicht die Antwort an die Webseite zurück

Der Code für den inneren Worker B ist ganz simpel:

this.onmessage = function(evt){
  if(evt.data === 23){
    this.postMessage(42);
  }
};

Worker A würde man nun normalerweise so programmieren, dass er Worker B erst erzeugt und dann direkt mit B spricht. Das fällt wegen des Chrome-Bugs aktuell flach. Also erzeugen wir A und B beide in der Webseite:

var outerWorker = new Worker('a.js');
var innerWorker = new Worker('b.js');

Wie bekommen wir nun B in A hinein gesteckt? Ohne weiteres geht das nicht, denn nur ganz bestimmte Objekte können passen durch den postMessage()-Kanal, und Worker-Objekte gehören nicht dazu. Allerdings könnte man einen Message Channel erzeugen. Dieser stellt zwei miteinander verbundene Message Ports zur Verfügung – und die sind via postMessage() transferierbar! Die folgende Funktion nimmt einen Worker, verbindet ihn mit einem von zwei Message Ports und gibt den anderen als Proxy auf diesen Worker zurück:

function createProxy(worker){
  var channel = new MessageChannel();
  var localPort = channel.port1;
  var remotePort = channel.port2;
  localPort.start();
  remotePort.start();
  localPort.onmessage = function(evt){
    worker.postMessage(evt.data);
  };
  worker.onmessage = function(evt){
    localPort.postMessage(evt.data);
  };
  return remotePort;
}

Den Proxy kann man nun ganz bequem in Worker A hinein transferieren:

var proxy = createProxy(innerWorker);

outerWorker.postMessage({
  port: proxy
}, [proxy]);

Der Code von Worker A muss nun diesen transferierten Port als inneren Web Worker verwenden statt ihn selbst zu erstellen, aber sonst bleibt alles beim alten; Worker A leitet einfach nur Nachrichten durch …

this.onmessage = function(evt){
  var that = this;
  var port = evt.data.port;
  port.onmessage = function(evt){
    that.postMessage(evt.data);
  };
  port.postMessage(23);
};

… dir wir auf dem outerWorker-Objekt in der Webseite empfangen können:

outerWorker.onmessage = function(evt){
  window.alert(evt.data);
};

Und schon läuft es!

Ist # die Sprungmarke für den Seitenanfang?

Ist es standardkonformes Verhalten, wenn href="#" Browser zum Seitenanfang springen lässt? Oder machen das die Browser einfach so von sich aus?

Dass die URL # Browser zum Seitenanfang führt, ist komplett korrekt und standardkonform. In früheren Zeiten waren solche Detailfragen der Browsermechanik nicht verbindlich festgelegt, doch dank HTML5 ist auch das Navigieren von Webseiten haarklein definiert.

Im Abschnitt 5.6 der HTML5-Spezifikationen geht es ausschließlich darum, wie man durch Webseiten navigieren kann und Unterpunkt 5.6.9 beschäftigt sich mit der Frage, wie Fragments (also jene URL-Bestandteile ganz am Ende mit dem # davor) anzusteuern sind. Dazu muss der Browser herausfinden, was der durch das Fragment (frag id) angesprochene Teil des Webseite (indicated part of the document) ist. Punkt 2 des entsprechenden Algorithmus besagt:

If [the] frag id is the empty string, then the indicated part of the document is the top of the document; stop the algorithm here.

Die Browser machen also in dieser Frage alles richtig.

Weitere Fragen?

Auch eure Fragen zu HTML5, JavaScript und anderen Webtechnologien beantworte ich gerne! Einfach eine E-Mail schreiben oder Twitter bemühen und ein bisschen Geduld mit der Antwort haben. Und falls es richtig viele Fragen gibt besuche ich euch gerne!