canvas のタイポグラフィ効果

HTML5 Rocks

経緯

私が <canvas> を意識するようになったのは、2006 年に Firefox v2.0 がリリースされたときからです。Ajaxian に関するある記事(変換行列について書かれた記事でした)に触発されて、初めての <canvas> web-app である Color Sphere(2007 年)を作成しました。それから色の世界とグラフィック プリミティブに没頭するようになり、Sketchpad(2007~2008 年)の作成へと駆り立てられました。この作品では、「ペイントよりも優れた」アプリケーションをブラウザに統合することを目指しました。

これらの実験は最終的に、長年の友人である Charles Pritchard との共作による最初の Mugtug の作成につながりました。今は HTML5 <canvas> で Darkroom を開発中です。Darkroom は、ピクセル ベースのフィルタにベクター ベースのタイポグラフィとドローイングを組み合わせた、非破壊的な写真共有アプリケーションです。

はじめに

pastedGraphic.png

<canvas> を使用すると、JavaScript プログラマは画面の色、ベクター、ピクセル(つまりモニタの視覚的な構成)を全面的にコントロールできます。

この後は、<canvas> であまり注目されてこなかった領域である「テキスト効果」の作成に関する例を示します。<canvas> で作成できるさまざまなテキスト効果は想像を超えるほど広い範囲にわたります。このデモでは、できることの一部を取り上げます。この記事では「テキスト」を扱っていますが、同じ方法を任意のベクター オブジェクトに適用し、ゲームや他のアプリケーションに魅力的なビジュアル効果を作成することができます。

<canvas> でのテキスト シャドウ
<canvas> における、CSS ライクなテキスト効果です。クリッピング マスクを作成し、<canvas> でメトリックを確認して、シャドウ プロパティを使用します。
虹色ネオン、ゼブラ柄の反転—連結効果
<canvas> での Photoshop ライクなテキスト効果です。globalCompositeOperation、createLinearGradient、createPattern を使用した例です。 
<canvas> での内側のシャドウと外側のシャドウ
「ほとんど知られていない機能」を明らかにします。時計回りと反時計回りの回転を使用して、逆向きのドロップ シャドウ(内側のシャドウ)を作成します。
残像—ジェネレーティブ効果
<canvas> でのジェネレーティブ ベースのテキスト効果です。hsl() の色切り替えと window.requestAnimationFrame を使用して、動いている感じを作成します。

canvas でのテキスト シャドウ

CSS3 で(border-radius や web-gradients などとともに)私のお気に入りの追加仕様の 1 つは、シャドウを作成できるようになったことです。CSS と <canvas> のシャドウの違いを認識することが重要です。特に次のような点です。

CSS は 2 つの方法を使います。div、span などのボックス要素に使用する box-shadow と、テキスト コンテンツに使用する text-shadow です。

<canvas> のシャドウは 1 種類です。それをすべてのベクター オブジェクトに使用します(ctx.moveTo、ctx.lineTo、ctx.bezierCurveTo、ctx.quadradicCurveTo、ctx.arc、ctx.rect、ctx.fillText、ctx.strokeText など)。<canvas> でシャドウを作成するには、次の 4 つのプロパティを使用します。

ctx.shadowColor = “red” // 文字列
シャドウの色。有効な入力は RGB、RGBA、HSL、HEX などです。
ctx.shadowOffsetX = 0; // 整数
テキストを基準にしたシャドウの水平方向の距離。
ctx.shadowOffsetY = 0; // 整数
テキストを基準にしたシャドウの垂直方向の距離。
ctx.shadowBlur = 10; // 整数
シャドウに適用するぼかし効果。値が大きいほど、ぼかしが強くなります。

はじめに、<canvas> で CSS 効果をどのようにエミュレートできるかを見てみましょう。Google の画像検索で「css text-shadow」を検索すると、私たちがエミュレートできる優れたデモがいくつか見つかります。Line25Stereoscopic、それに Shadow 3D です。

pastedGraphic_1.png

立体 3D 効果(詳しくは こちらのアナグリフ画像をご覧ください)は、簡単なコード行から優れた効果を生成する例です。次の CSS コードを使用すると、(3D の映画を見るときのような)3D 赤青メガネで見たときに、奥行の錯覚を作り出すことができます。

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

この文字列を <canvas> に変換するときの注意点が 2 つあります。

(1) シャドウのぼかし(3 つ目の値)はないので、実際にシャドウを実行する必要はありません。fillText で同じ結果が作成されます。

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) EM は <canvas> でサポートされていないので、PX に変換する必要があります。PT、PC、EM、EX、PX などの間の変換率を確認するには、同じフォント プロパティを持つ要素を DOM で作成し、幅を測定対象の形式に設定します。または、EM -> PX 変換をキャプチャするには、“height: 1em” の DOM 要素を測定すると、得られる offsetHeight が各 EM に含まれる PX の数になります。

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;

アルファ マルチプリケーションの防止

行 25 のネオン効果のようなもう少し複雑な例では、効果を適切にエミュレートするために shadowBlur プロパティを使用する必要があります。ネオン効果には複数のシャドウが必要ですが、ここで問題に行き当たります。<canvas> では各ベクター オブジェクトに 1 つのシャドウしか設定できないということです。そのため、複数のシャドウを描くためには、複数のバージョンのテキストを重ねて描く必要があります。この結果、アルファ マルチプリケーションが生じ、最終的にエッジがギザギザになります。

pastedGraphic_2.png

シャドウを表示するときに ctx.fillStyle = “rgba(0,0,0,0)”“transparent” を実行してテキストを隠すことを試してみましたが、この試みは役に立ちませんでした。シャドウは fillStyle アルファのマルチプリケーションであるため、シャドウが fillStyle よりも不透明になることはあり得ません。

幸い、これを回避する方法があります。テキストからのオフセット位置に、シャドウを離して(つまり、重ならないように)描くことで、画面の外にテキストを隠すことができます。

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

テキスト ブロック周囲のクリッピング

もう少しすっきりさせるために、クリッピング パスを追加することで、最初から fillText を描かないようにする(シャドウは描かれるようにしながら)ことができます。テキストを囲むクリッピング パスを作成するには、テキストの高さ(昔から印刷機で使っていた英字「M」の高さであることから「em-height」と呼ばれる)とテキストの幅を知る必要があります。幅は ctx.measureText().width を使用して取得できるのですが、ctx.measureText().height は存在しません。

これも幸いなことに、CSS ハックの手法(CSS での測定を使用して古い実装の <canvas> を修正する他の方法については、Typographic Metrics(タイポグラフィック メトリック)をご覧ください)を使用して、フォント プロパティが同じ <span> の offsetHeight を測定することで、テキストの高さがわかります。

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

そこからクリッピング パスとして使用する矩形を作成し、これによって「shadow」を囲んでダミーの図形を削除します。

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

最適化しながら、すべてを合わせて試します。シャドウにぼかしを使わない場合は、fillText を使用して同じ効果を得ることができるので、クリッピング マスクの設定を省略できます。

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

これらの <canvas> コマンドすべてを手作業で入力せずに済むように、簡単なテキスト シャドウ パーサーをデモのソースに含めてありますので、パーサーに CSS コマンドを読み込み、<canvas> コマンドを生成することができます。これで、<canvas> 要素に使用するスタイルが全部そろいました。これらと同じシャドウ効果を他の任意のベクター オブジェクトに使用することができます(WebFonts や、SVG からインポートした複雑な図形、さらにはジェネレーティブ ベクター図形など)。

<canvas> でのテキスト シャドウ効果を表示します。

pastedGraphic_3.png

余談(pixel-pushing について)

記事のこのセクションを書きながら、立体画像の例に興味を引かれました。<canvas> と、パースペクティブを少しだけずらして撮影した 2 つの画像を使用して 3D 映画の画面を作成するのは、どれだけ難しいのでしょうか。非常に難しいというほどでないことは確かです。次のカーネルは、最初の画像の赤のチャンネル(data)と 2 番目の画像のシアンのチャンネル(data2)を結合します。

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

3D メガネ(シアン/マゼンタ)で拡張される画像を作成する方法については、立体画像のデモをご覧ください。これで、2 台の iPhone を額にテープで貼り付け、2 台同時に「ビデオ録画」をタップするだけで、HTML5 による 3D 映画を制作できます。どなたかボランティアはいませんか?

pastedGraphic_5.png
3D メガネ

虹色ネオン、ゼブラ柄の反転—連結効果

<canvas> で複数の効果を連結することは簡単ですが、globalCompositeOperation(GCO)の基本知識が必要です。GIMP(または Photoshop)との処理の比較: <canvas> には 12 個の GCO があります。「darker」と「lighter」はレイヤーのブレンド モードと考えることができます。その他の 10 個の処理は、アルファ マスクとしてレイヤーに適用されます(1 つのレイヤーが他のレイヤーのピクセルを除去します)。globalCompositeOperation は「レイヤー」(または、ここではコードの文字列)どうしを結合し、新しく興味深い方法で合成します。

pastedGraphic_6.png

globalCompositeOperation チャートは、実際の GCO モードを示しています。このチャートは、使用可能なものを詳しく見るために、色のスペクトルの大部分とアルファ透過性の複数のレベルを使用しています。テキストの説明については、Mozilla の globalCompositeOperation リファレンスを参照することをおすすめします。さらに詳しく調べる場合は、Porter Duff の Compositing Digital Images(デジタル画像の作成)で、処理がどのように行われるかを確認できます。

私のお気に入りのモードは globalCompositeOperation=”lighter” です。lighter は、光の混合と同じように、追加されたピクセルを混合します。赤、緑、白の光が最大強度のとき、白の光が見えます。いろいろ試してみると面白い機能です。特に、<canvas> を低い globalAlpha に設定すると、より細かい制御が可能になり、滑らかなエッジになります。lighter には数多くの使い方があります。私の最近のお気に入りは、HTML5 のデスクトップ背景を作成するもので、http://weavesilk.com/ にあります。私のデモの 1 つ、Breathing Galaxies(息づく宇宙)(JS1k)でも、lighter モードを使用しています。これら 2 つの例からパターンを描画すると、このモードで生成される効果がわかります。

注: GCO モードの一部は、すべてのブラウザで共通してサポートされる段階に至っておらず、対応が望まれます。Chrome、Safari、Firefox、Opera のすべてのブラウザで共通して動作するモードは 6 つあります: source-over、source-atop、destination-over、destination-out、lighter、xor です。順調に進めば、今後のリリースで対処される予定です。詳細については、globalCompositeOperation のブラウザでの扱いに関して、こちらをご覧ください。

虹色ネオンのジッター効果

次のデモでは、ジッターを適用したアウトラインを使用して Photoshop ライクな虹色ネオンの輝きを作成します。globalCompositeOperation(source-in、lighter、darker)を使用して効果を連結しています。このデモは「<canvas> でのテキスト シャドウ」のデモを進化させたもので、シャドウをテキストから離すために同じ戦略を使用しています(前のセクションをご覧ください)。

pastedGraphic_7.png

虹色ネオンのジッター効果を表示します。

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

ゼブラ柄の反転効果

ゼブラ柄の反転効果は、CSS を使用してページに工夫を加える方法に関する、WebDesignerWall にあるすばらしいリソースに触発されています。アイディアをもう少し広げて、iTune に表示されるようなテキストの反転を作成します。この効果では、fillColor(白)、createPattern(zebra.png)、linearGradient(shine)を組み合わせています。これは、複数の塗りつぶしタイプを各ベクター オブジェクトに適用できることを示しています。

pastedGraphic_8.png

ゼブラ柄の反転効果を表示します。

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

canvas での内側/外側のシャドウ

<canvas> の仕様では「内側」と「外側」のシャドウの問題について触れていません。実際、一見しただけでは「内側」のシャドウはサポートされていないと考えがちです。ところがそうではありません。実現するのは少し面倒ですが、F1LT3R からの最近の投稿に示されているように、時計回りと反時計回りの回転ルールというユニークなプロパティを使用して、内側のシャドウを作成することができます。「内側」のシャドウを作成するには、コンテナ矩形を描画し、次に逆向きの回転ルールを使用して切り出し図形を描画することで、反転図形を作成します。

次の例は、内側のシャドウと fillStyle に色とグラデーションとパターンを同時に設定しています。パターンの回転を個別に指定することもできます。ゼブラ柄の縞が互いに垂直になっていることに注目してください。境界ボックスのサイズのクリッピング マスクを使用することで、切り出し図形を囲む特大コンテナが不要になり、シャドウの不必要な部分を処理せずに済むため、処理速度が上がります。

pastedGraphic_9.png

内側のシャドウ効果を表示してみます。

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

以上の例から、globalCompositeOperation を使用して効果を連結し、より複雑な効果を(マスクとブレンドを利用して)作成できることがわかります。画面を思いのままにできます。

残像—ジェネレーティブ効果

始まりは <canvas> での unicode 文字 0x2708 です。

pastedGraphic_10.png

これが次のようなシェーディング付きの例になります。

pastedGraphic_11.png

これを実現するには、x のオフセットとアルファを徐々に小さくしながら、ctx.strokeText() に狭い lineWidth(0.25)を指定して何回か呼び出します。これにより、ベクター要素に動いている感じを与えることができます。

要素の XY 位置をサイン/コサイン曲線にマッピングし、HSL プロパティを使用して色を順に切り替えることにより、「バイオハザード」の例のような、もっと面白い効果を作成できます。

pastedGraphic_12.png

HSL: Hue, Saturation, Lightness(色相、彩度、輝度)(1978 年)

HSL は、CSS3 仕様で新しくサポートされた形式です。HEX がコンピュータ用に設計されたのに対して、HSL は人間が読めるように設計されています。

HSL は簡単です。色のスペクトルを順に切り替えるには、「hue」(色相)を 360 から増やしていきます。色相は円柱の形式でスペクトルにマップされます。輝度は色の暗さまたは明るさをコントロールします。0% は黒いピクセルを示し、100% は白いピクセルを示します。彩度は、色の鮮やかさの度合いです。灰色は彩度 0% で作成され、鮮やかな色は 100% の値を使用して作成されます。

pastedGraphic_13.png

HSL は比較的新しい規格なので、従来のブラウザを引き続きサポートする必要がある場合は色空間の変換によってサポートします。次のコードは HSL オブジェクト { H: 360, S: 100, L: 100} を受け取り、RGB オブジェクト { R: 255, G: 255, B: 255 } を出力します。その後、これらの値を使用することで独自に rgb または rgba 文字列を作成できます。詳細については、Wikipedia で HSL に関して深く考察している次の記事をご覧ください: 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
  };
};

requestAnimationFrame によるアニメーションの作成

過去においては、JavaScript でアニメーションを作成する場合、setTimeoutsetInterval という 2 つの選択肢がありました。

window.requestAnimationFrame は、この両方を置き換える新しい規格です。この処理では、ブラウザは使用可能なリソースに基づいてアニメーションを制御できるため、コンピュータの処理サイクルがいくつか減り、世界的な節電に役立ちます。重要な機能として次のものがあります。

  • ユーザーがフレームを終了すると、アニメーションは低速になるか完全に停止するので、不必要なリソースを使わずに済みます。
  • フレーム レートには 60FPS という上限があります。この値になった理由は、人間が知覚できるレベルよりもはるかに上であるためです(ほとんどの人は最大でも 30FPS でアニメーションが「流れている」ように感じます)。

この記事の執筆時点では、requestAnimationFrame を使用するにはベンダー固有のプレフィックスが必要です。Paul Irish は、requestAnimationFrame for smart animating(スマート アニメーションのための requestAnimationFrame)で、ベンダー間サポートを備えたシム レイヤーを作成しています。

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

それをもう少し広げて、意欲的なユーザーは、requestAnimationFrame.js のような poly-fill と組み合わせることもできます(いくつかの機能では作業が必要です)。そうすることで、この新しい規格に切り替わるまでの間、さらに広い範囲で古いブラウザをサポートすることができます。

次の例は、アニメーションを作成し、ブラウザを停止させずに低アルファを使用して strokeText への多数の呼び出しを送信する方法を示しています。コントロールは少し変則的ですが、すばらしい結果が得られます。

残像効果を表示します。

(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);
})();
pastedGraphic_14.png
pastedGraphic_16.png
pastedGraphic_17.png

ソースコード

ブラウザ ベンダー界全体からのサポートによって、<canvas> の未来に疑問はありません。PhoneGap、または Titanium を使用することで、iPhone、Android、デスクトップに移植できます。

ソースコードは CanvasTextEffects.zip にあります。

Comments

0