画布的排版效果

HTML5 Rocks

我的背景介绍

我是在 2006 年 Firefox v2.0 发布时注意到 <canvas> 的。受到 Ajaxian 上一篇介绍变换矩阵的文章启发,我在 2007 年开发了自己的第一款 <canvas> 网络应用:Color Sphere。这款应用让我沉浸在颜色和图形基元的世界中,受此启发,我又在 2007 到 2008 年开发了 Sketchpad,为的是在浏览器中整合一款“比画图好用”的应用。

经过这些实验,最终我和老朋友查尔斯·普理查德 (Charles Pritchard) 开始着手创建 Mugtug。我们通过 HTML5 <canvas> 创建了Darkroom。Darkroom 是一款无损照片分享应用,将基于像素的过滤器的强大功能与基于矢量的排版和绘图结合在一起。

简介

pastedGraphic.png

<canvas> 可让 Javascript 编程人员完全地控制屏幕上的颜色、矢量和像素,也就是显示器的视觉效果。

以下示例涉及的是 <canvas> 经常被忽视的一个方面,也就是创建文字效果。这些演示仅仅是一部分,能在 <canvas> 中创建多少文字效果,完全取决于您的想象力。虽然我们在本文中讨论的是“文字”,但是您可以将这些方法应用于任何矢量对象,从而在游戏和其他应用中创建炫目的视觉效果:

<canvas> 中的文字阴影
<canvas> 中类似 CSS 的文字效果可用于创建剪辑蒙板、查找 <canvas> 中的度量以及使用阴影属性。
彩虹式霓虹灯、斑马线反射连锁效果
<canvas> 中类似于 Photoshop 的文字效果示例,使用了 globalCompositeOperation、createLinearGradient、createPattern。
<canvas> 中的内部和外部阴影
透露一个鲜为人知的功能:使用顺时针而不是逆时针方向绕圈,即可创建反转的投射阴影(即内部阴影)。
航天时代:生成效果
<canvas> 中基于生成的文字效果,使用 hsl() color-cycling 和 window.requestAnimationFrame 产生运动的感觉。

画布中的文字阴影

CSS3 规范中我最喜欢的新增功能之一(其他还包括边框圆角、网络梯度等)就是创建阴影的功能。CSS 阴影与 <canvas> 阴影是有差别的,很有必要区分开来,具体而言:

CSS 对框元素(例如 div、span 等)使用框阴影,而对文字内容使用文字阴影。

而 <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 文字阴影”可以找到一些很好的演示供我们进行模拟,例如 Line25立体效果阴影 3D

pastedGraphic_1.png

以立体 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);

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

禁止 alpha 倍增

在更为复杂的示例中(例如 Line25 中的霓虹灯效果),必须使用 shadowBlur 属性来正确地模拟效果。由于霓虹灯效果依赖于多个阴影,而在 <canvas> 中,每个矢量对象只能有一个阴影,这就带来了问题。因此,为了绘制多个阴影,您必须在文字的上层绘制多个版本的文字。这就导致了 alpha 倍增,并最终导致边缘锯齿化。

pastedGraphic_2.png

我曾试过运行 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 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> 中的文字阴影效果

pastedGraphic_3.png

暂停(像素推送上的切线)

我在写到本文的这一部分时,对立体效果示例感到非常好奇。要使用 <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;

请访问 立体效果演示,了解如何创建可通过 3D 眼镜(红/蓝)增强观看效果的图片。现在,只要用胶带把两台 iPhone 粘在脑门上,然后同时点击“录制视频”,就能利用 HTML5 制作自己的 3D 电影啦!有人自愿试试吗?

pastedGraphic_5.png
3D 眼镜

彩虹式霓虹灯、斑马线反射连锁效果

在 <canvas> 中连锁多种效果是很简单的,但是必须掌握 globalCompositeOperation (GCO) 的基本知识。这些操作与 GIMP(或 Photoshop)的对比:<canvas> 中有 12 种 GCO,其中包括 darker 和 lighter(可视为图层混合模式);另外 10 种操作应用于作为 alpha 蒙板(一个图层删除另一个图层的像素)的图层。globalCompositeOperation 将“图层”(在本例中为代码字符串)联系在一起,以新颖有趣的方式将它们组合起来:

pastedGraphic_6.png

globalCompositeOperation 图表显示了实际使用中的 GCO 模式;此图表中使用了很大一部分的多色谱和多层阿尔法透明处理,以便展示出预期的细节。我建议您查看 Mozilla 的 globalCompositeOperation 参考,以获取文字说明。要做进一步的研究,您可以了解如何在波特·达夫 (Porter Duff) 的复合数字图片中运用此操作。

我最喜欢的模式是 globalCompositeOperation=”lighter”。Lighter 以类似于光线混合的方式来混合附加像素;当红光、绿光和白光都处于最高亮度时,我们看到的是白光。这是一项很令人兴奋的功能,尤其是在将 <canvas> 设为低 globalAlpha 的情况下;可以实现更精细的控制,并获得更平滑的边缘。lighter 有很多用途,我最近喜欢用的是 http://weavesilk.com/ 上的 HTML5 桌面背景创建器。呼吸的银河系 (JS1k) 是我的演示之一,也使用了 lighter 模式绘图方案,从这两个示例中您可以看出此模式能产生什么效果。

请注意:某些 GCO 模式并不是在所有浏览器中都受支持的,这一问题还有待解决。以下 6 种模式适用于所有浏览器(Chrome 浏览器、Safari、Firefox 和 Opera):source-over、source-atop、destination-over、destination-out、lighter 和 xor。希望这一问题能够在以后发布的版本中得以解决,有关详情,请访问 globalCompositeOperation 浏览器处理

霓虹灯抖动效果

在下面的演示中,我们将使用 globalCompositeOperation(source-in、lighter 和 darker)连锁多种效果,做出类似于 Photoshop 的带抖动轮廓的霓虹灯光。此演示是 <canvas> 演示中文字阴影的后续,使用了与分离文字和阴影相同的策略(请参见前一部分)。

pastedGraphic_7.png

查看霓虹灯抖动效果

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 (white)、createPattern (zebra.png) 和 linearGradient (shine) 结合在一起;这说明了对每个矢量对象应用多个填充类型的功能:

pastedGraphic_8.png

查看斑马线反射效果

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 进行样式设置。您可以分别指定图案旋转;请注意,现在斑马线是彼此垂直的。使用与边界框大小相同的剪辑蒙板,就无需以超大的容器来封闭剪切形状了,这样可以避免处理不必要的阴影部分,从而加快速度。

pastedGraphic_9.png

查看内部阴影效果

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 着手:

pastedGraphic_10.png

...对于该阴影示例:

pastedGraphic_11.png

...可通过以下方法完成:按较薄的 lineWidth (0.25) 多次调用 ctx.strokeText(),同时缓慢降低 x-offset 和 alpha;这样可以让矢量对象产生运动的感觉。

通过将元素的 XY 位置映射到正弦/余弦波上,并使用 HSL 属性在各种颜色间循环,我们可以创建更有趣的效果,例如下面的“生化危机”示例:

pastedGraphic_12.png

HSL:色相 (Hue)、饱和度 (Saturation)、亮度 (Lightness) (1978)

HSL 是 CSS3 规范中新增的一种支持格式。HEX 是针对计算机设计的,而 HSL 则是人类可读的。

下面说明 HSL 的易用性:要在色谱中循环,我们只需从 360 开始递增“色相”,而色相会以圆柱形式映射到色谱。亮度控制了颜色的明暗程度;0% 表示黑色像素,而 100% 表示白色像素。饱和度控制了颜色的鲜艳程度或纯度;使用 0% 的饱和度将创建灰暗的颜色,而使用 100% 的饱和度将创建鲜艳的颜色。

pastedGraphic_13.png

由于 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 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 等多重填充结合在一起(某些功能还有待开发),这样可以在过渡到这一新标准的过程中更好地支持旧版浏览器。

下面的示例显示了如何在不停止浏览器的情况下,通过低 alpha 向 strokeText 发送数千调用,从而创建动画。控制方法有点奇怪,但是可以得到一些很酷的结果:

查看太空时代效果

(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);
})();
pastedGraphic_14.png
pastedGraphic_16.png
pastedGraphic_17.png

源代码

凭借跨浏览器供应商范围的支持,<canvas> 在将来无疑可以使用 PhoneGapTitanium 移植到 iPhone/Android 设备/桌面计算机的可执行文件。

CanvasTextEffects.zip 中提供了源代码

Comments

0

Next steps

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License.