Schönes neues CSS: border-image

Veröffentlicht am 27. September 2010

Auf vielfachen Wunsch hin wird die Serie schönes neues CSS wird weiter fortgesetzt. Heute auf dem Programm: border-image. Hierbei handelt es sich wieder um ein CSS3-Feature, das keine neuen Effekte o.Ä. ermöglicht, aber uns einiges anderes wesentlich einfacher (d.h. mit weniger HTML-Overhead und besserer Performance) umsetzen lässt. Das Ganze ist ein wenig komplexer als manch andere CSS3-Neuheit, aber für einen kurzen Überblick reicht ein Blogpost allemal.

Was macht border-image?

Der Name sagt es eigentlich schon: border-image erlaubt es uns, unkompliziert Grafiken für Rahmen einzusetzen. Was das praktisch bewirkt, lässt sich am besten an einem Beispiel erklären. Man stelle sich vor, wir wollten ein Interface mit HTML und CSS bauen, dessen Elemente einen solchen Rahmen tragen sollen:

Ein Interface

Oben gibt es eine Titelleiste, rundherum ein Rahmen und der weiße Bereich in der Mitte ist für den Inhalt vorgesehen. Die Maße des Elements sollen dabei variabel sein. Mit herkömmlichem CSS ist hier nichts wirklich sinnvolles zu machen – Man müsste diese Grafik zerschneiden, viele  <div>-Elemente ineinander verschachteln und diesen die verschiedenen Grafik-Schnipsel als Hintergrundbilder verpassen. Das alles wäre nicht nur umständlich, sondern aufgrund der vielen Grafikschnipsel schlecht für die Performance. Abhilfe verspricht die neue CSS3-Eigenschaft border-image.

Wie funktioniert border-image?

Man kann sich border-image in etwa wie CSS-Sprites mit eingebautem Autopilot vorstellen. Für eine einzige Grafik, die den gesamten Rahmen beinhaltet lässt sich angeben, welche Ausschnitte davon Ecken und Rahmen sind. Außerdem es lässt sich festlegen, wie die Rahmen konstruiert werden sollen, also ob ein Grafik-Ausschnitt gestreckt oder wiederholt werden soll. Für unser Interface sähe die gewünschte Segmentierung wie folgt aus:

Interface mit Segmenten

Die blau markierten Segmente sollen gekachelt werden, die Grünen können ihre Fläche durch Strecken füllen und die Eck-Segmente sollen einfach in den Ecken stehen. Klingt kompliziert, ist aber mit border-image eine Fingerübung:

#foo {
    border-width: 64px;
    border-image: url(interface.png) 64 64 64 64 stretch repeat;
}

Funktioniert! Zur Erläuterung:

  • border-width:64px; sorgt für die gewünschte Rahmenbreite
  • url(interface.png) in border-image gibt das zu verwendende Bild an
  • 64 64 64 64 definiert die Maße der Ausschnitte (Details siehe weiter unten)
  • stretch repeat legt fest, dass die horizontalen Rahmen aus gestreckten, die vertikalen Rahmen aus gekachelten Grafiken bestehen sollen

Die Ausschnitte können entweder als ganze Zahlen oder Prozente angegeben werden, wobei die Zahlen Pixel in der Grafik repräsentieren (die Angabe der Einheit px ist nicht nötig). Die einzelnen Werte werden als Abstände von Rändern der Grafik interpretiert und wie auch bei margin und padding kann man, wenn mehrere Werte gleich sind, sie von rechts nach links weglassen, so dass Folgendes herauskommt:

#foo {
    border-width: 64px;
    border-image: url(interface.png) 64 stretch repeat;
}

Die Angaben für die Ausschnitte werden wie üblich im Uhrzeigersinn angegeben. So würde die Angabe 25% 30% 12% 20% zur folgenden Segmentierung einer Grafik führen (s. border-image-slice):

border-image-slice-Werte werden im Uhrzeigersinn interpretiert

Dabei ist border-image mehr als nur die CSS-Eigenschaft border-image. Die kompletten Spezifikationen sind recht umfangreich, wobei border-image als selbst nur die Kurzversion für eine Kombination aus border-image-source, border-image-slice, border-image-width, border-image-outset und border-image-repeat. Die vielen nützliche Kleinigkeiten wie z.B. round als Streck/Wiederholmodus für Grafiken (Demo) würden den Umfang dieses Artikels sprengen und wichtiger ist ohnehin die Frage, was man denn mit all dem heute schon anfangen. Wie so oft gibt es hier Licht und insbesondere in der Nähe von Redmond auch eine Portion Schatten.

Welche Browser verstehen border-image?

Mit Opera 10.5+, Firefox 3.5+ und der Webkit-Brigade (ab Engine-Version 522) verstehen die meisten Browser, von denen man es auch erwarten würde, border-image – die beiden letztgenannten noch mit Vendor-Prefix. Problematisch ist nur, dass CSS3, da ja noch nicht fertig, ständig weiterentwickelt wird und jeder Browser unterstützt eine andere Revision von border-image. Um ein und denselben Effekt zu erreichen, muss man also gegebenenfalls mehr machen als nur die Vendor-Prefixes austauschen, in den meisten Fällen reicht jedoch genau das:

#foo {
    width: 400px;
    border-width: 64px; /* Diese Angabe könnte der Firefox auch in seiner -moz-border-image-Eigenschaft unterbringen */
    border-image: url(interface.png) 64 64 64 64 stretch repeat; /* Aktueller Stand der Spezifikationen (von Opera unterstützt) */
    -moz-border-image: url(interface.png) 64 64 64 64 stretch repeat; /* Firefox/Gecko-Browser */
    -webkit-border-image: url(interface.png) 64 64 64 64 stretch repeat; /* Webkit-Browser */
}

Dieser Code führt zwar in den unterstützenden Browsern zum gleichen Ergebnis, aber da den Implementierungen jeweils verschiedene Revisionen von border-image zugrunde liegen, unterstützen nicht alle Browser den gleichen Funktionsumfang. Es gibt große Überschneidungen, aber nicht alles, was in den Spezifikationen steht funktioniert überall (und nicht alles was funktioniert, steht heute noch in den Spezifikationen). Schlimmer noch: der IE9 wird border-image überhaupt nicht unterstützen.

Das Fazit ist also: Je nachdem was man erreichen wollte, könnte man mit border-image schon einiges anstellen, wenn da nur der Internet Explorer nicht wäre. Vernünftige Browser haben bereits brauchbare Implementierungen an Bord, wobei man aber genau nachlesen sollte, welche Browserversion welche Revision der Spezifikationen inwiefern unterstützt.

Wie Otto Normalwebworker HTML5 verbessern kann

Veröffentlicht am 7. September 2010

Ein Strichmännchen

Wann immer ich Klagen höre, eine HTML5-API sei unzureichend in Browsern implementiert oder wäre einfach nur schlimm, denke ich mir: ändere es doch! Man muss kein Browserhersteller sein um HTML5 zu verbessern und man muss sich auch nicht auf Mailinglists von den hohen Herren abwatschen lassen – ein paar Zeilen JavaScript zu schreiben reicht. So ist etwa das HTML5-Element Canvas ein recht unhandliches Teil. Dank seiner etwas web-fremden Low-Level-Konzeption kann man mit einem normalen 2D-Context eigentlich so gut wie gar nichts anfangen, da die API viel zu rudimentär und benutzerunfreundlich ist. Um etwa das oben rechts abgebildete Strichmännchen darzustellen, müsste man das folgende Monstrum konstruieren:

var context = canvas.getContext('2d');
// Pfad beginnen
context.beginPath();
// Körper
context.moveTo(240, 100);
context.lineTo(240, 200);
// Beine
context.lineTo(190, 250);
context.moveTo(240, 200);
context.lineTo(290, 250);
// Arme
context.moveTo(240, 150);
context.lineTo(190, 150);
context.moveTo(240, 150);
context.lineTo(290, 150);
// Linie ziehen
context.lineWidth = 4;
context.strokeStyle = '#CC0000';
context.stroke();
context.closePath();
// Kopf
context.beginPath();
context.arc(240, 80, 35, 0, 6.28318531, false);
// Kopf zeichnen
context.fillStyle = context.strokeStyle;
context.fill();
context.stroke();
context.closePath();

Das ist dann doch recht mühsam. Aber das Canvas-Element ist auch gar nicht dafür gemacht, so direkt eingesetzt zu werden, es ist vielmehr eine Steilvorlage für JavaScript-Bibliotheken. Diese können dann spezialisiertere und/oder etwas leichter zu nutzende APIs umsetzen. So wäre der obrige Code-Brocken doch gleich schon viel angenehmer zu scheiben und zu lesen, wenn die Methoden jQuery-artig verkettet wären:

var context = CanvasQuery('test');
// Körper
context.moveTo(240, 100).lineTo(240, 200);
// Beine
context.lineTo(190, 250).moveTo(240, 200).lineTo(290, 250);
// Arme
context.moveTo(240, 150).lineTo(190, 150).moveTo(240, 150).lineTo(290, 150);
// Linien ziehen
context.set({'lineWidth': 4, 'strokeStyle': '#CC0000'}).stroke().closePath();
// Kopf
context.beginPath().arc(240, 80, 35, context.deg2rad(0), context.deg2rad(360), false);
// Linien ziehen
context.set('fillStyle', context.get('strokeStyle')).fill().stroke().closePath();

Die Canvas-API entsprechend umzubauen ist keine Herkulesaufgabe, nur knappe 60 Zeilen Code sind dafür nötig. Sowas kann jeder halbwegs fähige JavaScript-Nerd schreiben und es könnten auf diese Weise viele nicht nativ in Browsern implementierte HTML5-APIs nachgebaut oder ausgebessert werden, wie etwa die gruselige Drag-&-Drop-API oder die in den meisten Browsern nicht vorhandene API für data-*-Attribute. Ein perfektes Beispiel hierfür ist jStorage, eine kleine JS-Bibliothek, die dafür sorgt, dass eine Website clientseitig Daten speichern kann und sich um die Macken der diversen Browser kümmert.

Von genau dieser Sorte Script braucht die Welt mehr. Die Schlacht um ein besseres HTML5 muss nicht nur in den Arbeitsgruppen von W3C und WHATWG geschlagen werden sondern auch draußen im Web, von uns Webworkern. Wenn den großen Entscheidern mal eine Schnittstelle missrät oder wenn einfach mal etwas nur unzureichend implementiert ist, haben wir selbst es in der Hand, mit etwas JavaScript für eine De-facto-Reparatur zu sorgen. Also frisch ans Werk!

Fun Facts zu Doctypes

Veröffentlicht am 17. August 2010

Bis letzte Woche konnte ich nur erahnen, wie es ist, Wissenschafts- oder Politblogger zu sein. Diese bedauernswerten Zeitgenossen werden ja auf Schritt und Tritt von abstrusen Gestalten mit originellen Theorien über das Wesen der Welt verfolgt – Esotriker, Kryptofaschisten, Astrologen, 9/11-Besserwisser. Dann aber kam ein HTML5-Verschwörungstheoretiker auf mich zu, der behauptete beweisen zu können, dass der HTML5-Doctype <!DOCTYPE html>) mindestens so schlimm ist wie Handystrahlen und die jüdische Weltverschwörung, denn der HTML5-Doctype löse ja den Quirks Mode aus! Und wenn so ein dahergelaufener Eumel wie John Resig das Gegenteil behauptet, liegt er eben falsch.

Das Problem ist, dass unser HTML5-Verschwörungstheoretiker nicht nur unrecht hat, sondern in etwa so weit daneben liegt, wie ein 9/11-Thruther, der behauptet, die US-Regierung habe am 11. September 2001 nicht das World Trade Center, sondern den Eiffelturm gesprengt. Denn tatsächlich sind Browsern Art und Inhalt eines Doctypes komplett egal. Sofern am Start einer HTML-Seite irgend etwas steht, das grob nach Doctype aussieht, wird jeder Browser die Seite im Standards Mode rendern. Das Folgende ist formal ungültiger, aber wunderbar funktionierender Doctype (hier in Aktion):

<!DOCTYPE html public "Browsern ist völlig egal was im Doctype steht."
	"Solange da irgendwas steht, was grob nach Doctype-Syntax aussieht, wird Standards Mode verwendet.">

Warum aber ist dem Browser der Doctype so egal? Sehen wir uns einmal einen richtigen Doctype an:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Dieser Doctype besteht aus dem Syntax-Pflichtprogramm (<!DOCTYPE html ...), der Deklaration der hier verwendeten HTML-Variante (XHTML 1.0 Transitional) und einer URL. Diese URL enthält alle Informationen, die der Browser zum Verarbeiten der genannten HTML-Variante braucht. Theoretisch wäre das sehr wichtig, denn threoretisch ist HTML eine SGML-Anwendung. SGML ist so etwas wie der Urahn von XML und HTML stünde demnach in einer solchen Beziehung zu SGML, wie XHTML in Beziehung zu XML steht – das Letztere dient jeweils dazu, Ersteres umzusetzen. In der Theorie müsste man dem Browser als wirklich mitteilen, wo genau er nachlesen kann, wie er die Tags in einer HTML-Seite mit seinem SGML-Parser zu verarbeiten hat. Dieser Informationen findet der Browser unter der im Doctype angegeben URL; die .dtd-Datei enthält eine Liste aller HTML-Tags für den jeweiligen Doctype.

Die Praxis sieht allerdings anders aus. Kein Browser hat je so gearbeitet. Kein Browser hatte je einen SGML-Parser. Kein Browser hat je Doctypes verarbeitet und die .dtd-Dateien heruntergeladen. Es herrschte schließlich in der Anfangszeit des Webs Browserkrieg und man hatte dringenderes zu tun, als korrekte Parser zu implementieren –mit Standards nahm man es schon damals nicht so genau. So entwickelte sich HTML statt zu einer SGML-Anwendung zu einer ganz eigenen Auszeichnungssprache, die eigentlich gar keine Informationen aus Doctypes mehr brauchte. Alle Regeln zu HTML waren und sind auch noch heute fest in die Browser eingebaut.

Und so kommt es, dass Browsern die Inhalte von Doctypes völlig egal sind – Hauptsache es ist überhaupt einer vorhanden. Und folgerichtig funktioniert natürlich der HTML5-Doctype genau so gut wie alle anderen.

MODx Revolution: Mehrere FormIt-Instanzen auf einer Seite

Veröffentlicht am 12. August 2010

Formulare erstellt man in MODx Revolution (bekanntermaßen das beste CMS der Welt) mit einem neuen Snippet namens FormIt. FormIt ist der Revo-Nachfolger von eForms und ein gutes Stück weniger krampfig als eForms, hat es doch den Vorteil, Formulare und ihr Zubehör in wesentlich weniger großem Umfang über unzählige Chunks zu verteilen. Der größte Haken an FormIt ist meiner Meinung nach die noch etwas löchrige Dokumentation, die sich zum Beispiel darüber ausschweigt, wie man mehr als eine FormIt-Instanz auf einer Seite unterbringt, ohne dass sich diese in die Quere kommen.

Früher bei eForms behob man dieses Problem, indem man dem Snippet via Parameter jene Formular-ID mitteilte, um die das Snippet sich zu kümmern hatte. Ganz ähnlich geht es auch bei FormIt, nur auf etwas „formularigere“ Art und Weise: man teilt dem Snippet im Parameter submitVar den Namen eines Formularfelds mit, das als Identifikator fungiert – wohl in aller Regel ein Input vom Typ hidden, das speziell für diesen Zweck eingebaut wurde. Das könnte in Aktion dann so aussehen:



<form action="weblog/" method="post">
<input type="hidden" name="meinformular" value="1">

...

</form>

Da in diesem Fall $_POST["meinformular"] nur 1 ist, wenn auch wirklich das fragliche Formular abgesendet wurde, weiß die fragliche FormIt-Instanz wann sie in Aktion zu treten hat und wann sie Post-Daten ignorieren kann – denn die Formulare anderer FormIt-Instanzen übertragen eben keine meinformular-Variable.

Die Existanz von submitVar habe ich das über den Bugtracker des Projekts bei Github in Erfahrung bringen können – denn dokumentiert ist das Ganze wie gesagt nicht, Google liefert nichts Verwertbares und den Code von auch nur ein klein wenig komplexeren Revolution-Komponenten kann man ebenfalls nicht immer locker durchblicken. Schon doof dass es solche Doku-Löcher gibt, denn was nützt einem beste CMS der Welt, wenn man nicht auch mühelos nachlesen kann, wie es zum funktionieren zu bewegen ist?