在这个由两部分组成的 系列文章 的 第 1 部分 中,我们讨论了如何让 canvas 与 HTML 元素强强联手,共同构建丰富的 Internet 应用程序。
在本文中,我们将回顾选择 canvas 或以 HTML 为中心的架构的标准,了解动画考虑事项和克服文本渲染限制的方法,也就是说,通过将 HTML 和 canvas 元素分层,为视频游戏奠定基础,从而利用每一种方法的优势。图 1 显示了本文中太空射击游戏示例的基础。
图 1. 将 HTML 和 canvas 元素相结合的样例应用程序
您可以 下载 本文中使用的示例的源代码。
在使用大量图形组件、交互式体验和可视化效果构建应用程序时,重要的是要了解可用于该任务的工具。本节将探讨如何使用 HTML 实现 UI 组件,以及如何使用 canvas 实现动画组件。
尽管 canvas 可能具有令人印象深刻的图形性能,但它并不总是实现丰富 UI 的最佳选择;HTML 元素可能更适合。很多丰富的 Internet 应用程序包含各种部件,每个部件都有不同的用途和要求。将 canvas 和 HTML 元素强强联合的混合方法可以最有效地实现您的目标。
样例应用程序利用了 图 2 中的分层技术。canvas 主要负责实时图形和动画,而几个 HTML 元素将会覆盖在上面,用于构成各个 UI 组件。
图 2. 将 HTML 元素放在 canvas 上面一层
在决定最佳方法时,需要考虑应用程序各部件的需求。一般说来,要求具有较高的用户交互性、但又不需要实时更新的 UI 的组件通常最适合用于 HTML 层。这些元素可能包含文本、超链接和表单元素。
例如,尽管样例应用程序可能主要是一个 canvas ,它拥有一个仅仅使用 HTML 标记的聊天窗口组件。这如图 3 所示。
有了简单的 HTML 标记和 CSS 规则,您可以轻松创建交互式 UI 组件,比如文本框、滚动条和按钮。既然浏览器提供预置的组件,为何还要花费数小时试图模仿 canvas 中 UI 组件的外观和行为?
图 3. 用 HTML 实现的聊天窗口
在聊天窗口中,不涉及任何动画,对内容的更新相对不频繁(每当有新聊天项目时才更新)。因此,这是一个 HTML 实现的好的候选对象。
相反地,canvas 为以下内容提供一个更好的解决方案:
- 必须频繁更新(例如,每毫秒更新一次)
- 需要一个恒定的动画周期
- 只需要很小的用户交互性
动画内容在网站中变得越来越普遍。无论您只是为了让一个网站有生气而使用简单动画(比如导航转换),还是想要提供更高级的基于浏览器的游戏,都有多种选择任您支配。通常,可以使用 HTML/CSS 模型完成相对简单的动画,使用 canvas 就有些过了。
像 jQuery 等许多库都提供了便捷的工具来提供一致的跨浏览器输出。将这些库中的工具与一些 CSS 知识结合起来,您就可以大幅减少完成动画的工作,即使这些动画实际上是相当复杂的。
在示例中,图 4 中的太空飞船根据玩家的控制进行移动和旋转。这一行为只能使用 HTML/CSS 动画实现。不要求用户对 canvas 有任何了解。
图 4. 动态的太空飞船元素
当需要扩展时,HTML/CSS 动画模型开始分解。同时动态化大量元素会给浏览器带来很重的负担,这会降低应用程序的整体性能。
每次重新定位一个 DOM 元素时(对于平滑的动画来说,这肯定每秒发生多次),浏览器的布局引擎需要大量开销才能重新计算和绘制 DOM 层次结构中的元素。增至数十、数百或上千元素之后,即使在现代计算机上也会带来相当大的性能消耗。
渲染文本到屏幕上对于任何网站来说都是一项非常基本的任务,我们可能都不太注意其后台运作。当您希望屏幕上包含一些文本时,只需在两个 HTML 元素间标记(可能还有一点 CSS)之间输入文本即可,其余工作都会由浏览器接管。但是对于 canvas 来说,情况却并非如此。
当您希望使用 canvas 渲染文本时,有几个基本的工具可用。它们几乎支持工作所需的一切基本功能,但在开发 canvas 应用程序时,却没有提供太多的易用性。
canvas context 对象提供了您在渲染文本时可以设置的各种属性。它还提供了一个功能来执行实际渲染。这些属性包括:
context.font设置
context.font的值能够让您控制要渲染文本的字体类型、大小、粗细和样式。分配的值是各种选项拼凑在一块、由空格分隔的一个字符串。这里的输入格式有点儿难办。例如,每次更新值时,都必须在该字符串中提供字体类型。清单 1 显示了小字体的设置。
清单 1. 设置小字体context.font = 'italic 8px Arial'; context.fillText('Variety is the spice of life!', 0, 50);
清单 2 显示了大字体的设置。
清单 2. 设置大字体context.font = 'italic 20px Arial'; context.fillText('Variety is the spice of life!', 0, 50);
用户不可能仅设置字体一次,稍后,用户可以根据希望渲染到屏幕上的文本来调整其他选项。修复这一问题的方法会在稍后加以讨论。
context.fillStylecontext.fillStyle用于各种 canvas 操作;对文本设置该值是为了控制要渲染到屏幕上的字体颜色。指定值的输入格式遵从 CSS 输入格式。以下全部是有效的输入示例:- 基本颜色:'red'、'blue'、'green',等等
- 16 进制值:'#rrggbb'
- 'rgb(r, g, b)'
- 'rgba(r, g, b, a)'
例如,要设置 fillStyle,请使用
context.fillStyle = 'red';。context.fillText()调用这一函数可将文本渲染到 canvas 上。它接受以下参数:
(string) text:要绘制到 canvas 上的文本。(float) x:绘制文本的 x 的大小。(float) y:绘制文本的 y 的大小。[optional] (float) maxWidth:试图控制文本所需的最大宽度。如果可能的话,请将使用一个水平浓缩程度更高的字体,或者更小的字体。
您可以使用的另一个重要工具是上下文对象的一个函数,名为 measureText(),它接受一个字符串参数。结果是一个包含所提供字符串的度量尺寸的对象,如 清单 3 所示。
清单 3. 确定文本字符串的宽度
context.font = '30px Arial';
var dim = context.measureText(
'Hello, world!'
);
alert(
'width: ' + dim.width + '\n' +
'height: ' + dim.height
);
|
清单 4. 警报
width: 164
height: undefined
|
注意 height 后面的 undefined。令人费解的是,所有浏览器都总是会返回一个未定义的结果,因此使用 measureText() 函数实际上不可能确定具体的文本高度。有几项技术可用于确定一个颇具代表性的值。对于许多字体,某些字母相当方正,比如字母 M。您可以度量这些字符中某个字符的宽度,并将该值作为字体高度的近似测量。
另一种方法就是将提供的字体大小作为实际高度的基础。如果上述示例中提供了一个 30px 的值,那么您可以添加几个垂直填充像素,并将产生的值用作近似高度。
要提高开发的效率,您可以使用之前提到过的工具以一种友好的方式执行,创建一个简单的 wrapper 类。该类会自动的设置和置换各个上下文属性,最终将想要的文本渲染到 canvas 上。您可以使用 context.font 属性自动解决该问题。清单 5 中的示例在本系列的 第 1 部分 使用过。
清单 5. 在 canvas 中使用动态样式渲染文本
context.font = '18px Arial';
context.fillStyle = 'green';
context.fillText('Variety', 0, 50);
context.translate(60, 0); //move 60 pixels to the right (a)
context.font = '12px Arial';
context.fillStyle = 'blue';
context.fillText('is the', 0, 50);
context.translate(35, 0); //move 35 pixels to the right (b)
context.font = 'italic bold 12px Arial';
context.fillStyle = 'red';
context.fillText('spice of life!', 0, 50); // (c)
|
图 5 显示清单 5 中在 canvas 中渲染文本的三个步骤。
图 5. 在 canvas 中使用动态样式渲染文本
要使用原生 canvas API,则需要编写大量代码。如果您可以简化上述示例所需的代码,那么可以提高将文本渲染到 canvas 上的效率。例如,您可以使用清单 6 中的代码,而不是编写多行的原生代码。
清单 6. 增强 canvas 文本渲染的工作流的概念
var myText = new CanvasText();
myText
.family('Arial')
.size('18px')
.weight('bold')
.color('green')
.append('Variety')
.size('12px')
.weight('normal')
.color('blue')
.append('is the')
.style('italic')
.color('red')
.append('spice of life!')
.render();
|
图 6 显示了 清单 6 中的代码。该示例使用了链接 (chaining),链接是 jQuery 和 jQuery 插件中常用的一个干净而又简单的语法。
图 6. 增强 canvas 文本渲染
流程通过这种方式得到简化。尽管代码行数几乎一样,但每行的代码复杂性降低了。而且您不再需要手动定位样式的文本块。如果您稍后需要调整文本块的一些属性,则无需手动重新定位以下构建块。
让我们来回忆一下,canvas.font 属性是多个属性的组合,可决定如何渲染文本。在 Variety 与后续构建块之间,无需指定字体。在 'is the' 与 'spice of life!' 块之间,也不需要使用字体大小和粗细的技术。这里使用了一个简单的机制来记住之前应用过的属性,使您无需重新配置之前应用文本的预期属性。请参阅 参考资料,以获得有关 CanvasText 类的一个示例。
为了得到上述结果,只需对希望渲染的文本样式属性进行分组,相应地指定样式属性,然后调用闭合组的 CanvasText 对象的 append() 函数,并存储它供日后使用。渲染文本时,需要遍历这些组,然后指定样式并应用它。在迭代组时,保留之前的样式状态的工作记录,并在必要时覆盖这些记录。这些操作实现了文本存储,并降低了将文本渲染到 canvas 的技术难度。这只是使用 wrapper 类或构建自动化 canvas 文本渲染过程的一个优势。
自动换行是很多人需要的一个功能。当一个 HTML 元素包含因太长而无法放在一行的内容时,这个功能就起作用了。我们无需考虑文本的长度、容器的宽度等。然而,对于 canvas,换行并不是这么简单。HTML canvas 元素目前不包含管理这些事情的内置功能,因此您必须使用之前提到过的工具和方法以编程方式实现。
例如,要让文本在超过容器宽度时换行,则必须知道所用容器的宽度、希望渲染的文本宽度、文本行高度。您还需要创建虚拟容器,提供指定该容器宽度的方法。在创建这样的流程后,我们准备开始探索在 canvas 中实现自动换行所需的逻辑。
清单 7 详尽说明了 清单 6,并提供一些要传递给 CanvasText 类的构造函数的参数。
清单 7. 将函数传递到
CanvasText 类的构造函数中
var myText = new CanvasText(
{x: 50, y: 50},
{width: 100, height: 200}
);
myText
.family('Arial')
.size('18px')
.weight('bold')
.color('green')
.append('Variety')
.size('12px')
.weight('normal')
.color('blue')
.append('is the')
.style('italic')
.color('red')
.append('spice of life!')
.render();
|
其他参数包含要渲染的文本位置的 x 和 y 坐标、文本大小和容器大小。请注意,这里指定了一个显式宽度来限制内容换行。
有了要遵从的容器大小后,您可以创建一些功能来实现预期结果了。除了遍历不同的样式外,您还需要遍历每一个单词,以获取一些数据,了解经过渲染的文本实际需要多宽。有了这些信息,就可以跟踪已经渲染文本的宽度,并确定下一个词是否会呈现在预期范围之外。幸运的是,measureText() 函数正好提供了所需的信息。
清单 8 显示使用 CanvasText 类实现自动换行所需的代码。
清单 8. 包含自动换行功能
// use measure text
var currentWordWidth = context.measureText(currentWord).width
// word wrap code here
if (textAdjustment.x + currentWordWidth > this._size.x || textToDraw == '\n') {
textAdjustment.x = 0;
textAdjustment.y += parseInt(previousFontOptions.size, 10);
}
|
最终结果如 图 7 中所示。
图 7. 自动换行结果
请参阅 参考资料,以获得有关 CanvasText 类的自动换行示例。
使用一个相对简单的 wrapper 类,就可以在 canvas 中创建自动化样式和自动换行。每当您需要使用canvas渲染文本时,都可使用这个 wrapper 类。
我们往往将可用的 UI 看得理所当然。从 HTML 到 Flash 再到 Silverlight,它们都以文本、菜单、滚动条和表单元素的形式提供一组基本的 UI 组件。
下一个示例是一个简单的太空射击游戏的基本设计。您要创建的是:可围绕游戏区飞行的一个太空船组件、一个聊天窗口和一个商店。一些组件需要动态化,而另一些组件提供可能需要使用频繁更新或渲染的文本信息。
请参阅 参考资料,以获得完整的太空射击游戏示例。
第一步是尝试使用基本 HTML 标记创建每个游戏组件。只需少许 CSS 知识,就可以快速创建商铺和聊天系统这样的 UI 组件。
下面到了一个有趣的部分:太空船。对于 HTML 实现,可以创建一个简单的 DIV 元素,为太空船附加一个背景图像,添加元素来显示玩家姓名和健康状况等信息。到目前为止,用于这些组件的代码完全源于示例的 HTML 和 CSS。
清单 9 显示了太空飞船所需的一些 CSS 和显示在其下面的文本。您稍后要在 DOM 渲染函数中更新包含 player 类的 DIV 位置来完成动画。
清单 9. 样式化玩家的太空船、姓名和健康状况的 CSS
.player {
position: absolute;
width: 100px;
height: 100px;
}
.text-under-ship {
position: absolute;
top: 100px;
left: 0;
width: 100px;
}
.name {
font-family: Georgia;
font-size: 15px;
font-weight: bold;
color: red;
}
.health {
font-family: Georgia;
font-size: 10px;
color: yellow;
}
|
此时您可以开始使用处理游戏逻辑所涉及的代码了,如 清单 10 中所示。代码主要集中在 JavaScript 的 Game 对象中。它会负责处理用户输入,并调用相关组件的 update 和 render 函数。
清单 10. 用于驱动应用程序的
gameLoop 函数
gameLoop: function() {
// calculate time elapsed since last update
var currentTime = new Date().getTime();
var elapsed = currentTime - this._previousTime;
// call updates
Ship.update(elapsed);
// call renders
if (this._canvasRendering) {
CanvasManager.render();
Ship.renderCanvas();
} else {
Ship.renderDOM();
}
// store current time as the previous update time
this._previousTime = currentTime;
}
|
示例的一个重要组成部分是 Ship 类的 update 函数。这些函数负责管理和更新渲染代码所使用的太空船的速度、方向和位置。
到目前为止,不管渲染方法是什么,创建的大部分内容都是可重用的。清单 11 进一步探索了 DOM 与 canvas rendering 函数之间的区别。
清单 11. HTML
rendering 函数
renderDOM: function() {
var player = jQuery('#player');
player .css({
left: this._position.x,
top: this._position.y
});
var rotationTransform = 'rotate(' + (this._rotation / 100 * 360) + 'deg)';
var ship = player.find('#ship')
.css('transform', rotationTransform )
.css('-webkit-transform', rotationTransform )
.css('-moz-transform', rotationTransform )
.css('-ms-transform', rotationTransform )
.css('-o-transform', rotationTransform );
}
|
清单 11 中的代码最少,因为它使用 jQuery 来定位太空船并设置旋转。要实现真正的跨浏览器兼容性,必须设置各供应商风格的 transform 属性。
HTML 方法最终实现起来相对简单,不怎么复杂,且只需少量代码。它的劣势在于,鉴于渲染 DOM 元素的浏览器的开销大且实现可接受帧速的可能性较低。
在使用 canvas 之前,您需要引用要使用的 canvas 对象。在此之后,您需要初始化想要使用的上下文。这是本文之前讨论过的上下文,是使用 canvas 元素的手段。
使用一个简单的 canvas manager 类,如 清单 12 中所示。
清单 12. canvas
manager 类
var CanvasManager = {
canvas: null,
context: null,
_size: null,
init: function() {
this.canvas = document.getElementById('canvas-game-area');
this.context = this.canvas.getContext('2d');
this._size = {
x: this.canvas.width,
y: this.canvas.height,
}
},
render: function() {
this.context.clearRect(0, 0, this._size.x, this._size.y);
}
}
|
在大多数应用程序中,每当您想要更新 canvas 上渲染的内容时,必须首先清除之前渲染的内容,获得一个干净的显示区。您可以使用 clearRect() 函数实现这一点,如 清单 13 中所示。
清单 13. canvas 渲染函数
renderCanvas: function() {
var context = CanvasManager.context;
// save the context to prepare for our
// upcoming translation and rotation
context.save();
// translate the canvas to the ship's center position
context.translate(
this._position.x + 50,
this._position.y + 50
);
// rotate the canvas to show the angle the ship is pointing
context.rotate(this.getRotationInRadians());
// draw the ship with an offset of half the height
// and width to center the image
context.drawImage(
this._displayImage,
-50,
-50
);
// restore the context
context.restore();
this._playerName.render();
}
|
清单 13 中的代码显示了 canvas 方法的一些优势和劣势。主要优势是能提高性能。当您有多个组件要渲染时,会扩大效果,如 图 8 中所示。
图 8. 增加要渲染的太空船的数量以扩大性能差异
示例目前在渲染相互叠加的 50 艘太空船(参见 参考资料 中的工作示例)。显然,HTML 版本滞后不少。切换到 canvas 版本能够显著提高性能。
两个方法的代码量明显不同。对于 HTML 版本,我们使用 jQuery 通过一个函数调用来完成太空船的定位。而在使用 canvas 方法时,就需要多用一些代码。
在 清单 13 中,还有另外一个用于绘制玩家姓名的调用。在 HTML 方法中不需要这个调用,因为您要安置一个同时包含太空船和附加文本的元素。不过在 canvas 中,就没有这么方便。为了完成玩家姓名的渲染,该示例使用了 CanvasText 类,有效地将渲染太空船及其附加文本的所有工作都移出 DOM,然后将这些工作移入 canvas。
在这个由两部分组成的 系列文章 中,我们探讨了选择 canvas 还是以 HTML 为中心的架构的标准。在本文中,我们了解了动画考虑事项,以及如何克服文本渲染限制。本文中的一些示例展示了如何将这些概念集中起来,提供了对 canvas-HTML 混合架构的不同方法的洞察。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 本文的源代码 | canvashtmlpt2sourcecode.zip | 20KB | HTTP |
学习
CanvasText类的 工作示例。CanvasText类的自动换行 工作示例。- 本文太空射击游戏的 完整工作示例。
-
相互叠加的 50 艘太空船的 工作示例。
- jQuery:更多地了解这个快速而简洁的 JavaScript 库,它能够简化 HTML 文档遍历、事件处理、动画和 Ajax 交互,从而加速 Web 开发。
-
jQuery
Fundamentals(Rebecca Murphey,2010 年):阅读这一 jQuery JavaScript 库综述。
-
jQuery Events API:更多地了解在用户与浏览器交互时注册行为、使其生效的方法。
- “使用 HTML5 canvas 绘制精美的图形”(developerWorks,2011 年 2 月):阅读本文,了解如何使用 canvas(一个具有冲击力的简单 HTML 元素)增强您的 Web 页面。
-
HTML5 Canvas:观看该演示,其中侧重于 canvas API 的使用,展示给您如何绘制一个非常简单的动画。
- “HTML5 基础:第 4 部分:点睛之笔 Canvas”(developerWorks,2011 年 7 月):更多地了解 HTML5 的变更和 HTML5 canvas 元素。
-
Canvas
Pixel Manipulation:观看来自 Safari Dev Center 的该演示,了解如何管理 canvas 来开发有效的可视对象。
-
WHATWG:探索这个开发人员社区,该社区使用 W3C 来调优 HTML5。
-
Canvas 教程:查阅来自 Mozilla 开发人员的这一教程,了解如何在您的 HTML 页面中实现 canvas 元素。
-
HTML5 Canvas 参考书:使用 W3Schools.com 上该参考书中的练习加强您的 canvas 知识。
- developerWorks Web 开发专区:查找介绍各种基于 Web 的解决方案的文章。浏览 Web 开发技术库,获取各种各样的技术文章和技巧、教程、标准以及 IBM 红皮书。
- developerWorks 技术活动和网络广播:随时关注这些活动中的技术。
- developerWorks Live! 技术讲座:获取最新的 IBM 产品、工具以及 IT 行业趋势的资讯。
-
developerWorks 演示中心:那里提供了面向初学者的产品安装和设置演示,以及面向经验丰富的开发人员的高级功能。
- Twitter 上的 developerWorks:立即加入,了解 developerWorks 上的活动信息。
获得产品和技术
-
jQuery:是一个流行的 JavaScript 库,可简化 HTML 文档遍历、事件处理、动画和 Ajax 交互,从而加速 Web 开发。
-
Kibo:是专为即时跨浏览器键盘事件处理而编写的另一个流行的库。
-
IBM 产品评估版:下载或 在线试用 IBM SOA Sandbox,并开始使用来自 DB2、Lotus、Rational、Tivoli 和 WebSphere 的应用程序开发工具和中间件产品。
讨论
- developerWorks 社区:浏览开发人员驱动的博客、论坛、群和维基,并与其他 developerWorks 用户进行交流。
- 找到其他 对 Web 开发感兴趣的 developerWorks 成员。
