画布中的排版效果

Michael Deal
Michael Deal

我的背景

2006 年,Firefox v2.0 发布,<canvas>进入了我的视野。Ajaxian 上一篇关于转换矩阵的文章启发我创建了我的第一个 <canvas> Web 应用:Color Sphere (2007)。这让我沉浸在色彩和图形基元的世界中,并激发了我创建 Sketchpad (2007-2008) 的灵感,我希望在浏览器中打造一款“比画图更好”的应用。 这些实验最终促成了我与老朋友 Charles Pritchard 共同创立了 Mugtug。我们正在使用 HTML5 <canvas> 开发 Darkroom。Darkroom 是一款非破坏性照片分享应用,它将基于像素的滤镜与基于矢量的排版和绘图功能相结合。

简介

Canvas 横幅图片。

<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 使用两种方法: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; // 整数
阴影相对于文本的水平距离。
ctx.shadowOffsetY = 0; // 整数
阴影相对于文本的垂直距离。
ctx.shadowBlur = 10; // 整数
阴影的模糊效果,值越大,模糊效果越明显。

首先,我们来看看 <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> 命令,因此我在演示源代码中添加了一个简单的 text-shadow 解析器;这样,您就可以向其提供 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) 也使用了 lighter 模式,从这两个示例中绘制的图案可以看出,这种模式会产生什么样的效果。

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 最近的一篇帖子中所建议的那样,您可以使用顺时针与逆时针缠绕规则的独特属性来创建内阴影。为此,您需要通过绘制容器矩形来创建“内阴影”,然后使用相反的缠绕规则绘制一个切口形状,从而创建该形状的反向。

以下示例允许同时使用颜色 + 渐变 + 图案来设置 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,我们可以将效果串联在一起,从而产生更精细的效果(利用遮罩和混合)。屏幕就是你的舞台 ;)

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 是新的标准,旨在取代上述两种方法;它允许浏览器根据可用资源调节动画,从而节省全球电力(并减少计算机的运行负担)。 一些重要功能包括:

  • 当用户离开画面时,动画可以减慢或完全停止,以防止使用不必要的资源。
  • 帧速率上限为 60FPS。这是因为,该帧率远高于人类可以注意到的水平(大多数人在 30FPS 时就会看到动画是“流畅”的)。

在撰写本文时,需要使用供应商特定前缀才能使用 requestAnimationFrame。Paul Irish 在用于智能动画的 requestAnimationFrame 中创建了一个具有跨供应商支持的 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);
        };
})();

更进一步,更雄心勃勃的人可能会将其与 poly-fill(例如 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/桌面可执行文件,或者

Titanium。