在畫布中的打字效果

Michael Deal
Michael Deal

我的資歷

Firefox v2.0 推出時,<canvas> 就進入了我的認知。關於轉換矩陣的 Ajaxian 文章,啟發我建立第一個 <canvas> 網頁應用程式;Color36 (2007)。這讓我深入探索色彩的世界,也充滿了圖形原始物件;啟發我們打造 Sketchpad (2007-2008),希望能在瀏覽器中建構「比 Paint 更佳」的應用程式。 這些實驗最終促成了我和長期好友 Charles Pritchard 一起創立了 Mugtug。我們正在開發 HTML5 <canvas> 中的 Darkroom。Darkroom 是非破壞性的相片分享應用程式,結合了像素式濾鏡與向量字體排版和繪圖的強大功能。

引言

畫布橫幅圖片。

<canvas> 可讓 JavaScript 程式設計師完全掌控螢幕上的顏色向量像素,也就是螢幕的視覺組成。

下列範例處理 <canvas> 中有一個不特別注意的區域;建立文字效果。您可在 <canvas> 中建立各種文字效果,數量越多越好,這些示範將涵蓋幾個可能可行的子節。儘管本文提及的「文字」一節已經處理,這些方法也適用於「任何向量物件」,在遊戲和其他應用程式中打造引人入勝的視覺效果:

<canvas> 中的文字陰影。
<canvas> 中具有 CSS 類文字效果,可以建立裁剪遮罩、在 <canvas> 中找出指標,並使用陰影屬性。
霓虹彩虹、斑馬-反射 - 鏈結效果。
<canvas> 範例中,使用 GlobalCompositeOperation、createLinearGradient、createPattern 等資源中的 Photoshop 類似文字效果。
<canvas> 內部和外部陰影
顯示小已知功能;使用順時針與逆時針方向,建立投射陰影的反向 (內部陰影)。
空間 - 生成效果。
<canvas> 中使用 hsl() 色彩循環和 window.requestAnimationFrame,建立生成式文字效果,營造動作感。

畫布中的文字陰影

在 CSS3 規格中 (加上邊框半徑、網路漸層等),我最喜歡的其中一項就是建立陰影。請務必瞭解 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" // 字串
陰影顏色;RGB、RGBA、HSL、HEX 和其他輸入皆為有效。
ctx.shadowOffsetX = 0; // 整數
相對於文字,陰影的水平距離。
ctx.shadowOffsetY = 0; // 整數
相對於文字,陰影的垂直距離。
ctx.shadowBlur = 10; // 整數
陰影的模糊效果會越大,模糊程度越高。

開始前,我們來看看 <canvas> 如何模擬 CSS 效果。在 Google 圖片中搜尋「css 文字陰影」後,可以找到一些絕佳的示範示範:Line25StereoscopicShadow 3D

CSS 3D 圖形

立體視覺 3D 效果 (詳情請參閱「Anaglyph 圖片」) 是一種簡單的程式碼行,適合使用。有了下列 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 轉換,我們就會測量 DOM 元素,同時測量「height:1em」的偏移量。
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 上的 Neon 效果) 中,必須使用 shadowBlur 屬性正確模擬效果。由於 Neon 效果取決於多個陰影,因此我們會發生問題;在 <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-高度」,過去是指字母「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 電影螢幕效果,會有多困難?顯然不要太難。下列核心結合了第一張圖像 (資料) 的紅色和第二張圖片的青色色 (資料 2):

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,而「lighter」可視為圖層混合模式;其他 10 項作業則會以 Alpha 遮罩的形式套用至圖層 (其中一層會移除另一層的像素)。GlobalCompositeOperation 會連結「圖層」(在我們的範例中為程式碼字串) 以新奇有趣的方式加以組合:

鏈結特效圖片

Global CompositeOperation 圖表會顯示工作的 GCO 模式。此圖表使用大部分的色譜和多層 Alpha 透明度,以便詳細瞭解可能發生的情況。建議您查看 Mozilla 的 globalCompositeOperation 參考資料以取得文字說明。如要進一步瞭解這項作業的運作方式,請參閱 Porter Duff 的合成數位圖片

我最喜歡的模式是 UniversalCompositeOperation="lighter"。較淺的混合了附加的像素,類似於光源的混合方式;當紅色、綠色和白色燈光全亮度時,即可看到白色燈號。想解決這個問題,這是一項令人期待的功能,尤其是當 <canvas> 設為全球較低的 Alpha 值時;提供更精細的控制項,以及更順暢的邊緣。更淺顯易懂的是,我最近成為 HTML5 電腦版背景創作工具的愛用,歡迎前往 http://weavesilk.com/。我的其中一個示範 Breathing Galaxies (JS1k) 也使用較輕模式,也就是從這兩個範例開始繪圖,看看這種模式會產生什麼影響。

globalCompositeOperation 瀏覽器處理

霓虹燈抖動效果

在以下示範中,我們會使用 globalCompositeOperation (原始碼內、較淡、更暗) 鏈結各種效果,藉此產生一個帶有類似 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();
};

斑馬的反射效果

Zebra Reflection 效果靈感來自 WebDesignerWall 的優質資源,瞭解如何運用 CSS 為網頁增添視覺效果。這個概念會稍微進一步,為文字建立「反思」,例如 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 最近的文章中提出,您可以使用順時針與反順時針方向規則的獨特屬性建立內部陰影。為此,您可以繪製容器矩形來建立「內部陰影」,然後用相反的波浪規則繪製凹口形狀,建立相反形狀。

以下範例會允許內部陰影和 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:

萬國碼 (Unicode) 字型

...到這個彩色範例:

陰影範例

...可透過採用細 LineWidth (0.25) 多次呼叫 ctx.strokeText() 的方式,同時慢慢降低 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 中建立動畫,有兩種選擇:setTimeoutsetInterval

window.requestAnimationFrame 是取代這兩種方式的新標準;讓瀏覽器根據可用資源執行動畫,藉此節省全球電力 (以及電腦幾次心跳)。以下列舉幾項重要功能:

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

撰寫本文時,您必須指定通風器專屬的前置字串,才能使用 requestAnimationFrame。Paul Ireland 在 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 廣告結合起來,在改用這項新標準的同時,還將支援舊版瀏覽器。

(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<canvas> 轉移到 iPhone/Android/Desktop 執行檔。

Titanium