我的背景
2006 年 Firefox 2.0 版推出時,我才注意到<canvas>。Ajaxian 上有一篇介紹轉換矩陣的文章,啟發我建立第一個 <canvas> 網頁應用程式:Color Sphere (2007)。這讓我沉浸在色彩和圖形基元的世界中,並激發我創作 Sketchpad (2007-2008),努力在瀏覽器中打造「比小畫家更好」的應用程式。
這些實驗最終促成我和老友 Charles Pritchard 創立 Mugtug。Darkroom 是我們正在開發的 HTML5 <canvas>。Darkroom 是一款非破壞性的相片分享應用程式,結合了像素濾鏡、向量字體排版和繪圖功能。
簡介
<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。
立體 3D 效果 (詳情請參閱浮雕圖片) 是簡單程式碼行的一大應用範例。只要使用下列 CSS 程式碼,就能在戴上 3D 紅藍眼鏡 (3D 電影院提供的眼鏡) 時,營造出深度錯覺:
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
將這個字串轉換為 <canvas> 時,請注意以下兩點:
- 由於沒有陰影模糊 (第三個值),因此實際上沒有理由執行陰影,因為 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>
<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 電影。有人自願嗎?
霓虹彩虹、斑馬反射:鏈結效果
在 <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 開始:
…到這個經過重定位處理的範例:
…可以透過多次呼叫 ctx.strokeText() 來實現,使用細的 lineWidth (0.25),同時慢慢減少 x 偏移和 alpha,讓我們的向量元素有運動的感覺。
將元素 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 的精闢文章。
// 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);
})();
原始碼
在瀏覽器供應商領域的支援下,<canvas> 的未來無庸置疑,可以使用 PhoneGap 移植到 iPhone/Android/桌面執行檔,或