Typografische Effekte in Canvas

Angebot für Michael
Michael-Deal

Hintergrundinformationen zu meiner Person

Ich wurde 2006 auf <canvas> aufmerksam, als Firefox v2.0 auf den Markt kam. Ein Artikel über Ajaxian, der die Transformationsmatrix beschreibt, hat mich dazu inspiriert, meine erste <canvas>-Web-App zu erstellen: Color Sphere (2007). Das hat mich in die Welt der Farben und grafischen Primitiven eingetaucht. Inspiriert wurde sie zur Entwicklung von Sketchpad (2007–2008), um eine Anwendung zu erstellen, 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 nicht destruktive App zum Teilen von Fotos, die die Möglichkeiten pixelbasierter Filter mit vektorbasierten Typografie- und Zeichenfunktionen kombiniert.

Einleitung

Canvas-Bannergrafik.

Mit <canvas> haben JavaScript-Programmierer die volle Kontrolle über die Farben, Vektoren und Pixel auf ihren Bildschirmen, also über den visuellen Aufbau des Monitors.

In den folgenden Beispielen wird ein bislang wenig viel diskutierter Bereich von <canvas> beschrieben, nämlich das Erstellen von Texteffekten. Die Vielfalt der Texteffekte, die in <canvas> erstellt werden können, ist so umfangreich, wie man sich vorstellen kann. Diese Demos decken nur einen Teil dessen ab, was alles möglich ist. In diesem Artikel geht es zwar um Text, die Methoden können aber auch auf alle Vektorobjekte angewendet werden. So können Sie spannende visuelle Elemente in Spielen und anderen Anwendungen erstellen:

Textschatten in <canvas>.
CSS-ähnliche Texteffekte in <canvas>, mit denen Ausschneidemasken erstellt, Messwerte in <canvas> ermittelt und die Schatteneigenschaft verwendet wird
Neon-Regenbogen, Zebra-Reflexion – Verkettungseffekte.
Photoshop-ähnliche Texteffekte in <canvas> Beispielen zur Verwendung von „globalCompositeOperation“, „createLinearGradient“ und „createPattern“.
Schatten nach innen und außen in <canvas>
Enthüllung eines wenig bekannten Merkmals: Durch das Gegenstück des Schlagschattens (innerer Schatten) entsteht die Umkehrung des Schlagschattens im oder gegen den Uhrzeigersinn.
Abstand – generativer Effekt.
Generative Texteffekte in <canvas> mit hsl()-Farbwechsel und window.requestAnimationFrame, um ein Gefühl von Bewegung zu erzeugen.

Textschatten in Canvas

Eine der besten Ergänzungen zu den CSS3-Spezifikationen (neben Border-Radius, Web-Farbverläufen und anderen) ist die Möglichkeit, Schatten zu erstellen. Beachten Sie die folgenden Unterschiede zwischen CSS- und <canvas>-Schatten:

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

<canvas> hat einen Schattentyp. Er wird für alle Vektorobjekte verwendet: ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText usw. Um in <canvas> einen Schatten zu erstellen, tippen Sie auf diese vier Eigenschaften:

ctx.shadowColor = "red" // String
Die Farbe des Schattens; RGB, RGBA, HSL, HEX und andere Eingaben sind gültig.
ctx.shadowOffsetX = 0; // Ganzzahl
Horizontaler Abstand des Schattens in Bezug auf den Text
ctx.shadowOffsetY = 0; // Ganzzahl
Vertikaler Abstand des Schattens in Bezug auf den Text
ctx.shadowBlur = 10; // Ganzzahl
Unschärfe, je größer der Wert, desto stärker ist die Unschärfe.

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

CSS-3D-Grafik

Der stereoskopische 3D-Effekt (siehe Anaglyphenbild) ist ein Beispiel für eine einfache Codezeile, die sich hervorragend eignet. Mit der folgenden CSS-Zeile können wir einen Tiefeneffekt erzeugen, wenn man eine 3D-Rot-/Cyan-Brille betrachtet (wie bei 3D-Filmen):

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

Beim Konvertieren dieses Strings in <canvas> sind zwei Dinge zu beachten:

  1. Es gibt keine Schattenunschärfe (dritter Wert), also gibt es keinen Grund, shadow auszuführen, da "fillText" zu den gleichen Ergebnissen führen 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. EM-Werte werden in <canvas> nicht unterstützt. Daher müssen sie in PX-Dateien konvertiert werden. Um das Konvertierungsverhältnis für die Umwandlung von PT, PC, EM, EX, PX usw. zu ermitteln, erstellen Sie ein Element mit den gleichen Schrifteigenschaften im DOM und legen die Breite auf das zu messende Format fest. Um die Konvertierung EM in PX zu erfassen, messen wir das DOM-Element mit einer Höhe: 1em. Der resultierende Versatz in PXHeight entspricht dem Wert in PXHeight.
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 in Line25, muss die Eigenschaft shadowBlur verwendet werden, um den Effekt korrekt zu emulieren. Da der Neoneffekt auf mehreren Schatten basiert, tritt ein Problem auf. In <canvas> kann jedes Vektorobjekt nur einen Schatten haben. Um mehrere Schatten zu zeichnen, müssen Sie also mehrere Versionen des Texts übereinander zeichnen. Dies führt zu einer Alpha-Multiplikation und schließlich zu zerklüfteten Kanten.

Neongrafik

Ich habe versucht, mit ctx.fillStyle = "rgba(0,0,0,0)" oder "transparent" den Text zu verbergen und gleichzeitig den Schatten anzuzeigen. Dieser Versuch war jedoch erfolglos. Da der Schatten eine Multiplikation von „fillStyle“ ist, kann er nie opaker als „fillStyle“ sein.

Glücklicherweise gibt es eine Möglichkeit, dies zu umgehen. Sie können den Schatten vom Text versetzt zeichnen, dabei getrennt bleiben (damit sie sich nicht überschneiden) und den Text so seitlich am Bildschirm verbergen:

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

Um einen Textblock ausschneiden

Um dies zu bereinigen, können Sie verhindern, dass der fillText an erster Stelle gezeichnet wird (und der Schatten gezeichnet werden kann), indem wir einen Ausschneidepfad hinzufügen. Um einen Beschneidungspfad um den Text herum zu erstellen, müssen wir die Höhe des Textes (die "em-height", früher die Höhe des Buchstabens "M" auf einer Druckpresse genannt) und die Breite des Texts kennen. Die Breite lässt sich mit ctx.measureText().width ermitteln, ctx.measureText().height ist jedoch nicht vorhanden.

Glücklicherweise können wir durch CSS-Hacking (siehe Typografische Messwerte für weitere Möglichkeiten zur Korrektur älterer Implementierungen von <canvas> mithilfe von CSS-Messungen) die Höhe des Textes ermitteln, indem wir den offsetHeight einer <span> mit denselben Schrifteigenschaften messen:

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

Von dort aus können wir ein Rechteck erstellen, das als Ausschneidepfad verwendet wird. Es schließt den „Schatten“ ein, während wir die Dummy-Form entfernen.

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

Alle Elemente werden miteinander verbunden und anschließend optimiert. Wenn ein Schatten keine Unschärfe aufweist, kann „fillText“ für denselben Effekt verwendet werden, sodass wir die Ausschneidemaske 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 die Demoquelle einen einfachen Textschatten-Parser eingefügt. So können Sie CSS-Befehle einspeisen und <canvas>-Befehle generieren lassen. Jetzt haben die <canvas>-Elemente eine ganze Reihe von Stilen, mit denen sie verbunden werden können. 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 (Thema Pixel-Pushing)

Das Beispiel mit dem Stereoskopischen Effekt hat mich neugierig gemacht. Wie schwierig wäre es, mit <canvas> und zwei Bildern, die aus leicht unterschiedlichen Perspektiven aufgenommen wurden, einen 3D-Filmeffekt zu erzeugen? Offensichtlich nicht zu schwierig. Der folgende Kernel kombiniert den roten Kanal des ersten Bilds (data) mit dem Kanal in Cyan 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 zwei iPhones mit Klebeband an die Stirn befestigen und gleichzeitig auf "Video aufzeichnen" klicken, und wir könnten unsere eigenen 3D-Filme in HTML5 erstellen. Gibt es Freiwillige?

3D-Brille

Neonregenbogen, Zebra-Reflexion – Verkettung von Effekten

Das Verketten mehrerer Effekte in <canvas> kann einfach sein, es sind jedoch grundlegende Kenntnisse über die globalCompositeOperation (GCO) erforderlich. Zum Vergleich der Operationen mit GIMP (oder Photoshop): Es gibt 12 GCOs in <canvas> (dunkler) und hellere können als Ebenenüberblendmodi betrachtet werden. Die anderen 10 Vorgänge werden auf die Ebenen als Alpha-Masken angewendet (eine Ebene entfernt die Pixel der anderen Ebene). Die globalCompositeOperation verbindet „Layers“ (oder in unserem Fall Code-Strings) und kombiniert sie auf neue und spannende Weise:

Effektgrafiken verketten

Das globalCompositeOperation-Diagramm zeigt GCO-Modi bei der Arbeit. Es nutzt einen großen Teil des Farbspektrums und mehrere Ebenen der Alpha-Transparenz, um im Detail zu sehen, was Sie erwartet. Textbeschreibungen finden Sie in der globalCompositeOperation-Referenz von Mozilla. Weitere Informationen zur Funktionsweise des Vorgangs finden Sie im Artikel Compositing Digital Images von Porter Duff.

Mein Lieblingsmodus ist globalCompositeOperation="lighter". Die Option „Heller“ vermischt die angehängten Pixel ähnlich wie das Licht. Wenn rotes, grünes und weißes Licht in voller Intensität sind, sehen wir weißes Licht. Diese Funktion eignet sich besonders, wenn für <canvas> ein niedriger globalAlpha-Wert festgelegt ist. Sie ermöglicht eine feinere Steuerung und weichere Ränder. „Lighter“ wurde vielfältig eingesetzt. Mein neuestes Favorit ist ein Creator für HTML5-Desktophintergründe, den ich unter http://weavesilk.com/ gefunden habe. In einer meiner Demos, Breathing Galaxies (JS1k), wird ebenfalls der leichtere Modus verwendet. Durch Zeichnen von Mustern aus diesen beiden Beispielen sehen Sie, welchen Effekt dieser Modus hat.

globalCompositeOperation-Browserbehandlung.

Neon-Regenbogen-Jitter-Effekt

In der folgenden Demo werden wir einen Photoshop-ähnlichen Neon-Regenbogen-Schein mit einer jitterten Umrisslinie erstellen, indem wir Effekte mithilfe der globalCompositeOperation (source-in, heller und dunkler) miteinander verketten. Diese Demo ist eine Fortsetzung der Demo „Textschatten in <canvas>“. Dabei wird dieselbe Strategie verwendet, um den Schatten vom Text zu trennen (siehe vorherigen 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-Reflexionseffekt wurde von WebDesignerWall inspiriert, einer erstklassigen Ressource, in der es darum geht, wie Sie Ihre Seite mit CSS aufpeppen. Damit gehen Sie noch einen Schritt weiter und erstellen eine „Reflexion“ für den Text, wie etwa das, was Sie in iTunes sehen. Der Effekt kombiniert „fillColor“ (weiß), „createPattern“ (zebra.png) und „linearGradient“ (Glanz). Dies veranschaulicht die Möglichkeit, mehrere Fülltypen auf jedes Vektorobjekt anzuwenden:

Zebra-Effekt
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();
};

Schatten nach innen und außen in Canvas

In den <canvas>-Spezifikationen geht es nicht um die Schatten von inneren und äußeren. Auf den ersten Blick erwartet man, dass der "innere" Schatten nicht unterstützt wird. Das stimmt nicht. Es ist nur ein bisschen komplizierter, dies zu aktivieren. ;) Wie in einem aktuellen Beitrag von F1LT3R vorgeschlagen, können Sie innere Schatten mit den einzigartigen Eigenschaften von Dreh- im und gegen den Uhrzeigersinn erstellen. Dazu erstellen Sie einen „inneren Schatten“, indem Sie ein Containerrechteck zeichnen und dann mit entgegengesetzten Kurvenregeln eine Aussparungsform zeichnen und so die Umkehrung der Form erzeugen.

Im folgenden Beispiel können der innere Schatten und „fillStyle“ gleichzeitig mit Farbe, Farbverlauf und Muster gestaltet werden. Sie können die Musterrotation einzeln angeben. Beachten Sie, dass die Zebrastreifen jetzt senkrecht zueinander verlaufen. Es wird eine Ausschneidemaske in der Größe des Begrenzungsrahmens verwendet. Es ist kein sehr großer Container erforderlich, der die Aussparungsform umschließt. Dadurch wird die Geschwindigkeit verbessert, da unnötige Teile des Schattens nicht verarbeitet werden.

Schatten nach innen und außen
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();
};

An diesen Beispielen sehen Sie, dass wir mithilfe von globalCompositeOperation Effekte verketten können, um durch Maskieren und Überblenden ausgefeiltere Effekte zu erzeugen. Das Display ist deine Austern ;)

Generative Effekte für den Abstand

Gehen Sie in <canvas> vom Unicode-Zeichen 0x2708 wie folgt vor:

gfaphic (Unicode)

...und auf dieses schattierte Beispiel:

Schattiertes Beispiel

...kann durch mehrere Aufrufe von ctx.strokeText() mit einer dünnen „lineWidth“ (0,25) erreicht werden, wobei der x-Offset und der Alphawert langsam verringert werden kann. So erhalten unsere Vektorelemente den Eindruck einer Bewegung.

Wenn die XY-Position der Elemente einer Sinus-/Kosinus-Welle zugeordnet und mithilfe der HSL-Eigenschaft ein Farbwechsel durchgeführt wird, können interessantere Effekte erzielt werden, wie z. B. dieses „biohazard“-Beispiel:

HSL-Zykluseffekt

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

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

Um das Farbspektrum zu durchlaufen, erhöhen wir einfach den Farbton von 360. Der Farbton wird dem Spektrum zylindrisch zugeordnet. Mit der Helligkeit wird festgelegt, wie dunkel/hell die Farbe ist. 0% steht für ein schwarzes Pixel und 100% für ein weißes Pixel. Die Sättigung steuert, wie hell oder lebhaft 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 es sich bei HSL um einen neuen Standard handelt, möchten Sie möglicherweise weiterhin ältere Browser unterstützen, was über die Farbraumkonvertierung möglich ist. 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. Anschließend können Sie mit diesen Werten Ihren RGB- oder RGBA-String erstellen. Ausführlichere Informationen finden Sie im aufschlussreichen Wikipedia-Artikel zum 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 konnten Sie zum Erstellen von Animationen in JavaScript zwei Optionen auswählen: setTimeout und setInterval.

window.requestAnimationFrame ist der neue Standard, der beide ersetzt. Sie sparen Strom und schonen Ihren Computer ein paar Herzschläge, da der Browser Animationen anhand der verfügbaren Ressourcen regulieren kann. Einige wichtige Funktionen sind:

  • Sobald ein Nutzer den Frame verlässt, kann die Animation verlangsamen oder vollständig stoppen, um die Verwendung nicht benötigter Ressourcen zu verhindern.
  • Es gibt eine Obergrenze von 60 fps für die Frame-Rate. Der Grund dafür ist, dass er deutlich über dem Niveau liegt, das für Menschen wahrgenommen werden kann (für die meisten Menschen mit 30 fps wird die Animation als „Flüssigkeit“) angezeigt.

Zum Zeitpunkt der Erstellung dieses Dokuments sind Anbieterspezifische Präfixe erforderlich, um requestAnimationFrame zu verwenden. Paul Irish hat in requestAnimationFrame für intelligente Animationen eine Shim-Ebene erstellt, die von mehreren Anbietern unterstützt wird:

// 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önnte man dies mit einem Polyfill wie requestAnimationFrame.js kombinieren, der für einen Wechsel zu diesem neuen Standard noch ein paar Funktionen ausbaut, die ältere Browser noch weiter unterstützen würden.

(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 zur Verpixelung der Notizen
Animationsgrafik
Matrixgrafik

Quellcode

Da die Browser von allen Anbietern unterstützt werden, steht keine Frage zur Zukunft von <canvas>. Es kann mit PhoneGap in die ausführbaren Dateien für iPhone/Android/Desktop übertragen werden.

Titan: