画布中的排版效果

迈克尔·迪尔 (Michael Deal)
Michael Deal

我的背景

我是在 2006 年 Firefox v2.0 发布时注意到 <canvas> 的。我在有关 Ajaxian 的一篇文章中描述转换矩阵,这促使我创建了自己的第一个 <canvas> Web 应用:Color Sphere(2007 年)。这让我沉浸在色彩和图形基元的世界中;激发了 Sketchpad(2007 年 - 2008 年)的问世,努力在浏览器中整合一款“优于 Paint”的应用。 这些实验最终促使我的老朋友 Charles Pritchard 创立了初创公司 Mugtug。我们正在 HTML5 <canvas> 中开发 Darkroom。Darkroom 是一款无损照片分享应用,它将基于像素的滤镜的强大功能与基于矢量的排版和绘图相结合。

简介

画布横幅图片。

<canvas> 可让 JavaScript 程序员完全控制屏幕上的颜色、矢量和像素(显示器的视觉结构)。

以下示例涉及的是 <canvas> 中一个备受关注的地方,即创建文字效果。可在 <canvas> 中创建的文本效果种类繁多,只要您想了解它就能实现,这些演示仅涵盖了一部分可能实现的文本效果。虽然我们在本文中讨论的是“文本”,但这些方法可应用于任何矢量对象;在游戏和其他应用中打造有趣的视觉效果:

<canvas> 中的文字阴影。
<canvas> 中的类似 CSS 的文字效果:创建剪裁蒙版、在 <canvas> 中查找指标,以及使用 shadow 属性。
霓虹彩虹、斑马反射链式效果。
<canvas> 示例中的 Photoshop(类似于 Photoshop)的文字特效使用 globalCompositeOperation、createLinearGradient、createPattern。
<canvas> 中的内部和外部阴影
揭示一个鲜为人知的功能:使用顺时针而不是逆时针方向绕圈来创建反转的阴影(即内部阴影)。
太空时代 - 生成效果。
<canvas> 中基于生成的文字效果,使用 hsl() 颜色循环,使用 window.requestAnimationFrame 来产生运动的感觉。

画布中的文字阴影

CSS3 规范中我最喜欢的新增功能之一(以及边框半径、网络渐变等)就是创建阴影的功能。请务必注意 CSS 阴影与 <canvas> 阴影之间的区别,具体来说:

CSS 对框元素(如 div、span 等)使用 box-shadow 方法,对文本内容使用 text-shadow

<canvas>如需在 <canvas> 中创建阴影,请点按以下四个属性:

ctx.shadowColor = "red" // 字符串
阴影的颜色;RGB、RGBA、HSL、HEX 和其他输入均有效。
ctx.shadowOffsetX = 0; // 整数
阴影相对于文字的水平距离。
ctx.shadowOffsetY = 0; // 整数
阴影相对于文字的垂直距离。
ctx.shadowBlur = 10; // 整数
阴影的模糊效果,值越大,模糊程度越大。

首先,我们来看看 <canvas> 如何模拟 CSS 效果。在 Google 图片中搜索“css text-shadow”后,我们发现了一些非常实用的演示,包括 Line25StereoscopicShadow 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 元素,生成的每个 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 hack-ardry(请参阅排版指标,了解使用 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 电影屏幕效果有多难?显然,没那么难。以下内核将第一张图片(数据)的红色通道与第二张图片 (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,其中“较暗”,较浅“”可视为层混合模式;其他 10 个操作作为 alpha 蒙版应用于这些层(其中一个层会移除另一个层的像素)。globalCompositeOperation 将“图层”(在本例中为代码字符串)联系在一起,并以令人兴奋的新方式将它们组合起来:

链接特效图形

globalCompositeOperation 图表显示了工作中的 GCO 模式;此图表使用了很大一部分色谱和多个级别的 alpha 透明度,以便查看细节。 建议您查看 Mozilla 的 globalCompositeOperation 参考文档,了解文本说明。如需进一步的研究,您可以在 Porter Duff 的合成数字图片中了解运算的工作原理。

我最喜欢的模式是 globalCompositeOperation="lighter"。Lighter 会以类似于光线的混合方式混合附加像素;当红光、绿光和白光处于全强度时,我们看到的是白光。这是一项非常令人兴奋的功能,尤其是在 <canvas> 设置为低 globalAlpha 的情况下;能够实现更精细的控制和更平滑的边缘。lighter 有很多用途,最近我最喜欢用的是 http://weavesilk.com/ 上的 HTML5 桌面背景创建工具。我的演示之一 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();
};

斑马线反射效果

斑马线反射效果的灵感来自 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 是一种新标准,可以取代这两者;它允许浏览器根据可用资源调整动画,从而节省全球电量(以及减少您的计算机耗电)。一些重要功能包括:

  • 当用户存在帧时,动画可能会减慢或完全停止,以防止使用不需要的资源。
  • 帧速率存在上限为 60FPS 的上限。其原因在于,这远高于人类所能察觉的级别(大多数人类以 30 FPS 的帧速率看出动画“流畅”)。

在编写时,供应商专用前缀必须使用 requestAnimationFrame。Paul Irish 在用于智能动画的 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(一些功能有待开发)结合起来,从而在切换到这一新标准的同时在更大程度上支持旧版浏览器。

(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/桌面可执行文件,或者