Eine Frage, die mich in letzter Zeit öfter mal via E-Mail und Blog-Kommentar erreicht hat, ist die nach dem Sinn der browserspezifischen CSS-Präfixe wie bei -moz-box-shadow und -webkit-border-radius. Die Antwort auf die Frage wieso bauen die das nicht gleich richtig ein? ist recht einfach: Die CSS-Eigenschaften sind in 90% aller Fälle Work-In-Progress-Varianten der richtigen CSS3-Eigenschaften. Der CSS2.1-Standard schreibt vor: wenn etwas zwar teilweise, aber nicht vollständig implementiert ist, soll man als verantwortungsvoller Browsermacher das Prefix davorkleben um anzuzeigen: das ist noch nicht die richtige Implementierung, sondern (erst mal) nur unsere Implementierung.
Besonders anschaulich wird das am Beispiel von border-top-left-radius. Bei Webkit-Browsern rundet man die obere linke Ecke einer Box wie folgt ab:
-webkit-border-top-left-radius:10px;
Bei Firefox geht das hingegen wie folgt:
-moz-border-radius-topleft:10px;
Da ist nicht nur das Präfix unterschiedlich, sondern gleich der ganze Name der CSS-Eigenschaft – da steht also noch einige Arbeit für Webkit- und Gecko-Entwickler an. Aber sobald die Implementierung standardkonform umgesetzt ist, fliegt das Präfix raus. So ist es jüngst beim Firefox 3.5 geschehen, wo man schon vor einiger Zeit opacity korrekt umgesetzt hatte und dafür nun das alte -moz-opacityrausgeworfen hat.
Die restlichen 10% der CSS-Präfixe entfallen auf echte proprietäre CSS-Eigenschaften wie das -ms-filter des IE8. Mehr Infos zu CSS-Präfixen bzw. vendor specific CSS extensions gibt es in diesem feinen Artikel nebst weiterführenden Links von Bobby van der Sluis (via Guymon).
Ein Design wie das meines Blogs sind praktisch kaum mit flexibler Breite umtzusetzen. Die diversen Schatteneffekte würden es nötig machen, an allen Kanten und Ecken von Boxen mehrere Einzelgrafiken einzusetzen – das geht, ist aber mühsam, unwartbar und schadet der Performance. Eine Lösung hierfür kündigt sich mit der CSS3-Eigenschaft box-shadow an.
Was macht box-shadow?
Mit box-shadow kann man beliebigen HTML-Elementen einen schönen Schatteneffekt verpassen. Die Spezifikationen sehen dabei auch mehrere Schatten auf einmal vor. Besonders praktisch ist, dass der Schatten bei Größenveränderungen der Box mitwächst oder mitschrumpft und sich bei abgerundeten Ecken ebenfalls abrundet.
Die ersten beiden Werte beschreiben den horizontalen und vertikalen Versatz des Schattens, für den man positive wie negative Werte eintragen kann. Der letzte Wert legt die Farbe des Schattens fest, wofür man auch eine der neuen Farbangaben von CSS3 einsetzen kann um die Transparenz fein zu steuern. Um eine weiche Schattenkante zu bekommen, fügt man einfach einen Wert für den Weichzeichner hinter den Angaben für den horizontalen und vertikalen Versatz ein:
Hier können auch negative Werte eingetragen werden, um einen kleineren Schatten zu erzeugen.
Es ist auch möglich, einem Element beliebig viele verschiedene Schatten auf einmal zuzuweisen. Mehrfache Schatten notiert man einfach, jeweils getrennt durch ein Komma, hintereinander. Dabei liegt der zuerst definierte Schatten über allen anderen und der zuletzt definierte ganz unten:
/* Vier Schatten auf einmal */
#test {
box-shadow: 8px 8px 8px red,
-8px 8px 8px green,
8px -8px 8px blue,
-8px -8px 8px yellow;
}
Und schließlich gibt es noch (zumindest im Firefox 3.5) die Möglichkeit, einem Element einen inneren Schatten zuzuweisen. Dazu muss man einfach lediglich das inset-Schlüsselwort irgendwo in der Schattendefinition unterbringen:
In Sachen Bowserunterstützung sieht es vergleichsweise finster aus. Zwar unterstützt neben den beiden Webkit-Browsern Safari und Chrome auch der Firefox 3.5 box-shadow (jeweils mit -webkit bzw -moz-Präfix), aber zwischen dem, was die drei Browser am Ende optisch abliefern, bestehen noch sichtbare Unterschiede.
Opera hat box-shadow noch nicht implementiert und ehe es einen Internet Explorer mit CSS3-Features gibt, friert die Hölle zu. Das ist schon recht schade, weil box-shadow meiner Ansicht eher weniger dafür taugt, wie Textschatten und runden Ecken als zusätzliche Deko zu fungieren, auf die man zur Not auch verzichten kann. Wenn wir umfassenden Support für dieses Feature hätten, könnten wir hingegen so manches Byte an Grafikdatei einsparen und flexible Layouts einfacher und/oder schöner gestalten. Aber noch ist es wohl nicht so weit.
In meinem großen HTML5-Artikel kam letztens ein neues Element etwas zu kurz, das eigentlich etwas besseres verdient hatte. Denn dieses Element, <canvas>, hat viel Potenzial. Von dezenten optischen Extras bis hin zu kompletten eigenen Programmoberflächen kann man damit einiges anstellen und alles was man dafür braucht, ist ein klein wenig Ahnung von HTML und Javascript.
Was ist <canvas> und wozu ist es gut?
Das englische Wort Canvas bedeutet auf deutsch Leinwand und ist ziemlich genau das: Eine Leinwand, auf die man mit Javascript dynamisch Bitmap-Grafiken malen kann. Erfunden wurde das Element vom Webkit-Team und später sind auch Mozilla und Opera auf den <canvas>-Zug aufgesprungen. Damit ist das das Element heutzutage in alle anständigen Browser implementiert (für den IE gibt es einen Trick – dazu später mehr) und Teil des werdenden HTML5-Standards.
Die Möglichkeiten von <canvas> sind relativ weit gefasst. Wann immer man man dynamisch Grafiken erzeugen möchte, kann man das mit <canvas> machen. Beispiele sind z.B. die bekannte Image-Replace-Technik Typeface.js, die experimentelle Online-IDE Bespin (quasi ein komplettes mit Canvas geschriebenes Programm) und das Javascript-Toolkit MochaUI. Das sind alles relativ mächtige Beispiele, aber man kann auch getrost kleinere Brötchen backen – sofern man nur ein wenig Ahnung von HTML und Javascript hat, kann man mit Canvas schon etwas anstellen. Und genau darum geht es im folgenden.
Wie bindet man das <canvas>-Element ein?
Sowohl ein öffnender als auch ein schließender Tag werden für <canvas> gebraucht – zwischen den Tags kann man Fallback-Content notieren, also Inhalt, der ausgegeben wird wenn das <canvas>-Element selbst nicht angezeigt werden kann. Breite und Höhe sollten als Attribute angegeben werden – wenn man die Maße per CSS festlegt, wird der <canvas>-Inhalt ausgehend von der Ausgangsgröße entsprechend verzerrt.
Eine ID zu vergeben ist meist auch sinnvoll – man will ja das Element irgendwann mit Javascript beackern können.
Wie arbeitet man mit <canvas>?
Ein <canvas>-Element stellt einen oder mehrere sogenannte rendering contexts zur Verfügung, quasi Ebenen auf denen man zeichnen kann. Der im Moment einzige Context ist der 2D-Context – an einem 3D-Context wird aber gearbeitet. Für den Moment vergessen wir also einfach all das und beschränken uns auf den 2D-Context. Um mit <canvas> erste Schritte zu machen, brauchen wir nur zwei Dinge: ein <canvas>-Element in einem HTML-Dokument und eine Javascript-Funktion, die auf dem <canvas>-Element herummalt. Das Canvas-Element könnte so aussehen:
<canvas id="testcanvas1" width="300" height="200">
Dein Browser kann diese Grafik nicht darstellen.
</canvas>
Es enthält Maße, Fallback-Content und eine ID zur Steuerung. Wenn wir jetzt dieses Rechteck mit einer Farbe füllen wollten, bräuchten wir nur 8 Zeilen Javascript:
function drawCanvas(){
var canvas = document.getElementById('testcanvas1');
if(canvas.getContext){
var context = canvas.getContext('2d');
context.fillStyle = "rgb(255, 0, 255)";
context.fillRect(0, 0, canvas.width, canvas.height);
}
}
Zur Erläuterung:
var canvas = document... holt das <canvas>-Element anhand seiner ID
var context = canvas.getContext('2d') holt den 2D-Context in die handliche context-Variable. Das brauchen wir, weil wir letzlich nicht auf dem <canvas>-Element selbst, sondern auf einem Context des Elements zeichnen.
context.fillStyle = "rgb(255, 0, 255)" setzt die Füllfarbe für alles, was wir im Context hiernach zeichnen werden auf ein schönes, kräftiges Pink
context.fillRect(0, 0, canvas.width, canvas.height) zeichnet ein gefülltes Rechteck, beginnend bei den Koordinaten 0,0 und endend bei der unteren rechten Ecke des <canvas>-Elements, definiert durch die gesamte Höhe und Breite
Jetzt braucht man nur noch die Funktion drawCanvas() irgendwie auszuführen (z.B. mittels <body onload="drawCanvas()">) und schon hat man ein fertiges, funktionierendes Canvas-Beispiel.
Was kann man genau alles mit <canvas> anstellen?
Natürlich kann man mit <canvas> einiges mehr anstellen als nur pinke Flächen malen. Die komplette API ist in den offiziellen HTML5-Spezifikationen sehr erschöpfend behandelt. Für eine etwas bekömmlichere Darstellung, die auf zwei A4-Seiten passt und obendrein noch bebildert ist, sei dieser Cheat Sheet aufs allerwärmste empfohlen.
Kurz gesagt kann man Pfade zeichnen, vorhandene Bilder manipulieren, Text scheiben und mehr. Die eingangs erwähnten Beispiele wie Bespin und MochaUI machen deutlich, dass man mit Canvas sehr viel anstellen kann. Man muss es eben nur wollen. Drei weitere (einfache) Beispiele gefällig?
Canvas-Beispiel 1: Ein Bild drehen
Man kann in ein <canvas>-Element Bilder oder auch den Inhalt anderer <canvas>-Elemente zeichnen. Das könnte zum Beispiel so aussehen. Im Prinzip ist das das gleiche Spiel wie beim Einführungs-Beispiel:
function drawCanvas(){
var canvas = document.getElementById('testcanvas1');
var img = new Image();
img.onload = function(){
if(canvas.getContext){
var context = canvas.getContext('2d');
context.translate(200, 0);
context.rotate(90 * Math.PI/180);
context.drawImage(img, 0, 0, 200, 200);
}
}
img.src = 'test.png';
}
var img = new Image() erstellt ein neues img-Element, das als Referenz für unser canvas-Element dient.
context.translate(200, 0) positioniert den Punkt, um den der Context rotieren wird
context.rotate(90 * Math.PI/180) macht eine 90°-Drehung
context.drawImage(img, 0, 0) zeichnet das Bild img ausgehend von den Koordinaten 0,0.
Weil in diesem Beispiel der gesamte Context und nicht das Bild rotiert wird, kann man die Funktion drawCanvas() immer wieder aufrufen um das Ganze in 90°-Schritten immer weiter zu drehen.
function drawCanvas(){
var canvas = document.getElementById('testcanvas1');
if(canvas.getContext){
var context = canvas.getContext('2d');
animate(context, 31, 69, 1, -1, 8, 25, 1, 1);
}
}
… und eine mit einem Timer versehene animate()-Funktion, die nichts weiter macht, als anhand eines Haufens von Koordinaten Frame für Frame alle neu zu zeichnen.
Einfach bei jedem Frame mit context.clearRect(0, 0, 200, 200) die Canvas putzen und die Quadrate an ihren neuen Koordinaten zeichnen.
Beispiel 3: Paint
Richtig Interessant wird <canvas> erst, wenn man den Benutzer richtig mitmachen lässt. So ist zum Beispiel ein (sehr sehr rudimentäres) Malprogramm recht einfach machbar. Man nehme bei Mausbewegung die Koordinaten …
var x, y;
canvas.onmousemove = function(e){
x = e.clientX-canvas.offsetLeft;
y = e.clientY-canvas.offsetTop;
kx.innerHTML = x;
ky.innerHTML = y;
paint();
}
… und male, falls die Maustaste gedrückt ist, einfach an der Mausposition eine Fläche:
var active = false;
canvas.onmousedown = function(){ active = true; }
canvas.onmouseup = function(){ active = false; }
function paint(){
if(active){
context.fillRect(x, y, 10, 10);
}
}
Ist wie gesagt sehr rudimentär, aber es ein Beispiel dafür, wie man <canvas> interaktiv ensetzen kann.
Und was machen wir mit dem Internet Explorer?
Dass keine einzige Version des Internet Explorer mit <canvas> etwas anfangen kann, ist nicht überraschend, wenn auch ärgerlich. Glücklicherweise gibt es ExplorerCanvas. Das ist ein Script, dass versucht, <canvas> mit diversen proprietären IE-Technologien wie z.B. VML nachzubilden.
Die Verwendung von ExplorerCanvas ist denkbar simpel: Einfach das Script einbinden und weitermachen wie bisher.
Einige <canvas>-Features wie radialGradient funktionieren nicht oder leicht anders als im Original, aber das ist eben der Internet Explorer. Was will man machen?
Es gibt viele Plugins, die eine Tweet-This-Funktion für Wordpress bereitstellen, also einen Link, der auf twitter.com verweist und automatisch einen Link zum gerade gelesenen Artikel in das Textfeld einfügt. Aber für so einen Kleinkram gleich ein eigenes Plugin installieren? Braucht man nicht, das kann man auch mit ein paar Zeilen in der functions.php eines Themes machen. Gesagt, getan und weil sich @datenkind eine Veröffentlichung gewünscht hat, hier ist das Ding.
Das Ganze gibt direkt einen fertigen Link zurück, den man nur noch mit echo o.Ä. ausgeben muss. Alle drei Funktionsparameter sind optional bzw. mit Standardwerten ausgestattet:
$message_template: Die Twitter-Nachricht mit den Platzhaltern %TITLE für den Artikeltitel und %URL für den Link. Beide werden automatisch eingefügt und gekürzt.
$link_title: Die Beschriftung des Links.
$link_length: Die maximale Länge des Titels, bevor er gekürzt wird.
Wer mit den Standards leben kann (Linktitel Artikel twittern und Twitter-Nachricht Lese gerade: %TITLE %URL) kann einfach irgendwo in seinem Post-Loop echo get_tweet_this_link(); einfügen.