Typografische Effekte in Canvas

Michael Deal
Michael Deal

Mein Hintergrund

<canvas> wurde mir 2006 mit der Veröffentlichung von Firefox v2.0 bekannt. Ein Artikel auf Ajaxian, in dem die Transformationsmatrix beschrieben wurde, inspirierte mich dazu, meine erste <canvas>-Web-App zu erstellen: Color Sphere (2007). Dadurch bin ich in die Welt der Farben und grafischen Primitive eingetaucht und habe Sketchpad (2007–2008) entwickelt, um eine Anwendung zu schaffen, die „besser als Paint“ im Browser ist. Diese Experimente führten schließlich zur Gründung des Start-ups Mugtug mit meinem langjährigen Freund Charles Pritchard. Wir entwickeln Darkroom in HTML5 <canvas>. Darkroom ist eine App zum Teilen von Fotos, die pixelbasierte Filter mit vektorbasierten Typografie- und Zeichenfunktionen kombiniert.

Einführung

Canvas-Bannergrafik.

<canvas> bietet JavaScript-Programmierern die volle Kontrolle über die Farben, Vektoren und Pixel auf dem Bildschirm – also über die visuelle Darstellung auf dem Monitor.

Die folgenden Beispiele behandeln einen Bereich in <canvas>, der bisher wenig Beachtung gefunden hat: das Erstellen von Texteffekten. Die Vielfalt der Texteffekte, die in <canvas> erstellt werden können, ist so groß, wie Sie es sich vorstellen können. Diese Demos decken einen Teilbereich dessen ab, was möglich ist. Obwohl wir uns in diesem Artikel mit „Text“ befassen, können die Methoden auf alle 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, Suchen von Messwerten in <canvas> und Verwenden der Schatteneigenschaft.
Neon-Regenbogen, Zebra-Reflexion – Verkettungseffekte.
Photoshop-ähnliche Texteffekte in <canvas> Beispielen mit globalCompositeOperation, createLinearGradient und createPattern.
Innere und äußere Schatten in <canvas>
Eine wenig bekannte Funktion wird vorgestellt: Durch die Verwendung von Wicklungen im Uhrzeigersinn im Vergleich zu Wicklungen gegen den Uhrzeigersinn wird das Gegenteil eines Schlagschattens (der innere Schatten) erzeugt.
Spaceage – generativer Effekt.
Generativer Texteffekt in <canvas> mit hsl()-Farbwechsel und window.requestAnimationFrame, um den Eindruck von Bewegung zu erzeugen.

Textschatten im Canvas

Eine meiner Lieblingsergänzungen zu den 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, insbesondere:

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

<canvas> hat einen Schattentyp, der für alle Vektorobjekte verwendet wird: 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, können Sie diese vier Eigenschaften nutzen:

ctx.shadowColor = "red" // string
Farbe des Schattens; RGB, RGBA, HSL, HEX und andere Eingaben sind gültig.
ctx.shadowOffsetX = 0; // integer
Horizontaler Abstand des Schattens zum Text.
ctx.shadowOffsetY = 0; // integer
Vertikaler Abstand des Schattens im Verhältnis zum Text.
ctx.shadowBlur = 10; // integer
Weichzeichnungseffekt für den Schatten. Je größer der Wert, desto stärker die Weichzeichnung.

Sehen wir uns zuerst an, wie <canvas> CSS-Effekte emulieren kann. Die Suche nach „css text-shadow“ in Google Bilder hat uns zu einigen großartigen Demos geführt, die wir nachahmen können: Line25, Stereoscopic und Shadow 3D.

3D-Grafik für Preisvergleichsportale

Der stereoskopische 3D-Effekt (weitere Informationen finden Sie unter Anaglyphenbild) ist ein Beispiel für eine einfache Codezeile, die sehr nützlich ist. Mit der folgenden CSS-Zeile können wir die Illusion von Tiefe erzeugen, wenn wir das Bild mit einer 3D-Brille (Rot/Cyan) betrachten (wie sie im Kino verwendet wird):

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

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

  1. Es gibt keine Schattenunschärfe (der dritte Wert). Es gibt also keinen Grund, den Schatten tatsächlich auszuführen, da „fillText“ dieselben Ergebnisse liefern würde:
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. EMs werden in <canvas> nicht unterstützt und müssen in PX konvertiert werden. Wir können das Umrechnungsverhältnis für die Umrechnung zwischen PT, PC, EM, EX, PX usw. ermitteln, indem wir ein Element mit denselben Schriftarteigenschaften im DOM erstellen und die Breite auf das zu messende Format festlegen. Um beispielsweise die Umrechnung von EM in PX zu erfassen, würden wir das DOM-Element mit „height: 1em“ messen. Die resultierende offsetHeight wäre die Anzahl der PX in jedem EM.
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 Neon-Effekt auf Line25, muss die shadowBlur-Eigenschaft verwendet werden, um den Effekt richtig zu emulieren. Da der Neoneffekt auf mehreren Schatten basiert, gibt es 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 zu einer Alphamultiplikation und letztendlich zu gezackten Kanten.

Neon-Grafik

Ich habe versucht, ctx.fillStyle = "rgba(0,0,0,0)" oder "transparent" auszuführen, um den Text auszublenden und den Schatten anzuzeigen. Dieser Versuch war jedoch vergeblich, da der Schatten eine Multiplikation des fillStyle-Alphas ist und daher nie undurchsichtiger als der fillStyle sein kann.

Glücklicherweise gibt es eine Möglichkeit, dies zu umgehen. Wir können den Schatten versetzt vom Text zeichnen, sodass sie getrennt bleiben (und sich nicht überlappen), und den Text dadurch am Bildschirmrand ausblenden:

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);

Clipping um einen Textblock herum

Um das zu bereinigen, können wir verhindern, dass „fillText“ überhaupt gezeichnet wird (während der Schatten gezeichnet werden darf), indem wir einen Beschneidungspfad hinzufügen. Um einen Beschneidungspfad um den Text zu erstellen, müssen wir die Höhe des Texts (die sogenannte „em-Höhe“, historisch die Höhe des Buchstabens „M“ auf einer Druckerpresse) und die Breite des Texts kennen. Die Breite können wir mit ctx.measureText().width abrufen, ctx.measureText().height existiert jedoch nicht.

Glücklicherweise können wir mit CSS-Hacks (weitere Möglichkeiten zur Behebung älterer Implementierungen von <canvas> mithilfe von CSS-Messungen finden Sie unter Typografische Messwerte) die Höhe des Texts ermitteln, indem wir die offsetHeight eines <span> mit denselben Schriftarteigenschaften messen:

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

Dort können wir ein Rechteck erstellen, das als Schnittpfad dient. Es umschließt den „Schatten“ und entfernt die Dummy-Form.

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

Wenn wir alles zusammenfügen und optimieren, können wir, wenn ein Schatten keine Unschärfe hat, „fillText“ mit demselben Effekt verwenden und müssen die Schnittmaske nicht einrichten:

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 diese <canvas>-Befehle nicht alle manuell eingeben möchten, habe ich einen einfachen Textschatten-Parser in die Demoquelle aufgenommen. So können Sie CSS-Befehle eingeben und <canvas>-Befehle generieren lassen. Jetzt haben unsere <canvas>-Elemente eine ganze Reihe von Stilen, die sie verwenden können. Diese Schatteneffekte können auf jedes Vektorobjekt angewendet werden, von WebFonts über komplexe Formen, die aus SVGs importiert wurden, bis hin zu generativen Vektorformen.

Textschatten in Canvas-Effekten

Intermission (tangent on pixel-pushing)

Beim Schreiben dieses Abschnitts des Artikels hat mich das stereoskopische Beispiel neugierig gemacht. Wie schwierig wäre es, mit <canvas> und zwei Bildern, die aus leicht unterschiedlichen Perspektiven aufgenommen wurden, einen 3D-Kinoleinwandeffekt zu erstellen? Anscheinend nicht zu schwierig. Der folgende Kernel kombiniert den roten Kanal des ersten Bildes (data) mit dem Cyan-Kanal des zweiten Bildes (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 nur noch jemand zwei iPhones mit Klebeband an der Stirn befestigen, gleichzeitig auf „Video aufnehmen“ klicken und schon können wir unsere eigenen 3D-Filme in HTML5 erstellen. Freiwillige vor!

3D-Brille

Neon-Regenbogen, Zebra-Reflexion – Verkettungseffekte

Das Verketten mehrerer Effekte in <canvas> kann einfach sein, erfordert jedoch grundlegende Kenntnisse der globalCompositeOperation (GCO). Vergleich der Vorgänge mit GIMP (oder Photoshop): Es gibt 12 GCOs in <canvas>. Dunkler und Heller können als Ebenen-Mischmodi betrachtet werden. Die anderen 10 Vorgänge werden auf die Ebenen als Alphamasken angewendet (eine Ebene entfernt die Pixel der anderen Ebene). Die globalCompositeOperation verbindet „Ebenen“ (oder in unserem Fall Code-Strings) miteinander und kombiniert sie auf neue und spannende Weise:

Grafik zu Verkettungseffekten

Das globalCompositeOperation-Diagramm zeigt 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. Ich empfehle, die globalCompositeOperation-Referenz von Mozilla zu lesen, um Textbeschreibungen zu erhalten. Weitere Informationen zur Funktionsweise des Vorgangs finden Sie im Artikel Compositing Digital Images von Porter Duff.

Mein Lieblingsmodus ist globalCompositeOperation="lighter". „Aufhellen“ mischt die angehängten Pixel ähnlich wie Licht. Wenn rotes, grünes und weißes Licht mit voller Intensität leuchten, sehen wir weißes Licht. Es ist eine spannende Funktion, mit der man herumspielen kann, besonders wenn <canvas> auf einen niedrigen globalAlpha-Wert gesetzt ist, was eine feinere Steuerung und glattere Kanten ermöglicht. Lighter wurde für viele Zwecke eingesetzt. Mein aktueller Favorit ist ein HTML5-Desktop-Hintergrund-Creator, den Sie unter http://weavesilk.com/ finden. In einer meiner Demos, Breathing Galaxies (JS1k), wird ebenfalls der Modus „lighter“ verwendet. Wenn Sie sich die Muster aus diesen beiden Beispielen ansehen, können Sie erkennen, welchen Effekt dieser Modus hat.

globalCompositeOperation browser handling.

Effekt „Neon-Regenbogen-Jitter“

In der folgenden Demo erzielen wir einen Photoshop-ähnlichen Neon-Regenbogen-Glüheffekt mit einer unregelmäßigen Kontur, indem wir Effekte mithilfe von globalCompositeOperation (source-in, lighter und darker) verketten. Diese Demo ist eine Weiterentwicklung der Demo „Textschatten in <canvas>“ und verwendet dieselbe Strategie, 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();
};

Effekt „Zebra-Spiegelung“

Der Zebra-Reflexionseffekt wurde von der hervorragenden Ressource von WebDesignerWall inspiriert, die zeigt, wie Sie Ihre Seite mit CSS aufpeppen können. Hier wird die Idee noch ein wenig weitergeführt und eine „Spiegelung“ für den Text erstellt, wie Sie sie vielleicht in iTunes sehen. Der Effekt kombiniert „fillColor“ (weiß), „createPattern“ (zebra.png) und „linearGradient“ (shine). Dies zeigt, dass mehrere Fülltypen auf jedes Vektorobjekt angewendet werden können:

Effekt „Zebra“
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 im Canvas

In den <canvas>-Spezifikationen wird das Thema „innere“ und „äußere“ Schatten nicht behandelt. Auf den ersten Blick könnte man meinen, dass „inner“ nicht unterstützt wird. Das stimmt nicht. Es ist nur ein bisschen schwieriger zu aktivieren ;) Wie in einem kürzlich erschienenen Beitrag von F1LT3R vorgeschlagen, können Sie innere Schatten mithilfe der einzigartigen Eigenschaften von Wicklungsregeln im Uhrzeigersinn und gegen den Uhrzeigersinn erstellen. Dazu erstellen Sie einen „inneren Schatten“, indem Sie ein Containerrechteck zeichnen und dann mit entgegengesetzten Wicklungsregeln eine Ausschnittform zeichnen, um die Umkehrung der Form zu erstellen.

Im folgenden Beispiel können „inner-shadow“ und „fillStyle“ gleichzeitig mit Farbe, Farbverlauf und Muster formatiert werden. Sie können die Musterrotation individuell festlegen. Beachten Sie, dass die Zebrastreifen jetzt senkrecht zueinander stehen. Es wird eine Schnittmaske in der Größe des Begrenzungsrahmens verwendet, sodass kein übergroßer Container erforderlich ist, um die ausgeschnittene Form einzuschließen. Dadurch wird die Geschwindigkeit verbessert, da die unnötigen Teile des Schattens nicht verarbeitet werden müssen.

Innere/äußere Schatten
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 können Sie sehen, dass wir mit globalCompositeOperation Effekte miteinander verketten können, um komplexere Effekte (mit Maskierung und Überblendung) zu erzeugen. Der Bildschirm gehört dir ;)

Spaceage – generative Effekte

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

Unicode-Grafik

…zu diesem schattierten Beispiel:

Beispiel für Schattierung

…kann durch mehrere Aufrufe von ctx.strokeText() mit einer dünnen lineWidth (0,25) erreicht werden, wobei der x-Offset und der Alpha-Wert langsam verringert werden.Dadurch entsteht der Eindruck, dass sich die Vektorelemente bewegen.

Wenn wir die XY-Position der Elemente einer Sinus-/Kosinuswelle zuordnen und die Farben mithilfe der HSL-Eigenschaft durchlaufen, können wir interessantere Effekte erstellen, wie dieses „Biohazard“-Beispiel:

HSL-Zykluseffekt

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

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

Die Einfachheit von HSL wird deutlich, wenn wir das Farbspektrum durchlaufen: Wir erhöhen einfach den „Farbton“ von 360. Der Farbton wird zylindrisch auf das Spektrum abgebildet. Die Helligkeit bestimmt, 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 gesteuert, wie hell oder lebendig eine Farbe ist. Grautöne werden mit einer Sättigung von 0 % und lebendige Farben mit einem Wert von 100 % erstellt.

HSL-Grafik

Da HSL ein neuer Standard ist, möchten Sie möglicherweise weiterhin ältere Browser unterstützen. Dies ist über die 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. Mit diesen Werten können Sie dann Ihren RGB- oder RGBA-String erstellen. Weitere Informationen finden Sie im aufschlussreichen Wikipedia-Artikel zu 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

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

window.requestAnimationFrame ist der neue Standard, der beide ersetzen soll. Er spart Strom (und Ihrem Computer ein paar Herzschläge), da der Browser Animationen basierend auf den verfügbaren Ressourcen regulieren kann. Zu den wichtigsten Funktionen gehören:

  • Wenn ein Nutzer den Frame verlässt, kann die Animation verlangsamt oder vollständig gestoppt werden, um die Verwendung unnötiger Ressourcen zu vermeiden.
  • Die Framerate ist auf 60 fps begrenzt. Der Grund dafür ist, dass sie weit über dem Niveau liegt, das Menschen wahrnehmen können (die meisten Menschen sehen Animationen ab 30 fps als „flüssig“ an).

Zum Zeitpunkt der Erstellung dieses Artikels sind herstellerspezifische Präfixe erforderlich, um requestAnimationFrame zu verwenden. Paul Irish hat eine Shim-Ebene mit herstellerübergreifender Unterstützung in requestAnimationFrame for smart animating 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);
        };
})();

Noch einen Schritt weiter gehen die Ambitionierteren, die dies mit einem Polyfill wie requestAnimationFrame.js verknüpfen (es gibt einige Funktionen, die noch ausgearbeitet werden müssen), um ältere Browser beim Wechsel zu diesem neuen Standard besser zu unterstützen.

(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);
})();
Grafik: Notizen weichzeichnen
Animationsgrafik
Matrix-Grafik

Quellcode

Dank der Unterstützung durch die Browseranbieter ist die Zukunft von <canvas> gesichert. Es kann mithilfe von PhoneGap auf iPhone-/Android-/Desktop-Anwendungen portiert werden oder

Titan: