在畫布中的打字效果

Michael Deal
Michael Deal

我的背景

2006 年 Firefox 2.0 版推出時,我才注意到<canvas>。Ajaxian 上有一篇介紹轉換矩陣的文章,啟發我建立第一個 <canvas> 網頁應用程式:Color Sphere (2007)。這讓我沉浸在色彩和圖形基元的世界中,並激發我創作 Sketchpad (2007-2008),努力在瀏覽器中打造「比小畫家更好」的應用程式。 這些實驗最終促成我和老友 Charles Pritchard 創立 Mugtug。Darkroom 是我們正在開發的 HTML5 <canvas>。Darkroom 是一款非破壞性的相片分享應用程式,結合了像素濾鏡、向量字體排版和繪圖功能。

簡介

Canvas 橫幅圖片。

<canvas> 讓 JavaScript 程式設計師能完全控制螢幕上的顏色、向量和像素,也就是螢幕的視覺構成。

以下範例將說明 <canvas> 中一個較少人注意的領域:建立文字效果。在 <canvas> 中,您可以盡情發揮想像力,創造出各式各樣的文字效果,而這些示範只是其中一部分。雖然本文討論的是「文字」,但這些方法也適用於任何向量物件,可在遊戲和其他應用程式中建立精彩的視覺效果:

<canvas> 中的文字陰影。
類似 CSS 的文字效果,包括在 <canvas> 中建立剪輯遮罩、在 <canvas> 中尋找指標,以及使用陰影屬性。
霓虹彩虹、斑馬紋反射 - 鏈結效果。
Photoshop-like text-effects in <canvas> examples of using globalCompositeOperation, createLinearGradient, createPattern.
<canvas>中的內陰影和外陰影
揭露鮮為人知的功能:使用順時針和逆時針繞線,建立與投射陰影相反的效果 (內陰影)。
太空時代 - 生成效果。
生成式文字效果,在 <canvas> 中使用 hsl() 色彩循環和 window.requestAnimationFrame 營造動態感。

畫布中的文字陰影

我最喜歡的 CSS3 規格新增功能之一 (以及 border-radius、web-gradients 等),就是建立陰影的功能。請務必瞭解 CSS 和 <canvas> 陰影之間的差異,具體如下:

CSS 使用兩種方法:box-shadow 用於方塊元素,例如 div、span 等;text-shadow 用於文字內容。

<canvas> 只有一種陰影,適用於所有向量物件:ctx.moveTo、ctx.lineTo、ctx.bezierCurveTo、ctx.quadradicCurveTo、ctx.arc、ctx.rect、ctx.fillText、ctx.strokeText 等。如要在 <canvas> 中建立陰影,請使用下列四個屬性:

ctx.shadowColor = "red" // string
陰影顏色;可輸入 RGB、RGBA、HSL、HEX 等有效值。
ctx.shadowOffsetX = 0; // integer
陰影與文字之間的水平距離。
ctx.shadowOffsetY = 0; // integer
陰影與文字之間的垂直距離。
ctx.shadowBlur = 10; // integer
陰影的模糊效果,值越大,模糊程度越高。

首先,我們來看看 <canvas> 如何模擬 CSS 效果。在 Google 圖片中搜尋「css text-shadow」後,我們找到幾個很棒的示範,可以做為參考:Line25、Stereoscopic 和 Shadow 3D。

CSS 3D 圖像

立體 3D 效果 (詳情請參閱浮雕圖片) 是簡單程式碼行的一大應用範例。只要使用下列 CSS 程式碼,就能在戴上 3D 紅藍眼鏡 (3D 電影院提供的眼鏡) 時,營造出深度錯覺:

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

將這個字串轉換為 <canvas> 時,請注意以下兩點:

  1. 由於沒有陰影模糊 (第三個值),因此實際上沒有理由執行陰影,因為 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);</pre>
  1. <canvas> 不支援 EM,因此必須轉換為 PX。我們可以在 DOM 中建立具有相同字型屬性的元素,並將寬度設為要測量的格式,藉此找出 PT、PC、EM、EX、PX 等格式之間的轉換比例。舉例來說,如要擷取 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;</pre>

防止 Alpha 乘法

在更複雜的範例中,例如 Line25 上的霓虹效果,必須使用 shadowBlur 屬性才能正確模擬效果。由於霓虹效果需要多個陰影,因此我們遇到一個問題:在 <canvas> 中,每個向量物件只能有一個陰影。因此,如要繪製多個陰影,您必須在文字上方繪製多個版本的文字。這會導致 Alpha 乘法,最終產生鋸齒狀邊緣。

霓虹圖案

我嘗試執行 ctx.fillStyle = "rgba(0,0,0,0)" 或 "transparent" 來隱藏文字,同時顯示陰影…但這個嘗試徒勞無功;因為陰影是 fillStyle alpha 的乘積,所以陰影的不透明度永遠不會超過 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> 指令,因此我在示範來源中加入了一個簡單的文字陰影剖析器;這樣一來,您就可以為其提供 CSS 指令,並讓它產生 <canvas> 指令。現在,<canvas> 元素可以連結多種樣式。這些陰影效果可用於任何向量物件,包括 WebFonts、從 SVG 匯入的複雜形狀、生成式向量形狀等。

畫布效果中的文字陰影

中場休息 (像素推送)

撰寫本文的這個部分時,我對立體範例感到好奇。如果使用 <canvas> 和兩張從稍微不同角度拍攝的圖片,要建立 3D 電影螢幕效果有多難?顯然不會太難。下列核心會將第一張圖片 (data) 的紅色色版與第二張圖片 (data2) 的青色色版結合:

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

現在,只要有人用膠帶把兩支 iPhone 貼在額頭上,同時點選「錄製影片」,我們就能用 HTML5 製作自己的 3D 電影。有人自願嗎?

3D 眼鏡

霓虹彩虹、斑馬反射:鏈結效果

在 <canvas> 中鏈結多個效果可能很簡單,但需要具備 globalCompositeOperation (GCO) 的基本知識。如要比較 GIMP (或 Photoshop) 的作業:<canvas> 中有 12 個 GCO,darker 和 lighter 可視為圖層混合模式;其他 10 項作業會以 Alpha 遮罩的形式套用至圖層 (一個圖層會移除另一個圖層的像素)。globalCompositeOperation 會將「圖層」(在我們的例子中是程式碼字串) 綁在一起,以新穎有趣的方式組合:

鏈結效果圖

globalCompositeOperation 圖表會顯示運作中的 GCO 模式;這張圖表使用大部分的色譜和多個 Alpha 透明度等級,以便詳細查看預期結果。建議您查看 Mozilla 的 globalCompositeOperation 參考資料,取得文字說明。如要進一步研究,請參閱 Porter Duff 的數位圖片合成,瞭解作業的運作方式。

我最喜歡的模式是 globalCompositeOperation="lighter"。「變亮」會混合附加的像素,類似光線混合的方式;當紅、綠和白光達到全強度時,我們會看到白光。這項功能非常有趣,尤其是將 <canvas> 設為較低的 globalAlpha 時,可以更精細地控制,並使邊緣更平滑。Lighter 有許多用途,我最近最喜歡的是在 http://weavesilk.com/ 找到的 HTML5 桌面背景建立工具。我的其中一個示範 Breathing Galaxies (JS1k) 也使用較淺的模式,從這兩個範例中繪製圖案,您開始看到這個模式產生的效果。

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

斑馬反射效果

斑馬紋反射效果的靈感來自 WebDesignerWall 的優秀資源,介紹如何使用 CSS 讓網頁更生動。這會進一步延伸這個概念,為文字建立「反射」效果,例如您在 iTunes 中看到的內容。這個效果結合了 fillColor (白色)、createPattern (zebra.png) 和 linearGradient (光澤);這說明瞭可對每個向量物件套用多種填滿類型:

斑馬紋效果
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 最近發布的文章,您可以使用順時針和逆時針繞線規則的獨特屬性建立內陰影。如要這麼做,請繪製容器矩形來建立「內陰影」,然後使用相反的繞線規則繪製剪裁形狀,建立形狀的反向。

以下範例可同時使用顏色、漸層和圖案,設定內陰影和 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,我們可以將效果鏈結在一起,產生更精緻的效果 (利用遮罩和混合)。螢幕就是你的畫布 ;)

Spaceage:生成式特效

<canvas> 中,從萬國碼 (Unicode) 字元 0x2708 開始:

Unicode 圖形

…到這個經過重定位處理的範例:

陰影範例

…可以透過多次呼叫 ctx.strokeText() 來實現,使用細的 lineWidth (0.25),同時慢慢減少 x 偏移和 alpha,讓我們的向量元素有運動的感覺。

將元素 XY 位置對應至正弦/餘弦波,並使用 HSL 屬性循環顯示顏色,即可建立更有趣的效果,例如這個「生化危害」範例:

HSL 循環效果

HSL:色調、飽和度、亮度 (1978 年)

HSL 是 CSS3 規格中新支援的格式。HEX 是專為電腦設計,HSL 則專為人類可讀而設計。

為說明 HSL 的簡易性,如要循環顯示色譜,我們只需從 360 開始遞增「色調」;色調會以圓柱形方式對應至色譜。亮度控制顏色的深淺;0% 表示黑色像素,100% 表示白色像素。飽和度控制色彩的明亮或鮮豔程度;灰色是以 0% 的飽和度建立,而鮮豔色彩則是以 100% 的值建立。

HSL 圖片

HSL 是近期推出的標準,因此您可能需要繼續支援舊版瀏覽器,這可以透過色域轉換達成。下列程式碼接受 HSL 物件 { H: 360, S: 100, L: 100},並輸出 RGB 物件 { R: 255, G: 255, B: 255 }。接著,您可以使用這些值建立 rgb 或 rgba 字串。如需更深入的資訊,請參閱維基百科上有關 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 中建立動畫,有兩種選擇:setTimeout和setInterval。

window.requestAnimationFrame 是取代這兩者的新標準,可讓瀏覽器根據可用資源調節動畫,節省全球電力 (並讓電腦少跳幾下)。重要功能包括:

  • 當使用者離開畫面時,動畫可以減慢或完全停止,避免使用不必要的資源。
  • 影格率上限為 60 FPS。這是因為這個值遠高於人類可察覺的程度 (大多數人類在 30 FPS 時會看到動畫「流暢」)。

撰寫本文時,必須使用供應商專屬前置字元才能使用 requestAnimationFrame。Paul Irish 在 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);
        };
})();

更進一步來說,更具野心的做法可能是將這項功能與 requestAnimationFrame.js 等 poly-fill 結合 (有幾項功能需要解決),以便在切換至這項新標準時,進一步支援舊版瀏覽器。

(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);
})();
Notes 模糊效果圖片
動畫圖示
矩陣圖

原始碼

在瀏覽器供應商領域的支援下,<canvas> 的未來無庸置疑,可以使用 PhoneGap 移植到 iPhone/Android/桌面執行檔,或

Titanium。