Typografische Effekte in Canvas

Michael Deal
Michael Deal

Hintergrundinformationen zu meiner Person

<canvas> wurde mir 2006 bekannt, als Firefox 2.0 veröffentlicht wurde. Ein Artikel auf Ajaxian, in dem die Transformationsmatrix beschrieben wird, inspirierte mich dazu, meine erste <canvas>-Web-App zu erstellen: Color Sphere (2007). Dadurch bin ich in die Welt der Farben und grafischen Primitiven eingetaucht. Inspiriert bin ich bei der Entwicklung von Sketchpad (2007–2008), um im Browser eine App zu entwickeln, die "besser als Paint" funktioniert. Diese Experimente führten schließlich zur Gründung des Start-ups Mugtug mit meinem langjährigen Freund Charles Pritchard. Wir entwickeln Darkroom für HTML5<canvas>. Darkroom ist eine App zum Teilen von Fotos, die pixelbasierte Filter mit vektorbasierter Typografie und Zeichnung kombiniert.

Einführung

Canvas-Bannergrafik

<canvas> bietet Javascript-Programmierern die volle Kontrolle über die Farben, Vektoren und Pixel auf ihren Bildschirmen – also die visuelle Darstellung des Monitors.

Die folgenden Beispiele beziehen sich auf einen Bereich in <canvas>, der bisher nicht viel Aufmerksamkeit erhalten hat: das Erstellen von Texteffekten. Die Vielfalt der Texteffekte, die in <canvas> erstellt werden können, ist so groß, wie Sie sich vorstellen können. Diese Demos decken nur einen Teil dessen ab, was möglich ist. In diesem Artikel geht es zwar um „Text“, die Methoden können aber auf beliebige Vektorobjekte angewendet werden, um spannende visuelle Elemente in Spielen und anderen Anwendungen zu erstellen:

Textschatten in <canvas>
CSS-ähnliche Texteffekte in <canvas>, Erstellen von Schnittmasken, Messwerte in <canvas> finden und die Eigenschaft „Schatten“ verwenden.
Neon-Regenbogen, Zebra-Reflexion – Effekte kombinieren.
Photoshop-ähnliche Texteffekte in <canvas>-Beispielen für die Verwendung von globalCompositeOperation, createLinearGradient und createPattern.
Innen- und Außenschatten in <canvas>
Er zeigt ein wenig bekanntes Feature: Mit einer Drehung im oder gegen den Uhrzeigersinn wird der umgekehrte Schatten (innerer Schatten) erstellt.
Abstände – generative Wirkung.
Generativer Texteffekt in <canvas> mit hsl()-Farbwechsel und window.requestAnimationFrame, um ein Bewegungsgefühl zu erzeugen.

Textschatten in Canvas

Eine meiner Lieblingserweiterungen der CSS3-Spezifikationen (neben „border-radius“, „web-gradients“ und anderen) ist die Möglichkeit, Schatten zu erstellen. Es ist wichtig, die Unterschiede zwischen CSS- und <canvas>-Schatten zu kennen:

CSS verwendet zwei Methoden: box-shadow für Feldelemente wie div, span usw. und text-shadow für Textinhalte.

<canvas> hat einen Schattentyp. Dieser wird für alle Vektorobjekte verwendet: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText usw. Wenn Sie in <canvas> einen Schatten erstellen möchten, verwenden Sie die folgenden vier Properties:

ctx.shadowColor = "red" // String
Farbe des Schattens. RGB, RGBA, HSL, HEX und andere Eingaben sind zulässig.
ctx.shadowOffsetX = 0; // integer
Horizontaler Abstand des Schattens zum Text.
ctx.shadowOffsetY = 0; // Ganzzahl
Vertikaler Abstand des Schattens zum Text.
ctx.shadowBlur = 10; // integer
Weichzeichnereffekt für den Schatten. Je höher der Wert, desto stärker die Unschärfe.

Sehen wir uns zuerst an, wie <canvas> CSS-Effekte emulieren kann. Die Suche bei Google Bilder nach „css text-shadow“ führte zu einigen großartigen Demos, die wir emulieren konnten: Line25, Stereoscopic und Shadow 3D.

3D-Grafik für Preisvergleichsportale

Der stereoskopische 3D-Effekt (weitere Informationen finden Sie unter Anaglyphbild) ist ein Beispiel für eine einfache Codezeile, die sehr gut eingesetzt werden kann. Mit der folgenden CSS-Zeile können wir die Illusion von Tiefe erzeugen, wenn die Seite mit 3D-Rot/Cyan-Brillen betrachtet wird (die Art, die man in 3D-Filmen bekommt):

text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;

Bei der Umwandlung dieses Strings in <canvas> sind zwei Dinge zu beachten:

  1. Da es keine Schattenunschärfe (dritter Wert) gibt, gibt es keinen Grund, den Schatten tatsächlich auszuführen, da mit „fillText“ dieselben Ergebnisse erzielt werden:
var text = "Hello world!"
ctx.fillStyle = "#000"
ctx.fillText(text, -7, 0);
ctx.fillStyle = "red"
ctx.fillText(text, 0, 0);
ctx.fillStyle = "cyan"
ctx.fillText(text, 7, 0);</pre>
  1. EM-Dateien werden in <canvas> nicht unterstützt und müssen daher in PX-Dateien konvertiert werden. Das Umwandlungsverhältnis zwischen PT, PC, EM, EX, PX usw. lässt sich ermitteln, indem im DOM ein Element mit denselben Schrifteigenschaften erstellt und die Breite auf das zu messende Format festgelegt wird. Wenn wir beispielsweise die Umwandlung von EM in PX erfassen möchten, messen wir das DOM-Element mit „height: 1em“. Die resultierende offsetHeight gibt an, wie viele Pixel in jedem EM enthalten sind.
var font = "20px sans-serif"
var d = document.createElement("span");
d.style.cssText = "font: " + font + " height: 1em; display: block"
// the value to multiply PX 's by to convert to EM 's
var EM2PX = 1 / d.offsetHeight;</pre>

Alpha-Multiplikation verhindern

In einem komplexeren Beispiel wie dem Neoneffekt auf Line25 muss die Eigenschaft „shadowBlur“ verwendet werden, um den Effekt richtig zu emulieren. Da der Neoneffekt auf mehreren Schatten basiert, stoßen wir auf ein Problem: In <canvas> kann jedes Vektorobjekt nur einen Schatten haben. Wenn Sie also mehrere Schatten zeichnen möchten, müssen Sie mehrere Versionen des Textes übereinander zeichnen. Dies führt zur Alpha-Multiplikation und schließlich zu zerklüfteten Kanten.

Neongrafik

Ich habe versucht, ctx.fillStyle = "rgba(0,0,0,0)" oder "transparent" auszuführen, um den Text auszublenden und gleichzeitig den Schatten anzuzeigen. Dieser Versuch war jedoch erfolglos. Da der Schatten eine Multiplikation des fillStyle-Alpha ist, kann er niemals undurchsichtiger sein als das fillStyle.

Glücklicherweise gibt es eine Möglichkeit, das zu umgehen. Wir können den Schatten versetzt vom Text zeichnen, sodass sie sich nicht überschneiden, und den Text so an den Bildschirmrand verschieben:

var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);

Ausschneiden um einen Textblock

Um das Ganze etwas zu optimieren, können wir verhindern, dass der fillText überhaupt gezeichnet wird, während der Schatten gezeichnet werden darf. Dazu fügen wir einen Clipping-Pfad hinzu. Um einen Clippabzug um den Text herum zu erstellen, müssen wir die Höhe des Texts (die sogenannte „Em-Höhe“, historisch die Höhe des Buchstabens „M“ auf einer Druckpresse) und die Breite des Texts kennen. Wir können die Breite mit ctx.measureText().width abrufen, ctx.measureText().height gibt es jedoch nicht.

Glücklicherweise lässt sich die Höhe des Textes mithilfe von CSS-Hackardry (weitere Informationen zur Korrektur älterer Implementierungen von <canvas> mithilfe von CSS-Messungen) ermitteln, indem wir die offsetHeight einer <span> mit denselben Schriftarteigenschaften messen:

var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;

Anschließend können wir ein Rechteck als Zuschneideweg erstellen, den „Schatten“ umschließen und die Dummy-Form entfernen.

ctx.rect(0, 0, width, emHeight);
ctx.clip();

Wir bringen alles zusammen und optimieren nach und nach. Wenn ein Schatten keine Unschärfe hat, kann „fillText“ für denselben Effekt verwendet werden, sodass wir die Clipmaske nicht einrichten müssen:

var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
    ctx.shadowColor = shadow.color;
    ctx.shadowOffsetX = shadow.x + totalWidth;
    ctx.shadowOffsetY = shadow.y;
    ctx.shadowBlur = shadow.blur;
    ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
    ctx.fillStyle = shadow.color;
    ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);

Da Sie nicht alle diese <canvas>-Befehle manuell eingeben möchten, habe ich in der Demoquelle einen einfachen Text-Shadow-Parser hinzugefügt. So können Sie ihm CSS-Befehle zuführen und <canvas>-Befehle generieren lassen. Unsere <canvas>-Elemente können mit einer ganzen Reihe von Stilen verknüpft werden. Dieselben Schatteneffekte können für jedes Vektorobjekt verwendet werden, von WebFonts über komplexe Formen, die aus SVGs importiert wurden, bis hin zu generativen Vektorformen usw.

Textschatten in Canvas-Effekten

Pause (Angaben zu Pixel-Pushing)

Beim Schreiben dieses Abschnitts des Artikels hat mich das Beispiel für Stereoskope neugierig gemacht. Wie schwierig wäre es, mit <canvas> und zwei Bildern, die aus leicht unterschiedlichen Perspektiven aufgenommen wurden, einen 3D-Filmbildschirmeffekt zu erstellen? Offensichtlich nicht zu schwer. Der folgende Kernel kombiniert den Rotkanal des ersten Bilds (data) mit dem Cyankanal des zweiten Bilds (data2):

data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;

Jetzt muss jemand nur noch zwei iPhones mit Klebeband an die Stirn kleben, gleichzeitig auf „Video aufnehmen“ klicken und schon können wir unsere eigenen 3D-Filme in HTML5 erstellen. Haben Sie Lust?

3D-Brillen

Neon-Regenbogen, Zebra-Spiegelung – Verkettung von Effekten

Die Verkettung mehrerer Effekte in <canvas> kann einfach sein, es sind jedoch grundlegende Kenntnisse zu globalCompositeOperation (GCO) erforderlich. Zum Vergleich der Vorgänge mit GIMP (oder Photoshop): Es gibt 12 GCOs in <canvas>, die als Ebenenmischmodi betrachtet werden können: dunkler und heller. Die anderen 10 Vorgänge werden als Alphamasken auf die Ebenen angewendet (eine Ebene entfernt die Pixel der anderen Ebene). Mit dem globalCompositeOperation werden „Ebenen“ (in unserem Fall Codestrings) miteinander verbunden und auf neue und spannende Weise kombiniert:

Grafik mit Verkettungseffekten

Das Diagramm globalCompositeOperation zeigt die GCO-Modi in Aktion. Dieses Diagramm verwendet einen großen Teil des Farbspektrums und mehrere Ebenen der Alpha-Transparenz, um im Detail zu sehen, was zu erwarten ist. Textbeschreibungen finden Sie in der Referenz zu globalCompositeOperation von Mozilla. Weitere Informationen zur Funktionsweise finden Sie in Porter Duffs Buch Compositing Digital Images (Digitale Bilder zusammensetzen).

Mein Lieblingsmodus ist globalCompositeOperation="lighter". Lighter mischt die hinzugefügten Pixel ähnlich wie Licht. Wenn Rot, Grün und Weiß auf voller Intensität sind, sehen wir weißes Licht. Es ist eine spannende Funktion, mit der man experimentieren kann, insbesondere wenn <canvas> auf einen niedrigen globalAlpha-Wert eingestellt ist. Dies ermöglicht eine feinere Kontrolle und weichere Kanten. Lighter kann für viele Zwecke verwendet werden. Mein aktueller Favorit ist ein HTML5-Desktophintergrund-Generator unter http://weavesilk.com/. In einer meiner Demos, Breathing Galaxies (JS1k), wird ebenfalls der hellere Modus verwendet. Wenn Sie Muster aus diesen beiden Beispielen zeichnen, sehen Sie, welche Wirkung dieser Modus hat.

.

globalCompositeOperation-Browserbehandlung.

Neon-Regenbogen-Jitter-Effekt

In der folgenden Demo erstellen wir einen Photoshop-ähnlichen Neon-Regenbogen-Glanz mit einem verwackelten Umriss, indem wir Effekte mithilfe des globalen Composite-Effekts (Quelle-in, heller und dunkler) miteinander verknüpfen. Diese Demo ist eine Weiterentwicklung der Demo „Textschatten in <canvas>“. Dabei wird dieselbe Strategie verwendet, um den Schatten vom Text zu trennen (siehe vorheriger Abschnitt):

Regenbogen-Jitter
function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
        offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
            metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) { 
    var left = jitter / 2 - Math.random() * jitter;
    var top = jitter / 2 - Math.random() * jitter;
    ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}    
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};

Zebra-Reflexionseffekt

Der Zebra-Reflektionseffekt wurde von der hervorragenden Ressource WebDesignerWall inspiriert, wie Sie Ihre Seite mit CSS aufpeppen können. Hier wird die Idee noch ein wenig weitergeführt, indem eine „Reflexion“ für den Text erstellt wird, ähnlich wie in iTunes. Der Effekt kombiniert die fillColor (weiß), createPattern (zebra.png) und linearGradient (shine). Dies veranschaulicht die Möglichkeit, auf jedes Vektorobjekt mehrere Füllungstypen anzuwenden:

Zebraeffekt
function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";

// save state
ctx.save();
ctx.font = font;

// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;

// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);

// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);

// cut the gradient out of the reflected text 
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);

// restore back to original transform state
ctx.restore();

// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";

// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');

// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};

Innere/äußere Schatten in Canvas

In den Spezifikationen für <canvas> wird das Thema „innere“ im Vergleich zu „äußeren“ Schatten nicht behandelt. Auf den ersten Blick könnte man meinen, dass „innere“ Schatten nicht unterstützt werden. Das stimmt nicht. Es ist nur ein wenig schwieriger, diese Funktion zu aktivieren. ;) Wie in einem aktuellen Beitrag von F1LT3R vorgeschlagen, können Sie innere Schatten mithilfe der einzigartigen Eigenschaften von Regeln für das Umdrehen im oder gegen den Uhrzeigersinn erstellen. Dazu erstellen Sie einen „inneren Schatten“, indem Sie ein Containerrechteck zeichnen und dann mit entgegengesetzten Windungsregeln eine Aussparungsform zeichnen, um den Kehrwert der Form zu erzeugen.

Im folgenden Beispiel können der innere Schatten und „fillStyle“ mit Farbe, Farbverlauf und Muster gleichzeitig stilisiert werden. Sie können die Musterrotation einzeln festlegen. Beachten Sie, dass die Zebrastreifen jetzt senkrecht zueinander stehen. Da eine Clipmaske in der Größe des Begrenzungs-Quaders verwendet wird, ist kein übergroßer Container erforderlich, um die Ausschnittsform zu umschließen. Dadurch wird die Geschwindigkeit verbessert, da die unnötigen Teile des Schattens nicht verarbeitet werden.

Innen-/Außenschatten
function innerShadow() {

function drawShape() { // draw anti-clockwise
ctx.arc(0, 0, 100, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(70, 0);
ctx.arc(0, 0, 70, 0, Math.PI, false); // Mouth
ctx.moveTo(-20, -20);
ctx.arc(30, -30, 10, 0, Math.PI * 2, false); // Left eye
ctx.moveTo(140, 70);
ctx.arc(-20, -30, 10, 0, Math.PI * 2, false); // Right eye
};

var width = 200;
var offset = width + 50;
var innerColor = "rgba(0,0,0,1)";
var outerColor = "rgba(0,0,0,1)";

ctx.translate(150, 170);

// apply inner-shadow
ctx.save();
ctx.fillStyle = "#000";
ctx.shadowColor = innerColor;
ctx.shadowBlur = getBlurValue(120);
ctx.shadowOffsetX = -15;
ctx.shadowOffsetY = 15;

// create clipping path (around blur + shape, preventing outer-rect blurring)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
ctx.clip();

// apply inner-shadow (w/ clockwise vs. anti-clockwise cutout)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
drawShape();
ctx.fill();
ctx.restore();

// cutout temporary rectangle used to create inner-shadow
ctx.globalCompositeOperation = "destination-out";
ctx.fill();

// prepare vector paths
ctx.beginPath();
drawShape();

// apply fill-gradient to inner-shadow
ctx.save();
ctx.globalCompositeOperation = "source-in";
var gradient = ctx.createLinearGradient(-offset/2, 0, offset/2, 0);
gradient.addColorStop(0.3, '#ff0');
gradient.addColorStop(0.7, '#f00');
ctx.fillStyle = gradient;
ctx.fill();

// apply fill-pattern to inner-shadow
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 1;
ctx.rotate(0.9);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();

// apply fill-gradient
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var gradient = ctx.createLinearGradient(-offset/2, -offset/2, offset/2, offset/2);
gradient.addColorStop(0.1, '#f00');
gradient.addColorStop(0.5, 'rgba(255,255,0,1)');
gradient.addColorStop(1.0, '#00f');
ctx.fillStyle = gradient
ctx.fill();

// apply fill-pattern
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 0.2;
ctx.rotate(-0.4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();

// apply outer-shadow (color-only without temporary layer)
ctx.globalCompositeOperation = "destination-over";
ctx.shadowColor = outerColor;
ctx.shadowBlur = 40;
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 10;
ctx.fillStyle = "#fff";
ctx.fill();
};

Anhand dieser Beispiele sehen Sie, dass wir mit „globalCompositeOperation“ Effekte miteinander kombinieren und so komplexere Effekte erzielen können (mithilfe von Maskierung und Überblendung). Der Bildschirm ist deine Auster ;)

Spaceage – generative Effekte

In <canvas>, ausgehend vom Unicode-Zeichen 0x2708:

Unicode-Grafik

...in diesem schattierten Beispiel:

Beispiel mit Schattierung

…erreichen Sie durch mehrere Aufrufe von ctx.strokeText() mit einer dünnen Linienbreite (0,25) und gleichzeitiger langsamer Verringerung des X-Offsets und von Alpha, wodurch unsere Vektorelemente in Bewegung versetzt werden.

Wenn wir die XY-Position der Elemente einer Sinus-/Kosinuswelle zuordnen und mit der HSL-Eigenschaft die Farben durchlaufen, können wir interessantere Effekte erzielen, wie in diesem Beispiel für „Biohazard“:

HSL-Zykluseffekt

HSL: Farbton, Sättigung, Helligkeit (1978)

HSL ist ein neu unterstütztes Format in den CSS3-Spezifikationen. Während HEX für Computer entwickelt wurde, ist HSL für Menschen lesbar.

Die Einfachheit von HSL veranschaulichen: Um durch das Farbspektrum zu wechseln, erhöhen wir einfach den Farbton von 360. Der Farbton wird zylindrisch auf das Spektrum abgebildet. Mit dem Attribut „Helligkeit“ wird festgelegt, wie dunkel oder hell die Farbe ist. 0% steht für ein schwarzes Pixel, 100% für ein weißes Pixel. Mit der Sättigung wird festgelegt, wie hell oder intensiv eine Farbe ist. Grautöne werden mit einer Sättigung von 0 % und kräftige Farben mit einem Wert von 100 % erstellt.

HSL-Grafik

Da HSL ein neuer Standard ist, möchten Sie möglicherweise ältere Browser weiterhin unterstützen. Dies ist über eine Farbraumkonvertierung möglich. Der folgende Code akzeptiert ein HSL-Objekt { H: 360, S: 100, L: 100} und gibt ein RGB-Objekt { R: 255, G: 255, B: 255 } aus. Anhand dieser Werte können Sie einen RGB- oder RGBA-String erstellen. Weitere Informationen finden Sie im Wikipedia-Artikel HSL.

// HSL (1978) = H: Hue / S: Saturation / L: Lightness
HSL_RGB = function (o) { // { H: 0-360, S: 0-100, L: 0-100 }
var H = o.H / 360,
    S = o.S / 100,
    L = o.L / 100,
    R, G, B, _1, _2;

function Hue_2_RGB(v1, v2, vH) {
if (vH < 0) vH += 1;
if (vH > 1) vH -= 1;
if ((6 * vH) < 1) return v1 + (v2 - v1) * 6 * vH;
if ((2 * vH) < 1) return v2;
if ((3 * vH) < 2) return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
return v1;
}

if (S == 0) { // HSL from 0 to 1
R = L * 255;
G = L * 255;
B = L * 255;
} else {
if (L < 0.5) {
    _2 = L * (1 + S);
} else {
    _2 = (L + S) - (S * L);
}
_1 = 2 * L - _2;

R = 255 * Hue_2_RGB(_1, _2, H + (1 / 3));
G = 255 * Hue_2_RGB(_1, _2, H);
B = 255 * Hue_2_RGB(_1, _2, H - (1 / 3));
}

return {
R: R,
G: G,
B: B
};
};

Animationen mit requestAnimationFrame erstellen

Bisher gab es zwei Möglichkeiten, Animationen in JavaScript zu erstellen: setTimeout und setInterval.

window.requestAnimationFrame ist der neue Standard, der beide ersetzen soll. So wird Strom gespart (und Ihr Computer wird entlastet), da der Browser Animationen basierend auf den verfügbaren Ressourcen regulieren kann. Einige wichtige Funktionen sind:

  • Wenn ein Nutzer den Frame verlässt, kann die Animation verlangsamt oder vollständig angehalten werden, um die Verwendung nicht benötigter Ressourcen zu verhindern.
  • Die Framerate ist auf 60 fps begrenzt. Das liegt daran, dass es weit über dem Niveau liegt, das Menschen wahrnehmen können. Für die meisten Menschen ist eine Animation bei 30 fps „flüssig“.

Zum Zeitpunkt der Erstellung dieses Artikels sind anbieterspezifische Präfixe erforderlich, um requestAnimationFrame zu verwenden. Paul Irish hat eine Shim-Ebene mit anbieterübergreifender Unterstützung in requestAnimationFrame für intelligente Animationen erstellt:

// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return  window.requestAnimationFrame       || 
        window.webkitRequestAnimationFrame || 
        window.mozRequestAnimationFrame    || 
        window.oRequestAnimationFrame      || 
        window.msRequestAnimationFrame     || 
        function(/* function */ callback, /* DOMElement */ element){
        window.setTimeout(callback, 1000 / 60);
        };
})();

Wenn wir noch einen Schritt weiter gehen, könnten wir dies mit einem Polyfill wie requestAnimationFrame.js verbinden, das beim Wechsel zu diesem neuen Standard ältere Browser in größerem Umfang unterstützen würde.

(function animate() {
var i = 50;
while(i--) {
    if (n > endpos) return;

    n += definition;
    ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
    if (doColorCycle) {
        hue = n + color;
        ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
    }
    var x = cos(n / cosdiv) * n * cosmult; // cosine
    var y = sin(n / sindiv) * n * sinmult; // sin
    ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();
Unschärfe-Grafik für Notizen
Animationsgrafik
Matrixgrafik

Quellcode

Aufgrund der Unterstützung verschiedener Browser von verschiedenen Anbietern kann es keine Frage über die Zukunft von <canvas> stellen, damit es über PhoneGap auf die ausführbaren Dateien für iPhone/Android/Desktop übertragen werden kann.

Titan