プレゼンターの自己紹介
<canvas> の存在を私が知ったのは、Firefox v2.0 がリリースされた 2006 年のことです。Ajaxian の記事で変換マトリックスについて知り、最初の <canvas> ウェブアプリである Color Sphere(2007 年)を作成しました。この経験から、色とグラフィック プリミティブの世界に没頭し、ブラウザで「ペイントよりも優れた」アプリケーションを構築しようと、Sketchpad(2007 ~ 2008 年)の作成に着手しました。
これらのテストは、最終的に、長年の友人である Charles Pritchard とのスタートアップ Mugtug の創設につながりました。Darkroom は HTML5 <canvas> で開発中です。Darkroom は、ピクセルベースのフィルタとベクターベースのタイポグラフィおよび描画を組み合わせた、非破壊的な写真共有アプリです。
はじめに
<canvas> を使用すると、JavaScript プログラマーは画面上の色、ベクトル、ピクセル(モニターの視覚的な構成要素)を完全に制御できます。
次の例では、<canvas> であまり注目されていない領域(テキスト効果の作成)を扱います。<canvas> で作成できるテキスト効果の種類は、想像できる限り多岐にわたります。これらのデモでは、可能なことの一部を紹介しています。この記事では「テキスト」を扱っていますが、この方法は任意のベクター オブジェクトに適用できます。ゲームやその他のアプリケーションで魅力的なビジュアルを作成できます。
<canvas>のテキストシャドウ。<canvas>での CSS のようなテキスト効果、クリッピング マスクの作成、<canvas>での指標の確認、シャドウ プロパティの使用。- ネオンレインボー、ゼブラ反射 - チェーン効果。
- Photoshop のようなテキスト効果を
<canvas>で実現する、globalCompositeOperation、createLinearGradient、createPattern の使用例。 <canvas>の内側と外側のシャドウ- あまり知られていない機能を公開。時計回りと反時計回りの巻き取りを使用して、ドロップ シャドウの逆(インナー シャドウ)を作成します。
- 宇宙時代 - 生成エフェクト。
<canvas>で生成ベースのテキスト効果。hsl() カラーサイクリングとwindow.requestAnimationFrameを使用して、動きの感覚を作り出します。
キャンバスのテキスト シャドウ
CSS3 仕様(border-radius、web-gradients など)に追加された機能の中で、私が特に気に入っているのが、CSS と <canvas> のシャドウの違いを理解することが重要です。具体的には次のとおりです。
CSS では、div、span などのボックス要素には box-shadow、テキスト コンテンツには text-shadow という 2 つのメソッドを使用します。
<canvas> には 1 種類のシャドウがあり、すべてのベクター オブジェクト(ctx.moveTo、ctx.lineTo、ctx.bezierCurveTo、ctx.quadradicCurveTo、ctx.arc、ctx.rect、ctx.fillText、ctx.strokeText など)に使用されます。<canvas> でシャドウを作成するには、次の 4 つのプロパティを使用します。
- ctx.shadowColor = "red" // string
- 影の色。RGB、RGBA、HSL、HEX などの入力が有効です。
- ctx.shadowOffsetX = 0; // 整数
- テキストに対するシャドウの水平方向の距離。
- ctx.shadowOffsetY = 0; // 整数
- テキストに対するシャドウの垂直方向の距離。
- ctx.shadowBlur = 10; // 整数
- 影のぼかし効果。値が大きいほど、ぼかしが大きくなります。
まず、<canvas> で CSS 効果をエミュレートする方法を見てみましょう。Google 画像検索で「css text-shadow」を検索すると、参考になる優れたデモがいくつか見つかりました。Line25、Stereoscopic、Shadow 3D です。
ステレオスコープ 3D 効果(詳しくは、アナグリフ画像をご覧ください)は、シンプルなコード行を有効に活用した例です。次の CSS 行を使用すると、3D 赤 / シアンメガネ(3D 映画で提供されるもの)で見たときに奥行きがあるように見せることができます。
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
この文字列を <canvas> に変換する際に注意すべき点が 2 つあります。
- shadow-blur(3 番目の値)がないため、fillText で同じ結果が得られるため、実際に shadow を実行する理由はありません。
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>
- EM は
<canvas>でサポートされていないため、PX に変換する必要があります。PT、PC、EM、EX、PX などの間の変換率を求めるには、DOM で同じフォント プロパティを持つ要素を作成し、測定する形式に幅を設定します。たとえば、EM から PX への変換をキャプチャするには、DOM 要素を「height: 1em」で測定します。結果の 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;</pre>
アルファ乗算の防止
Line25 にあるネオン効果のような、より複雑な例では、効果を適切にエミュレートするために shadowBlur プロパティを使用する必要があります。ネオン効果は複数のシャドウに依存しているため、問題が発生します。<canvas> では、各ベクター オブジェクトに 1 つのシャドウしか設定できません。そのため、複数のシャドウを描画するには、テキストの複数のバージョンを重ねて描画する必要があります。これにより、アルファ乗算が発生し、最終的にエッジがギザギザになります。
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 が最初に描画されないようにします(シャドウは描画できるようにします)。テキストを囲むクリッピング パスを作成するには、テキストの高さ(「em-height」と呼ばれ、歴史的には印刷機の文字「M」の高さ)とテキストの幅を知る必要があります。ctx.measureText().width を使用して幅を取得できますが、ctx.measureText().height は存在しません。
幸いなことに、CSS のハック(CSS 測定を使用して <canvas> の古い実装を修正する方法については、タイポグラフィ指標をご覧ください)により、同じフォント プロパティを持つ <span> の offsetHeight を測定することで、テキストの高さを確認できます。
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
そこから、クリッピング パスとして使用する長方形を作成し、ダミーの形状を削除しながら「影」を囲みます。
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> コマンドをすべて手動で入力するのは大変なので、デモのソースには簡単な text-shadow パーサーが含まれています。これを使用すると、CSS コマンドを入力して <canvas> コマンドを生成できます。これで、<canvas> 要素にさまざまなスタイルを関連付けることができるようになりました。
これらの同じシャドウ効果は、Web フォントから SVG からインポートされた複雑なシェイプ、生成ベクター シェイプなど、あらゆるベクター オブジェクトで使用できます。
休憩(ピクセル プッシュに関する余談)
この記事のこのセクションを執筆するにあたり、ステレオスコープの例に興味をそそられました。<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;
今や、2 台の iPhone をダクトテープで額に貼り付け、同時に [動画を録画] をクリックするだけで、HTML5 で独自の 3D 映画を作成できます。誰か協力してくれますか?
ネオンレインボー、ゼブラ反射 - エフェクトのチェーン
<canvas> で複数のエフェクトを連結するのは簡単ですが、globalCompositeOperation(GCO)に関する基本的な知識が必要です。GIMP(または Photoshop)のオペレーションと比較すると、<canvas> には 12 個の GCO があります。darker と lighter はレイヤのブレンドモードと考えることができます。他の 10 個のオペレーションは、アルファマスクとしてレイヤに適用されます(1 つのレイヤが他のレイヤのピクセルを削除します)。globalCompositeOperation は、「レイヤ」(この場合はコードの文字列)を結合し、新しい方法で組み合わせます。
globalCompositeOperation チャートは、動作中の GCO モードを示します。このチャートでは、期待される結果を詳細に確認できるように、色スペクトルの大部分と複数のレベルのアルファ透明度が使用されています。テキストによる説明については、Mozilla の globalCompositeOperation リファレンスをご覧ください。さらに詳しく知りたい場合は、Porter Duff の Compositing Digital Images でオペレーションの仕組みをご覧ください。
私のお気に入りのモードは globalCompositeOperation="lighter" です。Lighter は、光が混ざるのと同様に、追加されたピクセルを混ぜます。赤、緑、白の光が最大強度になると、白色光が見えます。これは、特に <canvas> が低い globalAlpha に設定されている場合に、より細かな制御と滑らかなエッジを可能にする、試してみる価値のある機能です。Lighter はさまざまな用途に使用されています。最近のお気に入りは、http://weavesilk.com/ にある HTML5 デスクトップ背景作成ツールです。私のデモの一つである Breathing Galaxies(JS1k)でも、lighter モードを使用しています。この 2 つの例からパターンを描画すると、このモードがどのような効果をもたらすかがわかります。
globalCompositeOperation のブラウザ処理。
ネオンレインボーのジッター効果
次のデモでは、globalCompositeOperation(source-in、lighter、darker)を使用してエフェクトを連結することで、ジッターのあるアウトラインを持つ Photoshop のようなネオン レインボー グローを実現します。このデモは、「<canvas> のテキストシャドウ」デモの発展形で、テキストからシャドウを分離する同じ戦略を使用しています(前のセクションを参照)。
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 の優れたリソースから着想を得たものです。この例では、iTunes で表示されるようなテキストの「反射」を作成することで、アイデアをさらに発展させています。このエフェクトは、fillColor(白)、createPattern(zebra.png)、linearGradient(shine)を組み合わせたものです。これは、各ベクター オブジェクトに複数の塗りつぶしタイプを適用できることを示しています。
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> の仕様では、「内側」と「外側」のシャドウについては触れられていません。実際、一見すると、「内側」のシャドウはサポートされていないように思われるかもしれません。これは誤りです。
有効にするのが少し難しいだけです。F1LT3R の最近の投稿で提案されているように、時計回りと反時計回りのワインディング ルールの独自のプロパティを使用して、内側のシャドウを作成できます。これを行うには、コンテナの長方形を描画して「内側の影」を作成し、反対の巻き方向ルールを使用して切り抜き形状を描画します。これにより、形状の反転が作成されます。
次の例では、inner-shadow と fillStyle を色、グラデーション、パターンで同時にスタイル設定できます。パターンの回転を個別に指定できます。ゼブラ ストライプが互いに垂直になっていることに注目してください。境界ボックスのサイズのクリッピング マスクを使用することで、切り抜き形状を囲むための非常に大きなコンテナが不要になります。これにより、影の不要な部分が処理されなくなり、速度が向上します。
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 から
…この網掛けされた例へ:
…は、細い lineWidth(0.25)で ctx.strokeText() を複数回呼び出し、x オフセットとアルファを徐々に減らすことで実現できます。これにより、ベクター要素に動きが加わります。
要素の XY 位置をサイン / コサイン波にマッピングし、HSL プロパティを使用して色を循環させることで、この「バイオハザード」の例のように、より興味深い効果を作成できます。
HSL: 色相、彩度、明度(1978 年)
HSL は、CSS3 仕様で新たにサポートされた形式です。HEX はコンピュータ向けに設計されていますが、HSL は人間が読めるように設計されています。
HSL の使いやすさを示すために、色スペクトルを循環させるには、360 から「色相」を増分するだけです。色相は円柱状にスペクトルにマッピングされます。明度は、色の暗さ / 明るさを制御します。0% は黒いピクセルを示し、100% は白いピクセルを示します。彩度は、色の明るさや鮮やかさを制御します。グレーは彩度 0% で作成され、鮮やかな色は 100% の値を使用して作成されます。
HSL は比較的新しい標準であるため、古いブラウザを引き続きサポートしたい場合は、色空間の変換によって実現できます。次のコードは、HSL オブジェクト { H: 360, S: 100, L: 100} を受け取り、RGB オブジェクト { R: 255, G: 255, B: 255 } を出力します。これらの値を使用して、rgb または rgba 文字列を作成できます。詳細については、HSL に関する Wikipedia の記事をご覧ください。
// 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 でアニメーションを作成するには、setTimeout と setInterval の 2 つの選択肢がありました。
window.requestAnimationFrame は、両方に代わる新しい標準です。ブラウザが利用可能なリソースに基づいてアニメーションを調整できるようにすることで、世界の電力を節約します(また、コンピュータのハートビートを数回節約します)。主な機能は次のとおりです。
- ユーザーがフレームから離れたときに、アニメーションを遅くしたり、完全に停止したりして、不要なリソースの使用を防ぐことができます。
- フレームレートの上限は 60 FPS です。これは、人間が認識できるレベルをはるかに超えているためです(ほとんどの人間は 30 FPS でアニメーションが「滑らか」であると認識します)。
この記事の執筆時点では、requestAnimationFrame を使用するにはベンダー固有の接頭辞が必要です。Paul Irish は、requestAnimationFrame for smart animating で、クロスベンダーをサポートする shim レイヤを作成しました。
// 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 などのポリフィル(いくつかの機能が開発中)と組み合わせて、この新しい標準に切り替えながら、古いブラウザをさらにサポートするかもしれません。
(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);
})();
ソースコード
ブラウザ ベンダー全体からのサポートにより、<canvas> の将来性に疑問の余地はありません。PhoneGap を使用して iPhone / Android / デスクトップの実行可能ファイルに移植することも、