Informationen zu meiner Person
Zum ersten Mal stieß ich im Jahr 2006 auf <canvas>, als die Version 2.0 von Firefox auf den Markt kam. Ein Artikel zu Ajaxian, der die Transformationsmatrix beschrieb, brachte mich im Jahr 2007 dazu, meine erste <canvas>-Webanwendung, Color Sphere, zu erstellen. Dadurch beschäftigte ich mich intensiv mit der Welt der Farben und den Grundlagen zu Grafiken. Als Ergebnis entstand der Sketchpad (2007-2008) mit dem Ziel, dem Browser eine "gelungenere Anwendung als Paint" hinzuzufügen.Diese Ereignisse führten schließlich zur Gründung des Startup-Unternehmens Mugtug mit meinem langjährigen Freund Charles Pritchard. Wir entwickeln Darkroom in HTML5 <canvas>. Darkroom ist eine veränderungsfreie Anwendung zum Foto-Sharing und kombiniert leistungsstarke pixelbasierte Filter mit den Optionen für vektorbasierte Typografie und für Zeichnungen.
Einführung
Mit <canvas> haben JavaScript-Programmierer die volle Kontrolle über Farben, Vektoren und Pixel auf ihren Bildschirmen – also über das Aussehen des Monitors.
In den folgenden Beispielen wird ein bislang wenig bekannter Aspekt von <canvas> besprochen. Es geht um das Erstellen von Texteffekten. Die Vielfalt der mit <canvas> möglichen Texteffekte ist fast unbegrenzt. In diesen Demos sehen Sie nur einen Bruchteil der Möglichkeiten. Obwohl wir uns in diesem Artikel mit Text beschäftigen, lassen sich diese Vorgehensweisen auch auf alle Vektorobjekte anwenden. So erstellen Sie ganz besondere Grafiken für Spiele und andere Anwendungen:
- Textschatten in <canvas>
- CSS-ähnliche Texteffekte in <canvas> als Ausschneidemasken, Metriken in <canvas> und Verwendung der Schattierungseigenschaft
- Neon-Regenbogen, Zebra-Spiegelung – Verkettung von Effekten
- Photoshop-ähnliche Texteffekte in <canvas>, Beispiele für die Verwendung von globalCompositeOperation, createLinearGradient, createPattern.
- Innere und äußere Schatten in <canvas>
- Erläuterung einer eher unbekannten Funktion: Verwendung von Drehung im bzw. gegen den Uhrzeigersinn zur Umkehrung eines Schlagschattens, d. h. Erstellen eines inneren Schattens
- Generative Effekte für den Abstand
- Generative Texteffekte in <canvas> mit hsl()-Farbwechsel und
window.requestAnimationFramefür den Anschein einer Bewegung
Textschatten in Canvas
Eine der besten neuen Funktionen von CSS3, zu denen auch Border-Radius, Web-Farbverläufe und andere gehören, ist für mich die Möglichkeit zum Erstellen von Schatten. Dabei ist es wichtig, dass man sich die Unterschiede zwischen CSS und <canvas> in Bezug auf Schatten vor Augen führt. Diese lauten:
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 einen Schatten in <canvas> erstellen möchten, können Sie diese vier Eigenschaften nutzen:
- ctx.shadowColor = “red” // string
- Schattenfarbe: RGB, RGBA, HSL, HEX und andere Eingaben sind gültig.
- ctx.shadowOffsetX = 0; // integer
- Horizontaler Abstand des Schattens in Bezug auf den Text
- ctx.shadowOffsetY = 0; // integer
- Vertikaler Abstand des Schattens in Bezug auf den Text
- ctx.shadowBlur = 10; // integer
- Weichzeichnen des Schattens, je größer der Wert, umso größer die Weichzeichnung
Sehen wir uns zu Beginn an, wie <canvas> CSS-Effekte emuliert. Wenn man Bilder in Google nach "css text-shadow" durchsucht, findet man ein paar tolle Demos, die wir emulieren können: Line25 und Stereoscopic sowie Shadow 3D.
Der Stereoscopic 3D-Effekt (siehe Anaglyphenbild) ist ein Beispiel für eine einfache Codezeile, die sich vielseitig nutzen lässt. Mit der folgenden CSS-Zeile können Sie eine Illusion von Tiefe schaffen, wenn das Bild mit einer 3D-Brille in Rot/Cyan betrachtet wird. Eine solche Brille erhalten Sie auch im Kino für 3D-Filme.
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
Beim Umwandeln des Strings in <canvas> sind folgende zwei Dinge zu beachten:
(1) Es gibt keine Weichzeichnung beim Schatten (dritter Wert) und somit auch keinen Grund zum Ausführen von "shadow", da "fillText" zu denselben Ergebnissen führt.
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);
(2) EMs werden in <canvas> nicht unterstützt und müssen daher in Pixel umgewandelt werden. Das Umrechnungsverhältnis zwischen PT, PC, EM, EX, PX etc. erhalten Sie, indem Sie ein Element mit denselben Schriftarteigenschaften in DOM erstellen und seine Breite auf das zu messende Format festlegen. Zum Erfassen der EM -> PX-Umwandlung, messen Sie beispielsweise das DOM-Element mit einer "height: 1em". Die resultierende "offsetHeight" zeigt wie viele Pixel sich in jeder EM befinden.
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;
Alpha-Multiplikation verhindern
Bei einem komplexeren Beispiel, wie dem Neoneffekt in Line25, muss die Eigenschaft "shadowBlur" verwendet werden, um den Effekt korrekt zu emulieren. Da beim Neoneffekt mehrere Schatten erforderlich sind, haben wir hier ein Problem, denn in <canvas> kann jedes Vektorobjekt nur einen Schatten haben. Damit Sie mehrere Schatten zeichnen können, müssen Sie mehrere Versionen des Texts übereinander erstellen. Das führt zu Alpha-Multiplikation und letztlich zu unregelmäßigen Rändern.
Ich habe versucht ctx.fillStyle = “rgba(0,0,0,0)” oder “transparent” auszuführen, um den Text zu verbergen und gleichzeitig den Schatten anzuzeigen, aber das hat nichts bewirkt. Da der Schatten eine Multiplikation der "fillStyle"-Alpha ist, kann er niemals undurchsichtiger als "fillStyle" sein.
Zum Glück gibt es hierfür eine Lösung. Sie können den Schatten mit einem Abstand vom Text zeichnen, damit beide voneinander getrennt sind. Sie überlappen sich also nicht und der Text kann so seitlich außerhalb des Bildschirms verborgen werden.
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 Textabschnitt ausschneiden
Sie können diesen Vorgang ordentlicher gestalten, indem Sie verhindern, dass "fillText" überhaupt gezeichnet wird und dazu einen Ausschneidepfad hinzufügen. Der Schatten wird dennoch gezeichnet. Damit Sie einen Ausschneidepfad um den Text erstellen können, müssen Sie die Breite und die Höhe des Texts kennen. Letztere wird als "em-height" bezeichnet und entsprach früher der Höhe des Buchstabens "M" auf einer Druckpresse. Die Breite erhalten Sie mit ctx.measureText().width, aber ctx.measureText().height ist nicht vorhanden.
Zum Glück lässt sich die Höhe des Texts durch einen "CSS-Hack" herausfinden. Informationen zur Problemlösung bei älteren Bereitstellungen von <canvas>, die CSS-Maße verwenden, erhalten Sie unter Typographic Metrics. Hier finden Sie die Höhe des Texts, indem Sie 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;
Von hier aus können Sie ein Rechteck erstellen, das als Ausschneidepfad dient. Es beinhaltet den Schatten und entfernt die unnötige Form.
ctx.rect(0, 0, width, emHeight); ctx.clip();
Wenn man das nun alles verbindet und noch weiter optimiert, erhält man folgendes Ergebnis: Wenn ein Schatten keine Weichzeichnung hat, kann "fillText" verwendet werden, um denselben Effekt zu erstellen. So muss man keine Ausschneidemaske erstellen.
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 bestimmt nicht alle diese <canvas>-Befehle von Hand eingeben möchten, habe ich einen einfachen Textschatten-Parser in der Demo-Quelle erstellt. Über diesen können Sie CSS-Befehle übermitteln, die in <canvas>-Befehle umgewandelt werden. Das kann für viele verschiedene <canvas>-Elementstile genutzt werden. Dieselben Schatteneffekte können für alle Vektorobjekte verwendet werden. Dabei ist unerheblich, ob es sich um WebFonts oder um komplexe Formen, die aus SVGs importiert wurden, oder gar um generative Vektorformen usw. handelt.
Anzeigen des Textschatten in <canvas>-Effekts
Kurze Anmerkung (Thema Pixel-Pushing)
Beim Schreiben dieses Artikelabschnitts wurde ich neugierig auf das Stereoscopic-Beispiel. Wie schwierig wäre die Erstellung eines 3D-Filmeffekts mit <canvas> anhand von zwei Bildern, die aus leicht unterschiedlichen Perspektiven aufgenommen wurden? Wohl gar nicht so schwer. 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;
Besuchen Sie die Stereoscopic-Demo, in der gezeigt wird, wie Bilder für 3D-Brillen (Cyan/Magenta) erstellt werden können. Das heißt also, man müsste nur zwei iPhones mit Klebeband an der eigenen Stirn befestigen und gleichzeitig auf "Video aufzeichnen" klicken und man würde seinen eigenen 3D-Film in HTML5 erstellen. Gibt es Freiwillige, die das ausprobieren möchten?
Neon-Regenbogen, Zebra-Spiegelung – Verkettung von Effekten
Die Verkettung mehrerer Effekte in <canvas> ist ganz einfach. Es sind jedoch grundlegende Kenntnisse zu "globalCompositeOperation" (GCO) erforderlich. Wenn man die Operationen mit GIMP (oder Photoshop) vergleicht, ergibt sich Folgendes: Es gibt 12 GCOs in <canvas> "darker" und "lighter" können als Ebenen-Überblendmodi betrachtet werden, die anderen 10 Operationen werden den Ebenen als Alpha-Masken hinzugefügt, d. h. eine Ebene entfernt die Pixel der anderen Ebene. Die globalCompositeOperation verbindet auf neue und besondere Weise Ebenen oder, in unserem Fall, Code-Strings:
Die globalCompositeOperation-Grafik zeigt verwendbare GCO-Modi. Diese Grafik nutzt einen großen Teil des Farbspektrums und mehrere Stufen der Alpha-Transparenz, um detailgenaue Ergebnisse anzuzeigen. Textbeschreibungen erhalten Sie in der globalCompositeOperation-Referenz von Mozilla. Weitere Informationen zur Funktionsweise der Operation erhalten Sie unter Compositing Digital Images von Porter Duff.
Mir persönlich gefällt am besten globalCompositeOperation=”lighter”. Durch "lighter" werden die hinzugefügten Pixel vermischt. Das geschieht ähnlich, wie das Licht gemischt wird. Wenn rotes, grünes und weißes Licht mit voller Intensität strahlen, nimmt es das menschliche Auge als weißes Licht wahr. Mit dieser Funktion können Sie tolle Dinge ausprobieren, vor allem wenn <canvas> auf einen niedrigen globalAlpha-Wert festgelegt ist. So werden eine feinere Abstimmung und sanftere Ränder möglich. "lighter" kann in vielen Situationen verwendet werden. Erst kürzlich fand ich einen beeindruckenden Ersteller für Desktophintergründe in HTML5 unter http://weavesilk.com/. Eine meiner Demos, Breathing Galaxies (JS1k), verwendet ebenfalls den "lighter"-Modus. Anhand dieser beiden Beispiele sehen Sie die mit diesem Modus möglichen Effekte.
Hinweis: Nicht alle Browser unterstützen alle GCO-Modi. Dieses Problem muss noch angegangen werden. Es gibt sechs Modi, die in den Browsern Chrome, Safari, Firefox und Opera funktionieren: source-over, source-atop, destination-over, destination-out, lighter und xor. Hoffentlich wird dieses Problem für künftige Versionen behoben. Weitere Informationen erhalten Sie unter globalCompositeOperation browser handling.
Neon-Regenbogen-Jitter-Effekt
In der folgenden Demo sehen Sie, wie Sie einen Photoshop-ähnlichen Neon-Regenbogen-Schein mit einer Jitter-Umrisslinie erstellen, indem Sie Effekte verketten. Das geschieht mit der globalCompositeOperation ("source-in", "lighter" und "darker"). Diese Demo ist eine Fortsetzung der Demo "Textschatten in <canvas>" und verwendet dieselbe Vorgehensweise, um den Schatten vom Text zu trennen. Wie das geht, wurde im vorigen Abschnitt beschrieben:
Anzeigen des Neon-Regenbogen-Jitter-Effekts
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-Spiegelungs-Effekt
Der Zebra-Spiegelungs-Effekt wird in der ausgezeichneten WebDesignerWall gezeigt. Hier wird erläutert, wie Sie Ihre Seite mit CSS aufpeppen. Hier gehen wir noch einen Schritt weiter und erstellen eine Spiegelung für den Text. So etwas sehen Sie beispielsweise in iTunes. Der Effekt kombiniert "fillColor" (weiß), "createPattern" (zebra.png) und "linearGradient" (Schein). Auf diese Weise lassen sich verschiedene Fülltypen auf alle Vektorobjekte anwenden:
Anzeigen des Zebra-Spiegelungs-Effekts
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 von <canvas> finden "innere" oder "äußere" Schatten keine Beachtung. Auf den ersten Blick sieht es also so aus, als würde der "innere" Schatten nicht unterstützt. Das ist jedoch nicht der Fall. Es ist nur ein wenig knifflig, diese Option zu aktivieren. ;) Wie in einem Post von F1LT3R erst kürzlich vorgeschlagen, können Sie innere Schatten erstellen, indem Sie die besonderen Eigenschaften der Optionen zum Drehen im und gegen den Uhrzeigersinn verwenden. Dazu erstellen Sie einen inneren Schatten, indem Sie zuerst ein Container-Rechteck und dann mit gegensätzlichen Drehungen eine Ausschneideform zeichnen. So entsteht die Umkehrung der Form.
Im folgenden Beispiel kann der innere Schatten und "fillStyle" mit Farben, Farbverläufen und Mustern gleichzeitig geändert werden. Sie können eine individuelle Musterrotation angeben. Sehen Sie, dass die Zebrastreifen nun im rechten Winkel zueinander verlaufen? Eine Ausschneidemaske in der Größe des begrenzenden Felds wird verwendet. So ist kein riesiger Container erforderlich, der die Ausschneidemaske enthält. Dadurch wird die Geschwindigkeit verbessert, da unnötige Teile des Schattens nicht verarbeitet werden.
Anzeigen des Innerer Schatten-Effekts
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();
};
In diesen Beispielen sehen Sie, dass Sie mithilfe von globalCompositeOperation Effekte verketten können. So erzielen Sie anhand von Masken und Überblenden anspruchsvollere Effekte. Am Bildschirm ist alles möglich. ;)
Generative Effekte für den Abstand
Eine Bearbeitung des Unicode-Characters 0x2708 in <canvas> von
...zu diesem schattierten Beispiel
...wird erzielt durch mehrfache Aufrufe von ctx.strokeText() mit einer dünnen "lineWidth" (0,25). Dabei wird langsam "x-offset" und "alpha" verringert. So entsteht beim Vektorelement der Anschein einer Bewegung.
Durch das Zuweisen der XY-Position der Elemente zu einer Sinus-/Cosinus-Welle und durch die zyklische Verarbeitung der Farben anhand der HSL-Eigenschaft lassen sich interessantere Effekte erzielen, z. B. bei diesem "biohazard"-Beispiel.
HSL: Hue, Saturation, Lightness (Farbton, Sättigung, Helligkeit) (1978)
HSL ist ein in den CSS3-Spezifikationen neu unterstütztes Format. HEX wurde für Computer konzipiert; HSL wurde zur Lesbarkeit für den Menschen konzipiert.
Ein Beispiel für die einfache Nutzbarkeit von HSL: Soll das Farbspektrum zyklisch verarbeitet werden, wird der Farbton ("hue") von 360 inkrementiert. Der Farbton wird dem Spektrum in zylindrischer Weise zugewiesen. Die Helligkeit ("lightness") legt fest, wie dunkel/hell die Farbe ist. Dabei steht 0 % für ein schwarzes Pixel und 100 % für ein weißes Pixel. Die Sättigung ("saturation") legt die Dichte oder Intensität einer Farbe fest. Grautöne werden mit einer Sättigung von 0 % erstellt. Intensive Farben verfügen über einen Wert von 100 %.
Bei HSL handelt es sich um einen neuen Standard. Sie möchten jedoch bestimmt weiterhin ältere Browser unterstützen. Das ist über die Farbraumumwandlung möglich. Der folgende Code akzeptiert ein HSL-Objekt { H: 360, S: 100, L: 100} und erstellt ein RGB-Objekt { R: 255, G: 255, B: 255 }. Nun können Sie Ihren RGB- oder RGBA-String anhand dieser Werte erstellen. Weitere Informationen erhalten 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
Bisher standen zum Erstellen von Animationen in JavaScript zwei Möglichkeiten zur Auswahl: setTimeout und setInterval.
window.requestAnimationFrame ist hier der neue Standard, um beide zu ersetzen. Sie sparen Strom und schonen Ihren Computer, da der Browser die Animationen je nach den verfügbaren Ressourcen regulieren kann. Einige wichtige Funktionen lauten:
- Wenn ein Nutzer den Frame verlässt, wird die Animation eventuell langsamer oder stoppt, damit keine unnötigen Ressourcen verbraucht werden.
- Es gibt eine Obergrenze von 60 fps für die Frame-Rate. Der Grund hierfür ist, dass diese Frame-Rates sowieso außerhalb der menschlichen Wahrnehmung liegen. Die meisten Menschen nehmen eine Animation mit 30 fps als "flüssig" wahr.
Zurzeit sind für die Verwendung von requestAnimationFrame Anbieter-spezifische Präfixe erforderlich. Paul Irish erstellte eine Shim-Ebene, die mehrere Anbieter unterstützt: requestAnimationFrame for smart animating:
// 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 man diese Idee noch weiterentwickelt, könnte man dies mit einem "poly-fill" kombinieren, wie mit requestAnimationFrame.js. Hierfür müssten ein paar Funktionen entwickeln werden. Dann könnten ältere Browser beim Wechsel zu diesem neuen Standard weiterhin unterstützt werden.
Das folgende Beispiel zeigt, wie Sie eine Animation erstellen und tausende von Aufrufen an "strokeText" über ein "low alpha" ohne negative Auswirkungen auf den Browser senden. Die Steuerelemente sind ein wenig seltsam, aber die Ergebnisse sind wirklich cool:
Anzeigen des Abstand-Effekts
(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);
})();
Quellcode
Durch die Unterstützung mehrerer Browser bleiben bezüglich der künftigen Entwicklung von <canvas> keine Fragen mehr offen. Für iPhone/Android/Desktop ist eine Portierung mithilfe von PhoneGap oder Titanium möglich.
Der Quellcode befindet sich in CanvasTextEffects.zip