JavaScript高效图形编程(修订版)

978-7-115-38204-7
作者: 【美】Raffaele Cecco
译者: 徐鹏飞
编辑: 傅道坤

图书目录:

详情

本书讲解了如何使用JavaScript、jQuery、DHTML和HTML5的Canvas元素分别为计算机和移动设备创建富Web应用程序。通过本书,读者可以掌握设计街机游戏、DHTML特效等的方法,并掌握如何利用JavaScript创造新的用户体验。

图书摘要

版权信息

书名:JavaScript高效图形编程(修订版)

ISBN:978-7-115-38204-7

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Raffaele Cecco

  译    徐鹏飞

  责任编辑 傅道坤

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315

Copyright © 2011 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2012. Authorized translation of the English edition, 2011 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体字版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


本书是一本具有很强实操性的JavaScript图书,全书共分10章,涵盖的主要内容有:JavaScript的面向对象机制、JavaScript性能优化、jQuery和ExtJS库、高级UI设计、Web游戏开发、面向移动设备的开发、图形编程知识等。

本书适合有一定Web开发经验和JavaScript基础的开发人员学习。


Raffaele Cecco是欧洲视频游戏产业的资深程序员。他曾在伦敦King of the Jungle软件工作室任技术总监,其客户包括美国孩之宝玩具(Hasbro)公司和英国维珍(Virgin)集团。他使用过各种Web开发技术,并开发过零售电子商务系统。


本书封面的动物是一只蛮羊,或称鬃羊、巴贝里绵羊。

蛮羊是一类相对较大的羊科动物。它源于北非,现在还分布在西班牙东南部、美国西南部以及墨西哥部分区域。这些居住在沙漠之中的食草动物也被称为鬣羊。

蛮羊栖息在炎热荒凉的岩石和沙土地带,身体所需水分多来自于吃的各种植物。它们硕大的弯弯的角储有丰富的血液供应,使它可以在炎热干燥的沙漠中降温。除了特殊的角外,蛮羊从它的喉部到前胸再到前肢都长着柔软的长毛,其毛色为黄褐色。

蛮羊和其他沙漠中的动物一样,在白天寻找遮阳处,在较凉爽的黎明和傍晚最为活跃。它们擅长攀登和跳跃,可以在极端陡峭的斜坡上攀登跳跃,这使得它们极难被捕猎。由于它们的生活区域内少有高大植物藏身,因此它们主要依靠其表皮颜色骗过捕食者。在北非,它们曾经的捕食者有狞猫、狮子、北非豹,而如今人类才是它们的主要威胁。

尽管不易被捕猎,但在非洲,人类的捕猎还是让蛮羊的数量急剧减少。20世纪50年代,蛮羊被引入美国西南部,曾使其数量有短暂回升。蛮羊的数量目前在5000到10000之间,而且由于捕猎和丧失栖息地,预计在未来15年内数量将下降10%。因此,蛮羊已被列入国际自然和自然资源保护联合会的濒危物种名单。

封面图片来自于Riverside Natural History。


O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年开始,O’Reilly一直都是前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和发扬光大。

O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了Make杂志,从而成为DIY革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。O’Reilly的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业的革命性思想。作为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无论是通过书籍出版,在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的理念——信息是激发创新的力量。

业界评论

“O’Reilly Radar博客有口皆碑。”

——Wired

“O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。”

——Business 2.0

“O’Reilly Conference是聚集关键思想领袖的绝对典范。”

——CRN

“一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。”

——Irish Times

“Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照Yogi Berra的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去Tim似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也不错。”

——Linux Journal


作为资深的视频游戏开发人员,我已经习惯于和高性能的编程语言和硬件打交道,因此刚开始我并没有对JavaScript进行图形编程有太高的期望。不过后来发现,实际上JavaScript是一个优秀和高效的编程语言,而且随着更好的浏览器支持、本身的性能提升,以及新的工具库加入,JavaScript还在不断变好。JavaScript结合了HTML5 Canvas等特性,给Web开发人员提供了真正可以不用Adobe Flash等插件的方案。而WebGL等特性则为使用JavaScript和浏览器进行图形编程描绘了非常美好的未来。

这本书的目标读者需要具备一定的JavaScript知识,并且想要学习真正的Web图形编程,而不仅仅依赖于jQuery这样的库做一些动画特效。本书中涵盖了下面这些内容:

本书将带你了解不同的图形编程技术,你可以进一步探索自己感兴趣的领域。

多做尝试,你会获得很多乐趣!

打算阅读本书的读者应该对网站和Web应用开发,特别是JavaScript,具有一定知识和使用经验。

为了方便开发和表达,本书的许多示例代码都使用了jQuery。一般来说,本书中用到的所有外部库及其文件都可以从谷歌等可靠的内容分发网络获取。

另外,本书用到了一些基础的数学知识,包括向量和三角函数。

本书节奏较快,读者从第1章中就可以看到第一个图形编程的示例。

剩余章节涉及多个图形相关的技术,这些技术可以给你的Web应用增加视觉冲击力和交互性。

讨论交互式图形的书无法避开视频游戏。本书中将开发一个完整的视频游戏应用,并讨论相关的子图和滚动等技术。

本书每章的内容可以总结如下。

第1章,代码重用和优化

本章讨论JavaScript面向对象编程技术,以及图形应用中涉及的代码优化(包括jQuery优化)。本章甚至将介绍如何使用鲜为人知的JavaScript位操作符进行性能优化。

第2章,DHTML基础

本章展示了如何使用普通的DOM操作(DHTML)创建图形应用。我们将在本章开发一个适用于游戏和其他场景的子图系统,并将其以jQuery插件的形式封装。

第3章,滚动

本章首先讨论了CSS滚动技术,包括视差特效。然后本章将介绍基于JavaScript的滚动技术,以及基于块的视差卷轴特效。我们还将介绍一个强大的地图编辑器,用于创建基于块的地图。

第4章,高级UI

本章覆盖了jQuery UI和Ext JS两个UI库。我们将探讨两个库的不同工作方式和各自适合的应用类型。另外,我们还将构建一个三维旋转木马的示例。

第5章,JavaScript游戏介绍

本章演示了如何用开放Web技术,而不是Flash插件来构建有趣的Web游戏。我们将通过开发一个怀旧的视频游戏来说明我们讨论的技术。

第6章,HTML5画布

本章通过许多示例来深入介绍Canvas元素,包括如何使用Canvas和WebSockets创建一个图形化的聊天应用。其中涉及的画布主题包括:绘制、描边、填充、渐变、递归绘制、位图和动画。

第7章,游戏和模拟中的向量

本章介绍图形应用和游戏中广为使用的二维向量。代码示例包括大炮和火箭的模拟。

第8章,谷歌可视化

本章使用谷歌图表工具来对多种数据进行可视化,从基本的饼图到仪表图。本章不仅介绍了静态的可视化图形,而且覆盖了交互式的可视化图表,以及必要的数据格式化技术。

第9章,使用jQuery Mobile为移动设备开发

本章描述了jQuery Mobile,一个基于jQuery的、面向移动设备的开发框架。jQuery Mobile可以将普通的HTML页面转化为交互式和动画式的手机体验。本章中的主要例子是一个使用jQuery UI、面向移动设备的图形化滑动解谜游戏。

第10章,用PhoneGap创建Android应用

本章介绍如何使用PhoneGap将Web应用转换为手机的本地应用。本章解释了如何安装和配置PhoneGap来创建本地Android应用。在此之后,我们将把第9章的滑动解谜游戏转换为可以部署到移动设备上的本地应用。

提示

这个图标用来强调一个提示、建议或一般说明。

警告

这个图标用来说明一个警告或注意事项。

本书中提到一些有用的网站和页面,通常除了页面URL外,还会提供页面名称。

因此你可以选择直接输入URL或者通过搜索引擎搜索页面名称,找到相关页面,

可以在地址比较复杂,或页面地址被改变时使用后者。

本书包含许多代码片段、示例和一些完整充实的应用。有时手动输入代码很麻烦,因此推荐从本书的代码库中复制代码。本书的许多代码中穿插了普通文本,直接从代码库复制代码可以避免你去拼接不同位置的代码。

在本书的HTML页面示例中,大部分使用HTML5文档类型:

< DOCTYPE html >

为方便起见,示例中的所有CSS样式都被直接嵌入HTML页面。在实际Web应用开发中,还是推荐使用外部文件保存CSS样式。本书的示例代码可以在 http://www.professorcloud.com/supercharged中找到。

本书绝大部分示例代码都可以在较新的浏览器上工作,比如:

有些例子甚至可以在IE6和IE7上工作。

这些例子在Windows XP、Windows Vista和Windows 7上进行了完整测试,在iOS上进行了部分测试。理论上,这些例子也应能在上述浏览器的Linux版本上工作。

画布(Canvas)标签的使用则限于支持画布的浏览器,对IE来说,只有IE9可以(无需额外插件或库)直接支持。

有少量的例子需要特殊的环境,比如手机开发环境(PhoneGap)、服务器语言(PHP)或特殊浏览器。

如果是这种情况,书中会提到相关环境的设置和配置。

Safari在线图书是一个按需订阅的数字图书馆。它有不少于7500本技术和创意相关的书籍和视频供你参考和搜索。

通过订阅,你可以在线阅读任何页面或视频,甚至可以从手机或移动设备上在线阅读。

你可以在书籍出版前访问到它们,并给作者发送反馈。其他功能还包括:复制和赋值代码、组织收藏夹、下载和标记章节、做笔记、打印等。

O’Reilly Media已经将本书英文版上传到Safari在线图书服务。在http://my.safaribooksonline.com上免费注册,你就可以访问本书所有章节以及类似主题的书籍。

如果你想就本书发表评论或有任何疑问,敬请联系出版社:

美国:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们还为本书建立了一个网页,其中包含了勘误表、示例和其他额外的信息。你可以通过如下网址访问该网页:

http://www.oreilly.com/catalog/9781449393632

关于本书的技术性问题或建议,请发邮件到:

bookquestions@oreilly.com

欢迎登录我们的网站(http://www.oreilly.com),查看更多我们的书籍、课程、会议和最新动态等信息。

Facebook: http://facebook.com/oreilly

Twitter: http://twitter.com/oreillymedia

YouTube: http://www.youtube.com/oreillymedia

以作者一己之力出版一本书几乎是一件不可能的事情,在此我想特别感谢为本书做出贡献的人们。


在HTML5 Canvas、SVG和Flash等现代浏览器技术的背景下,DHTML今天看起来有点过时。不过,就像龟兔赛跑中的龟,当更令人激动的方法不能保证可用的情况下,DHTML总是那个更可靠的方案。

实际上,很多时候你只需要DHTML就够了;使用其他方法往往是因为开发者“想要”而不是“需要”。休闲游戏、图像缩放和许多其他特效都不需要借助其他“强力工具”就能完美实现。jQuery这样的库还能使其操作起来更简单。熟练的DOM操作技术加上一点点想法就能保证DHTML图形的快速和流畅。

在本章,我们将用vanilla JavaScript和DHTML开发一个快速sprite系统。出于兼容性考虑,我们会避免使用语言的最新特性,而集中于核心JavaScript的有效使用。

在计算机图形学中,sprite是可以用软件控制移动的二维比特图对象。在三维多边形图形学之前,视频游戏几乎无一例外的使用sprite来生成可移动的角色。如今,移动设备上的休闲游戏和其他的用户界面效果等,引起了sprite图形的复兴。你可以用DHTML来模拟sprite功能。下面章节中,我们将创建一个用于不同应用的DHTMLSprite对象。尽管创建sprite效果有更新、更快的方法,如HTML5 Canvas元素,但普通的DHTML可以提供不错的浏览器兼容性,在许多情况下作为Adobe Flash的替代方案是完全可行的。

提示

本章中的sprite和CSS sprite是有区别的。CSS sprite是一个流行的Web设计技术,指的是仅通过改变HTML元素的CSS背景位置,使得元素显示一个大背景图像的一小部分,一般用于实现动画效果。在计算机图形学术语中,这叫做动态纹理坐标。本章提到的sprite,还是取其原意:一个移动的图形对象。同时我们也将用到CSS sprite技术来改变其图像。

DHTMLSprite应该足够灵活以用在不同应用中,并提供下列功能:

没有动画的sprite很没劲,因此我们需要一个简洁的方法来改变sprite中所用的图像。尽管img元素似乎是一个很明显的选择,但它需要对每个动画帧载入不同的图像文件。有一个更好的办法可以使用少量的图像文件,而处理多个sprite图像。

CSS的background-position(背景位置)属性使得HTML元素(如一个div)可以显示图像的一小部分。因此一个大图像可以作为许多小sprite图像的容器。要使用这些sprite图像,我们必须定义background-position属性在div内的水平和垂直位移,以及宽和高。但这种动画方式并不直接,而需要技巧。最好是通过简单的索引就能引用到sprite图像。比如在图2-1中,组成一个齿轮动画的5幅图像可以用索引0、1、2、3和4表示。而第一个正方形用索引5表示,依此类推。

我们需要将索引转化为容器图像内的像素位移。一种方法是手动创建一个表格来记录sprite图像索引和对应的像素位移。尽管这个方法很有效,但手动输入和更新这些位移将很枯燥。更好的方法是通过计算得到这些位移。

将索引转换为水平和垂直像素位移只需要很简单的算术。在图2-1中,容器图像是256像素宽,每个sprite图像(底层除外)是64像素的正方形。像素位移可以用JavaScript这样计算:

注意计算出的值是负数。想象div元素是在对准第一个齿轮图像(索引为0)、宽与高各64像素的正方形。为了显示索引为1的下一张图像,容器图像必须向左移64像素(负水平位移)。如果要显示索引为4的最后一个齿轮图像,容器图像必须向上移64像素(负垂直位移)。

图2-1 嵌入到一个容器图像中的动画图像,每个虚线格子为32像素宽、高的正方形

如何处理不同大小的sprite呢?在图2-1中,在容器图像底部有一些更小的32像素宽、高的sprite图像。

决定像素位移的计算和之前一样,不同的是sprite大小改为32像素:

考虑到现在的sprite大小是32像素,图2-1中第一个32像素的sprite图像(底行第一个小黑圈)的索引为32。只要sprite图像的边缘坐标是它们大小的倍数,就可以使用索引计算的方式。

提示

图2-1中的容器图像是一个32位PNG文件,支持百万颜色和一个用于透明度的alpha通道。不过,32位PNG不适用于IE6,因为透明区域会变成不透明的灰色。一个解决方案是将图像存为8位的调色板PNG。这可以在IE6中正确显示,不过半透明区域会完全消失并显示粗糙的边缘。

将所有DOM操作细节,封装在DHTMLSprite中,隐藏在使用它的应用之外,会使代码更简单更易维护;应用可以集中于应用逻辑而不是画图细节。由于应用逻辑和画图细节的分离,将应用转为另一个画图方法如HTML5 Canvas元素或SVG变得更简单,甚至可以使应用程序根据浏览器能力选择合适的画图方法。

重复的增删和销毁DOM元素对性能会有不利的影响。为了降低性能影响,可以初始化一个隐藏的sprite列表。当需要sprite时,你可以将其从列表中取出并使其可见,而不是真的在DOM中插入新的东西。当sprite不再需要时,你可以将其隐藏并放回列表中。在DHTMLSprite中提供一个show和hide方法将使应用实现这项技术。

如果要永久地移除一个DHTMLSprite,应移去其DOM元素并进行相关的其他清理工作。

与其将若干单独的参数传给sprite,不如将所有设置参数放入叫做params的对象传入。除了避免参数次序的麻烦之外,还使从DHTMLSprite继承的其他对象,可以将它们自己的设置参数加入params中。任何使用params的对象都可以忽略跟它不相关的参数。表2-1显示了params对象中的参数。

表2-1 DHTMLSprite对象参数

参  数

描  述

images

图像文件的路径

ImagesWidth

图像文件的像素宽度

width

sprite的像素宽度

height

sprite的像素高度

$drawTarget

sprite将要附加于的父元素

下面,我们将params属性复制为局部变量。通过局部变量访问参数比通过params对象的属性要快。如此定义的局部变量是私有的,只能从DHTMLSprite内的方法访问。

接下来,我们在params.$drawTaget指定的DOM元素后加上一个sprite div元素。$element保存了一个对sprite div的引用。变量和属性名前的$符号用做提醒它们指向jQuery对象。elemStyle直接引用了sprite div的style属性,用于快速更新其CSS属性。

现在我们要给sprite div元素设置初始CSS属性。因为我们只进行一次初始化,因此可以使用方便的jQuery css()函数,尽管这也许不是改变属性最快的方式。

下面我们要在that中创建并保存一个DHTMLSprite对象。它包含了所有的sprite方法,注意that的方法可以访问前面定义的局部变量。这个that对象创建了一个闭包,它能永久访问前面DHTMLSprite函数里定义的变量。

draw方法更新sprite div元素的位置:

changeImage()方法改变显示的sprite图像。将索引转为像素位移的方法和前面描述的一样,但有些小的优化:

然后,我们定义隐藏、显示和移除sprite div元素的方法:

下面是一个基本的HTML页面,它初始化并显示了两个sprite。

为了创建sprite,我们需要一个包含初始化参数的对象:

下面创建两个sprite。因为两个sprite大小相等并使用同一个DOM画图区域,所以不需要改变任何参数。第一个sprite使用默认索引值0,而第二个sprite的图像索引值为5。

最后画出这两个sprite。图2-2显示了输出结果。

图2.2 画出来的两个sprite

这个应用中没有移动也没有动画,让我们在下一个例子中“动”起来。

下面的应用展示了sprite的存在价值:动画和移动。之前我们画了两个sprite,而没有控制它们移动。这个例子中我们定义一个新对象:bouncySprite,一个会反弹的DHTMLSprite。实现方法之一是在bouncySprite中创建一个DHTMLSprite,并将其作为单独的实例控制。更简洁的方法是让bouncySprite继承所有DHTMLSprite的能力,并添加自己额外的能力。在JavaScript中这种继承和增强很简单:

为了提高速度,我们用局部变量保存设置参数。这里的params对象也包含DHTMLSprite的参数,但这些和bouncySprite无关。表2-2显示了传入的参数。

表2-2 bouncySprite对象参数

参  数

描  述

x

像素x位置

y

像素y位置

xDir

x移动方向

yDir

y移动方向

maxX

最大x位置

maxY

最大y位置

animIndex保存了当前动画图像索引:

我们在that中创建和引用一个DHTMLSprite。params对象包含了其设置参数。

接着给that引用的DHTMLSprite实例加一个moveAndDraw方法,实际上就是创建一个bouncySprite实例:

通过增加xDir和yDir变量来移动sprite的x和y位置:

下面的代码根据xDir方向对animIndex变量进行增或减,接着用取余操作(%)将其维持在−4到+4之间。如果animIndex是负的,纠正到对应的正索引。

接着检查bouncySprite是否超过了maxX和maxY定义的范围。如果超过,对移动的方向取负,使bouncySprite弹回。

更新bouncySprite动画索引,并将其画到新位置:

返回在that中引用的bouncySprite实例,供应用程序使用:

定义了bouncySprite对象后,我们可以初始一些对象,并在setInterval()或settTimeout()控制下调用它们的 moveAndDraw()方法。更好的方法是创建一个对象可以初始化和处理任意数量的bouncySprite。这个对象可以叫做bouncyBoss。bouncyBoss可以传入两个参数,如表2-3所示。

表2-3 bouncyBoss对象参数

参  数

描  述

numBouncy

要初始化的bouncySprite个数

$drawTarget

bouncySprite要添加到的目标父元素

创建指定数目的bouncySprite,并放入bouncys数组中。每个bouncySprite给一个随机起始位置和移动方向(xDir和yDir),并根据$drawTarget的宽和高计算最大范围。

现在我们定义moveAll方法,它调用了bouncys数组中每个bouncySprite的moveAndDraw方法。每次移动,它创建一个setTimeOut来调用自己,实现连续的循环。

下面是使用新bouncyBoss对象的页面布局:

一个bouncyBoss创建了50个bouncySprite对象,并连续调用它们的moveAndDraw方法。图2-3显示了输出结果。

图2-3 绘制后处于动态的多个sprite实例

将bouncySprite转为一个jQuery插件,可以利用jQuery通过CSS选择器搜索并返回DOM 元素列表的能力。这个插件可以搜索任何元素并用 bouncyBoss 给它附上多个bouncySprite实例,并可以改变附加的实例个数和背景颜色。

将bouncySprite转为一个灵活的jQuery插件,实际没有想象得那么难。因为DHTMLSprite、bouncySprite和bouncyBoss对象是以模块化方式开发的,可以很顺利地转为jQuery插件结构。

下面这个单独的分号看起来奇怪,但它可以避免前面的代码遗漏分号可能导致的问题。通常这不会是个问题,因为JavaScript通过换行符将插件代码识别为新语句。不过,如果代码和插件是压缩的,空格和换行符也许会被删除。而插件也许会因为它和前面代码之间缺乏换行符而失效。

下面我们定义一个匿名函数。这将所有插件代码包成一个自给自足的语境。$是传入的参数,这里指的是全局jQuery对象本身(见插件最后一行)。现在,在插件中我们可以使用$()代替jQuery()来调用jQuery。传jQuery对象似乎没必要,因为它已经在全局定义了。不过,它可以避免由于外部代码(比如其他JavaScript库)重新定义$变量而导致插件不能使用$()调用jQuery。

为增强jQuery的能力,我们将对插件的引用保存在jQuery的fn属性中。如果另一个插件定义了同样的名字,可能会出现冲突。为避免这种情况,你应该起个有想象力的名字。比如,“zoom”很可能冲突,而“cloudZoom”则不太会冲突。

此处我们插入DHTMLSprite、bouncySprite和bouncyBoss的代码,插入时没必要做任何修改。因为它们以局部变量保存,所以是对插件私有的。

这个插件使用option对象的属性作为选项。用这种方式给插件传入选项比较灵活,因为它可以传入所有参数、一些参数或者空参数。jQuery的extend函数融合了option属性和$.fn.bouncyPlugin.defaults对象定义的默认option属性。option属性具有优先权,它们不存在时才使用默认属性。因为默认选项是公共的,一个应用可以通过在$.fn.bouncyPlugin.defaults中创建新的defaults对象来改变默认选项。

这个插件在找到的DOM元素列表上进行迭代,对每个元素执行一个匿名函数。在这个函数里,this指的是DOM元素列表中的当前元素。函数从this中创建一个jQuery对象,并存在$drawTarget中。option中定义的背景颜色被应用到$drawTarget中,option中指定的numBouncy则传入一个新的bouncyBoss实例。

下面是一个包含此插件的HTML页面。显示结果如图2-4所示。

在此处插入插件:

当页面准备好后,在指定元素上调用此插件,此例指的是任何有draw-target CSS类的元素。

接着,我们定义4个draw-target类的div元素:

关于jQuery的更多细节,请阅读jQuery社区专家撰写的jQuery Cookbook(O’Reilly;http://oreilly.com/catalog/9780596159788)。

图2-4 通过一个jQuery插件生成的多个bouncyBoss对象实例

本节将讨论如何在JavaScript中控制图形的更新速度以保证用户体验。我们想要图形有平滑流畅的移动,不要太快也不要太慢。用户计算机的性能会影响图形更新的速度。下面我们将讨论减少不同机器上速度差异的解决方案。

JavaScript的setInterval()和setTimeout()函数使你可以定期调用JavaScript代码。需要定期更新图形的应用,比如电脑游戏,几乎都离不开这些函数。

你可以将回调函数传给setInterval()来重复调用此函数:

注意执行bigFunction()花费20毫秒。如果循环间隔比这个值小呢?

看起来20毫秒的bigFunction()会在第一个回调函数返回之前被重新调用。实际上,新的回调将排在队列中直到前面的回调函数结束。

如果延时更短点呢?

可以预计每执行一个回调函数,若干个回调函数在排队。事实上,通常的行为是只有一个排队的bigFunciton()会激活。排队的回调函数会在第一个回调函数结束后立即执行吗?有可能,但不一定。其他时间和浏览器中运行的代码可能使得setInterval()回调函数被延时或丢弃。回调函数甚至有可能连续发生(比规定的间隔短),如果JavaScript发现一个时间窗口,可以清除队列。

这里想要说明的是:不能保证回调函数以指定的间隔执行。

setTimeout()在指定的延时后调用一个函数,和setInterval()类似,但可预见性更强。

这会在50毫秒后,调用一次bigFunction()。和setInterval()一样,这个延时仅仅是一个参考。

你可以用setTimeout()连续调用一个函数,其行为将比setInterval()更可预见:

每当bigFunction()结束,它设置另一个以自己作为回调函数的setTimeout()。

在这个例子中,尽管设置的timeout值比bigFunction()执行时间要短,回调函数只会在bigFunction()结束后再执行。实际上,执行的频率和下面使用setInterval()的代码类似:

Windows下的浏览器只有粗粒度的定时器。例如Windows XP的底层操作系统定时器提供15毫秒精度。这意味着Date()、setInterval()和setTimeout()等JavaScript函数不能提供可靠的15毫秒以下的定时。Google Chrome是例外之一,它将Windows切换到一个准确的定时器模式并提供1毫秒的精度。

提示

阅读下面的在线文章,可以深入了解JavaScript定时器这一主题:

  • http://ejohn.org/blog/how-javascript-timers-work/
  • http://ejohn.org/blog/javascript-in-chrome/

这里的要点是一个应用程序不应该依赖低于15毫秒(约1/64秒)的定时器。这个问题严重吗?一般情况下不严重。浏览器中不太可能或不应该运行对时间这么敏感的应用程序。动画也许会比预计的慢一点或快一点,游戏等应用程序中帧率也不是绝对的稳定。如果在一段时间内细致检查这些不精确的累加效果,也许可以看到一定的误差。不过,在通常情况下,比如玩游戏或看菜单特效时,这些误差是察觉不到的。

不过在使用Date()进行代码性能分析时要小心。下面的例子中,如果执行的代码太快结束的话,将得到不准确的结果:

一个更好的解决方案是在较长的时间段(如1秒)内循环执行代码,然后用期间完成的迭代次数来衡量执行速度。

前面的sprite实现,具体来说是移动sprite的代码,存在一个问题——不同的浏览器下动画和移动的速度(即帧率)不一样。比如2.8GHz的PC、Opera或Google Chrome等浏览器可以在移动100个Sprite时轻松达到50FPS(每秒帧数),Firfox也许能有30FPS,而IE8也许只有25FPS。如果考虑不同的硬件,帧率的差异会更大。

这对装饰性的动画和特效不是大问题,但游戏等应用程序需要一致的移动速度来保证可玩性。

为在不同的软硬件环境中保持速度一致,必须在sprite移动和动画涉及的计算中考虑帧率的不同。具体来说,一个以30FPS每帧移动两个像素的sprite,和一个以60FPS每帧移动一个像素的sprite看起来速度一样。这两者之间的主要视觉区别是30FPS sprite的移动不如60FPS sprite的移动流畅。不过至少看起来他们是以同样的速度在屏幕上移动。

为此,必须计算一个时间系数,并在移动和动画代码中使用。表2-4显示了一个例子。

表2-4 时间系数

目标FPS

实际FPS

时 间 系 数

60

30

2

60

15

4

30

40

0.75

50

50

1

很明显,时间系数=目标FPS/实际FPS。

为计算实际FPS,可以用JavaScript的Date对象记录当前时间(即开始时间,单位为毫秒位)。在执行所有应用逻辑后再记录时间(结束时间)。下面是代码:

如果CPU负荷过重,帧率会很慢。以6FPS、每帧10像素在屏幕上移动的sprite看起来不平稳,肯定不适合游戏。表2-5列出了帧率和流畅度的对应关系。

表2-5 FPS和流畅度

FPS

流 畅 度

<15

相当不平稳

15~20

勉强可接受

20~30

基本流畅

30~40

流畅

40以上

非常流畅

这不是说10FPS的低帧率毫无用处。对俄罗斯方块这样的游戏而言,这种帧率也许就可以接受了。

现在我们创建一个timeInfo对象,它将提供保持应用速度一致所需的所有功能。它接受一个goalFPS参数,即我们想要达到的目标FPS。如果达不到,函数将调整移动速度使其至少看起来达到了goalFPS。timeInfo对象中还提供了其他时间相关的信息。

下面的函数返回一个对象,其中包含getInfo()方法。getInfo()方法返回一个对象,其属性如表2-6所示。

表2-6 timeInfo.getInfo()返回的对象属性

属  性

描  述

elapsed

从上次getInfo()调用开始的毫秒数

coeff

在移动和动画计算中所用的参数

FPS

从上次getInfo()起所达到的FPS

averageFPS

从第一次getInfo()起所达到的平均FPS

averageCoeff

平均参数

paused变量表明这是在应用程序开始或暂停后,getInfo()第一次被调用。它保证在经过一个很长的暂停之后,getInfo()传回的值是良性的,并且不会返回一个非常大的值。

我们通过从上一次getInfo()中记录的oldTime,减去新时间,得到经过时间(elapsed time)。然后用经过时间来计算帧率。+new Date()语句等价于new Date(). getTime();:

然后返回一些有用的信息属性,如表2-6所示。

接着我们定义pause()方法,在暂停应用程序时都应调用此方法。

现在,我们可以在原始的bouncySprite和bouncyBoss代码中使用timeInfo对象了:

moveAndDraw方法现在接受时间系数作为参数。计算和原来相似,但使用了时间系数。changeImage()函数的参数应该是整数,但因为animIndex受时间系数影响,也许不是整数。为此,我们复制一个整数版的animIndex为animIndx2,并传入changeImage():

bouncyBoss对象现在要创建一个目标FPS为40的timeInfo实例(存在timer变量中)。moveAll()在每个迭代调用timeInfo.getInfo()得到时间系数,并将其传给每个bouncySprite实例的moveAndDraw()方法。注意只需要一个timeInfo实例即可,因为每个bouncySprite实例可以使用同一个系数。

即使对完全正当的跨浏览器代码,IE6也不能完全处理好。具体来说,IE6在缓存背景图像上有问题。当多次访问同一个背景图像时,IE6从服务器重新获取图像,而不是从本地缓存读取。在用背景图像实现动画的情况下,这显然会极大地影响性能。如果你认为还是有必要兼容IE6,可以采用这种变通方案:


HTML5最吸引人的一个特性就是画布(Canvas)元素。画布的形式是在页面上类似于div 的一个矩形区域,使你可以用JavaScript绘制复杂的图形。它最初是由苹果为在Mac操作系统上用Safari浏览器渲染用户界面组件和其他图形而开发的。苹果公司将画布相关的专利以WWW联盟(W3C)的免版税许可条款发布。也就是说,苹果将为在W3C HTML推荐范围内的画布提供免版税的许可。

本章介绍了画布的基本知识并用它来实现多种实际应用。除了阅读本章之外,你不妨考虑下面的书籍进一步增加这方面的知识:

画布是一个底层、立即模式的应用程序编程接口(API):

底层

画布提供了快速但相当基本的功能集。例如,矩形是仅有的原生形状。不过你可以通过JavaScript编程来增强其功能。

立即模式

当画布绘图指令被调用时,就立即被执行。这和SVG在绘图之前使用中间数据结构保存层级图形对象是不同的,画布没有这样的中间数据结构。这意味着,可以加入无限层的绘图操作而不影响性能——这对诸如像素图艺术包或其他精细的“层次”特效是特别完美的。

下面的画布示例会显示一个蓝色矩形:

(是否使用jQuery取决于个人的喜好)

画布的底层特性使其整洁和简单,而其速度非常适合于动态图形应用。任何熟悉位图图形编程的人使用画布时都会有宾至如归的感觉。

大部分流行的浏览器都支持画布元素,包括Firefox、Chrome、Opera和Safari。2010年7月1日,微软通过IE9开发博客宣布其最新的浏览器会支持画布。事实上,该公司甚至为画布支持提供了硬件加速。这个相对低调的声明掩盖了它的重要性:IE仍然持有大部分的浏览器市场份额,它对画布提供的支持对使用画布的开发人员是个好消息。然而IE9仅适用于Windows Vista和Windows7,它不支持Windows XP。所有的Windows用户都能享受画布,还需要一段时间。

画布为不同的应用,设计了一个小而精的矢量图和位图命令集。两者之间的区别是什么?

矢量图

矢量图形由直线和曲线的数学表示定义。你可以填充矢量形状或/和描绘其轮廓。矢量图形的关键优势在于它们可以缩放到任意大小而不损失质量:边缘和细节依旧锋利。矢量图最适合单色或渐变区域面积较大、细节密度较小的图像。最典型的有:图表、图形、旗帜、线路图和卡通风格的图像。因为其数学特性,JavaScript操作矢量图特别方便。

位图

位图图像(如无所不在的JPEG格式)是不同颜色的像素组成的网格。它们不能很好地进行缩放,当放大时将看到明显的方块,而缩小时将损失信息。这是因为单个像素不是被放大了就是被丢失了。有些画布实现可以通过使用模糊滤波来降低这种不良效应。位图最适用于有着大量细节的摄影风格的图像。

警告

无论如何生成图像,画布最终可视的输出结果始终是位图。如果你要利用矢量缩放的优势,你需要使用矢量图命令在新的尺度下重绘图像。仅仅使用浏览器交互或CSS来放大画布,其效果和放大位图图像一样:会有块状/模糊效应。

使用画布时有一些限制,其中一些和其底层特点有关:

有些人最初对苹果创建另一个浏览器图形标准持保留意见,他们可能认为SVG已经足够了。从表面上看SVG和画布提供类似的图形能力,但它们有一个根本的区别:SVG是一个高层的、基于XML的标记语言,可以通过创建XML元素属性来定义图像;而画布则提供了可以直接从JavaScript访问的绘图API。

你可以使用任何文本编辑器手动创建SVG XML,或将它从Adobe Illustrator或Inkscape等绘图软件中导出。以下的SVG例子显示了一个蓝色矩形:

要通过JavaScript操纵这个矩形,你需要访问a_rectangle元素并适当调整其属性。听起来很熟悉吗?就像HTML中那样,我们通过一个类似DOM的结构去定义视觉效果。试想假如我们需要1000个矩形?没错,我们必须插入1000 矩形元素到XML中。这种方式对需要更多编程的动态图形来说,不是特别有效或直观。

不过,SVG不用JavaScript就能带给你绘制和动画功能,而且有大量设计工具供你编辑SVG图形。由于现在IE9中也提供了基本的支持,当需要矢量图时,SVG是一个不错的解决方案。维基百科等网站就广泛使用SVG作为插图。

大多数网民都熟悉Adobe Flash。大量的在线广告内容、视频和游戏都使用了Flash。事实上,有很多网站是完全用Flash创建的。这是一个可以追溯到1996年的成熟插件,现在几乎所有系统上都安装Flash。不过 Flash也有自身的问题,HTML5(包括画布)的发展,可能预示着互联网富内容创建会有翻天覆地的变化。

HTML5和Flash的辩论,会引起很多争议。希望Flash长存的资深Flash开发人员自然会质疑HTML5能否取代Flash,而开放Web的支持者会说HTML5使Flash显得多余。

现实中,Flash很快消失是不太可能的。它太根深蒂固了,而HTML5在各个方面进展缓慢。然而,考虑到跨浏览器支持、熟悉和免费的开发工具,只有最乐观的Flash开发人员会忽略HTML5。不过,随着JavaScript性能的提升和工具库(如jQuery)的发展,以及考虑到画布等因素,已没有什么理由去以100%-Flash的方式去构建网站了,估计用不了多久这种网站就会停止出现。

画布完全由JavaScript控制,因此了解JavaScript是充分利用画布的前提。基于标记语言的方式无法来访问画布功能。不过倒是出现了画布导出器和转换器,它们可以生成绘制画布需要的JavaScript代码。这对JavaScript能力较弱的设计师来说是个极好的消息,对程序员来说也是如此,因为通过输入画布命令的方式手动创建精致的矢量艺术是枯燥且易出错的。

Adobe Flash CS5+(http://www.adobe.com/products/flash.html)

Adobe的Flash CS5+有一个画布导出器可以将部分Flash导出为JavaScript 画布源代码。这对想兼顾Flash和画布的开发者很有用。然而,由于该解决方案需要购买Flash创作工具,它对仅仅想开发画布的人来说可能不太划算。

Canvg(http://code.google.com/p/canvg/)

Canvg(如图6-1所示)是一个用画布绘制SVG数据的JavaScript库。不幸的是,其画布 JavaScript语句没有以任何形式保存下来,因此你必须始终包含Canvg库来绘制SVG。

SVG-to-Canvas(http://www.professorcloud.com/svg-to-canvas/ )

这个在线工具将静态的SVG转换为JavaScript的画布函数。它使用了一个修改版的Canvg库。

AI-Canvas(http://visitmix.com/labs/ai2canvas/

这个复杂的Adobe Illustrator插件(如图6-2所示)可以转换静态图片和动画。如果插件遇到不能转换的图像元素,它会把这些元素转换成位图。所有这些图像元素都被转化为可以进一步修改的画布JavaScript函数。

图6-1 Canvg

图6-2 AI-Canvas可以处理动画

下面的九节将讨论基本的画布绘图命令。

在网页中插入画布元素和插入任何其他HTML元素没有什么不同。

如果你不指定任何宽度或高度属性,默认大小为300×150像素。可以但不推荐通过CSS(例如,宽度:50%)改变画布大小。输出有可能是被扭曲或被缩放的,这取决于浏览器的实现。但是,你可以用CSS设置边框、边距和背景颜色,虽然这绝不会影响绘制到画布内容本身。坐标系统默认左上角为原点(0,0),因此绘制在坐标(10,15)的图案将定位在从左往右第10像素,从上往下第15个像素。

如果浏览器不支持画布, 将显示开始和结束之间的替代内容(fallback content)。理想的情况下,替代内容应该是画布所显示数据常规的文本或HTML表示。例如,画布中可能显示饼图,替代内容会显示一个普通表格。有的情况下,替代内容根本无法取代画布;游戏和绘图应用程序没有对应的文本或HTML表示。在这种情况下,替代内容应显示一个有用的信息,向用户解释:画布不可用,浏览器应升级。

单独放到页面的画布没有给我们任何的功能,它必须由JavaScript控制才能做些有用的事。你很少会看到没有id属性的画布,因为JavaScript代码通常用id属性来识别画布。通常情况下,JavaScript将这样得到画布的“句柄”变量:

我们必须从画布获得一个“绘图环境”后才可以使用绘图命令:

虽然不是正式的推荐,不过在画布例子代码中你会经常看到用ctx来代表绘图环境。

提示

画布还提供了一个3D绘图环境,使你可以访问目前处于试验阶段的WebGL接口。WebGL基于OpenGL ES 2.0的标准(OpenGL的削减版本),并通过JavaScript提供3D图形处理能力。在大多数浏览器的开发版本中都支持。OpenGL实际是一个的底层函数集,你仍然需要做大量的工作来创建一个3D应用程序。

在Web社区曾经有人怀疑JavaScript是否能在较复杂的3D场景中管理对象的层次;不管这些对象是不是由WebGL绘制,管理一个3D应用或游戏需要进行大量的计算。不过随着JavaScript性能的不断改善,大家对JavaScript越来越有信心,而且出现各种更高层的3D库,可以简化3D应用开发。所有这些库都是建立在WebGL之上的:

  • O3D(原本是一个插件,但现在是一个JavaScript库)

  • GLGE

  • C3DL
  • SpiderGL
  • SceneJS
  • Processing.js

画布内置的绘图形状非常有限,实际上只有矩形而已:

不过这个限制不算大问题,因为我们可以用直线和曲线组合定义的路径来创建所有其他形状。

路径定义可以填充和/或使用大纲描边的形状。画布包括以下功能执行路径绘制:

函  数

描  述

beginPath()

开始新路径

moveTo()

设置路径的起始位置

LineTo()

定义从当前位置开始的直线

arc()

定义弧

acrTo()

定义从当前位置开始的弧

quadraticCurveTo()

定义从当前位置开始的二次曲线

bezierCurveTo()

定义从当前位置开始的贝塞尔曲线

closePath()

结束路径

stroke()

勾画路径

需要注意的是,“to”命令(lineTo()、bezierCurveTo()等)的结束位置也定义了下一个“to”命令的开始位置。你可以将“to”命令想象成用笔在纸上连续地画线(不离开纸面)。moveTo()命令则使你将笔离开纸面,并从其他地方重新开始画。

下面的示例使用线在左上角绘制一个填充三角形和描边三角形(如图6-3所示),假设画布尺寸为500×500像素:

图6-3 填充和描边三角形

注意你不需要为填充三角形执行closePath()命令,因为fill()自动关闭路径。

提示

画布允许你指定分数像素位置。你可能觉得这很奇怪,因为像素是不能分割的单元元素。实际上画布是使用了抗锯齿技术给人以分数像素位置存在的假象。这可以使视觉上边缘更干净,移动更平滑,尤其当移动速度较慢时。

你可以使用arc()命令来绘制圆,或圆的部分:

参数如下:

  x,y

    圆心位置。

  radius 

    像素半径。

  startAngle,endAngle

绘图将“横扫”这两个角度之间。角度以弧度定义,2π弧度(约6.283)相当于360°。

antiClockwise

绘制弧线的方向。

下面是弧度转换所需的计算:

下面的代码绘制了两排圆,每个圆的开始角度为0弧度,endAngle逐渐增加。上一行以顺时针绘制,下一行以逆时针绘制(如图6-4所示)。

图6-4 结束角度递增的弧,第一排是顺时针,第二排是逆时针

提示

如果没有使用 moveto()来设置开始位置,将从上个弧的结束位置开始画新的弧。

arcTo()命令和arc()命令类似,但是以不同的方式指定曲线:

曲线由两条直线定义,第一条直线是从当前位置到第一个点(x1,y1),第二条直线是从点(x1,y1)到点(x2,y2)。这样定义一条曲线是为了方便创建直线之间的圆角。曲线将占据两条直线相交的角。

下面的函数绘制(w,h)大小的圆角矩形。圆角的半径由参数cr定义。

图6-5显示了使用不同圆角半径(从0开始,依次增加2π弧度)调用此函数的结果。

图6-5 使用arcTo()命令绘制的圆角正方形

以下的页面代码显示了如何在循环内调用drawRoundedRect()给出图6-5所示的输出:

quadtraticCurveTo()和bezierCurveTo()命令使我们绘制一个或两个控制点的曲线。控制点可以使曲线弯曲,从而得到arc()和arcTo()命令不能绘制的非对称曲线。这种类型的曲线经常可以在Photoshop、Freehand和Inkscape等矢量绘图工具中见到。在JavaScript中使用这些曲线可能比较棘手,因为我们不能直接看到控制点的位置和它们对曲线的效果。

以下页面代码分别在画布顶部和底部显示了二次曲线和贝塞尔曲线(如图6-6所示)。它还显示了可以用鼠标拖动的控制点,使用了jQuery UI的“可拖动”功能来移动控制点。请注意控制点实际上是普通的div元素,而不是画布路径。以这种方式组合画布和普通DOM元素不仅完全合法,而且非常有用:

图6-6 一个控制点的二次曲线(顶部),两个控制点的贝塞尔曲线(底部)

我们可以用drawImage()命令绘制位图图像。这个命令可以有3、5或9个参数。在所有情况下,第一个参数指定图像源以提供绘制的像素数据。图像源可以是用image()函数载入的图像、普通的<img>标签、甚至是另一个画布或<video>标签。这种指定图像源的灵活性为你提供了巨大的创造潜力。例如,图6-7中为制造“爆炸”效果使用<video>标签作为图像源,而图6-8用一个大图的随机部分创建自然的星云动画。

图6-7 使用<video>标签作为drawImage()的位图图像源;每个小的爆炸片内有一小部分视频

警告

如果使用drawImage()时遇到性能问题,确保图像源是另一个画布标签可能是有益的。这防止了某些浏览器上的图像转换开销。例如,图6-7中的视频“爆炸”效果将视频图像复制到画布元素,再使用drawImage()分为小片。

图6-8 叠加和放大大图中的随机部分来创建一个自然的星云动画特效

3个参数版本的drawImage()最容易使用,它只简单将图像源复制到画布的(x,y)坐标。位图的宽度和高度由源位图本身决定:

5个参数版本允许你指定目标高度和宽度,使你能够缩放图像到所需的大小:

9个参数版本允许你复制图像源的一部分,其中参数2~5指定源图像中的源矩形块,参数6~9指定内绘制在画布上的目标矩形:

警告

如果你使用 drawImage()分数像素位置,有些浏览器(特别是Firefox和Opera)可能遭受严重的性能损失和其他奇怪的故障。为了避免这些问题,确保将位置四舍五入为整数形式:

在前面的例子中,我们使用了stroke()命令来创建一个默认黑色的1像素宽的路径轮廓。你可以使用lineWidth和strokeStyle属性更改轮廓的风格,并用fillStype属性指定内部填充颜色。下面是一个加入这些属性的圆角矩形代码(如图6-9所示):

图6-9 设置为 lineWidth=4 , strokeStyle = ‘#f00’及fillStyle = ‘#0f0’

请注意描边比指定的4个像素薄。这是因为描边以路径为中心,而内部的两个项目被绿色填充隐藏了。增加线条的宽度才能获得所期望的结果。

你还可以同alpha值指定颜色的透明度。alpha值的范围从0(完全透明)到1(完全不透明)。除了为当前描边或填充命令设定本地alpha值外,你还可以使用globalAlpha属性给所有描边和填充设置alpha值;本地alpha值将被乘以globalAlpha属性。

此外,你还可以用globalApha属性给位图设置透明度。位图中所有像素的alpha值将被乘以globalAlpha属性。PNG图像包含了一个alpha通道实现透明效 果,图像中alpha为0.5的像素,用globalAlpha为0.5绘制将实际得到alpha为0.25。

警告

绘制alpha值小于1的元素将涉及浏览器额外的工作,因为浏览器必须进行额外的计算来显示每个像素的最终颜色。无论画布实现是否使用硬件加速都是如此。当设计你的应用程序时,考虑是否绝对需要使用alpha值,尤其是当绘制速度很重要时。

如果指定(或通过globalAlpha计算得到)alpha值为0(完全透明),浏览器可能仍然会尝试绘制。这涉及不必要的工作,可能带来性能问题。尽量避免画alpha值为0的元素。

我们用CSS3定义画布中的颜色。下面这些声明语句中的任意一条都可以设定填充颜色为红色:

除了纯色的填充和描边外,你可以使用createLinearGradient()或createRadial Gradient() 命令指定颜色渐变。

提示

用createLinearGradient()创建渐变,需要一些设置:

1.使用createLinearGradient()创建一个CanvasGradient对象。传入的4个参数定义了将绘制渐变颜色的线。

2.沿着这条线添加颜色点,其中0表示线的开始,1表示线的末尾。定义渐变你必须至少设置两个颜色点。

3.使用CanvasGradient对象作为填充或描边的样式。

我们使用CanvasGradient addColorStop()命令添加颜色点。此命令接受0和1之间的值,其中0表示渐变的开始,1代表结束。下面的代码定义了一个从黑色到白色再到红色的渐变:

下面的函数产生一个渐变的天空和草地效果(如图6-10所示):

图6-10 用createLinearGradient()创建的渐变天空和草地效果

我们调用这个函数时要传入一个画布环境。

如果绘制的矩形和画布的大小不同,会发生什么?我们将前面的函数最后一行替换为下面一行,使得图6-11显示了1/4个画布大小的矩形。

图6-11 与图6-10相同的渐变,但是使用的是更小的矩形运行绘制

请注意绘制矩形的行为像在CanvasGradient对象定义的渐变上开一个“窗口”。

createRadialGradient()命令则可以创建一个跨越两个圆的径向渐变。该命令接受指定圆心和半径的两个圆:

通常情况下,两个圆心在相同位置,而且第一个圆在第二个圆之内。内圆的所有区域都用addColorStop()定义的第一种颜色填充;这种颜色将渐变到由addColorStop()定义的最终颜色,并填充从内圆到外圆的区域。而外圆外的区域也是由addColorStop()定义的最终颜色填充。

下面的函数创建一个太阳,使用径向渐变,纯白色变淡黄色透明。在天空渐变和草地渐变上放上这个太阳,即可得到一个阳光明媚的效果(如图6-12所示):

图6-12 用一个径向渐变创建太阳特效

使用JavaScript(或jQuery等JavaScript库)时,你可能习惯操作页面元素的位置、大小、图像或色彩,并看着它神奇地直接忘记其旧属性,而更新其新属性。按这个逻辑,如果我们不断增加一个元素的x和y位置,可以创建将此元素移到页面右下角的动画效果。但如果我们在画布上以这种方式移动方块的话,结果可能让我们很意外(如图6-13所示):

图6-13 简单地在画布上移动方块得到的结果

请记住画布是一个低级别和立即模式系统:每次循环在屏幕上绘制的另一个矩形,都会叠加于上次迭代的矩形之上。这样的结果是一个大涂鸦,而不是一个动画。为创建在页面上移动的方块动画,我们需要稍稍多做点工作:

1.存储方块的初始(x,y)位置;

2.清除画布;

3.更新方块的(x,y)位置;

4.在新的位置绘制方块;

5.等待一小会儿;

6.循环回到第2步。

基本上所有的位图动画系统都在幕后做类似上述循环的操作。在某些情况下第2步是可选的。例如,如果背景完全被实色、渐变或位图图像填补,那就没有必要清除它。第5步是必要的,这使用户有机会看到动画,并让浏览器有时间去做其他事情,否则该浏览器将立即被冻结。通常情况下大约20~50毫秒的延迟比较合适。以下页面会呈现我们想要的效果:

立即模式绘图的好处之一是不需要创建和操作什么中间数据结构。在立即模式绘图中,你可以立即忘记刚刚执行的绘图命令,或者叠加多个绘图命令。这对在画布中使用高密度、递归的绘图函数(如不规则碎片形)特别有用。递归函数指的是调用自己的函数。通过将函数的上次结果返回给函数本身,我们创建了软件的反馈环路。下面的例子递归调用自己10次:

这个例子虽然很简单,但它演示了递归函数的两个重要方面:

下面我们将尝试用更有趣的东西,具体说是用一点三角函数和一点随机元素,来代替简单的递归递减。图6-14显示了递归调用简单的画布画线命令得出的树。递归图形函数的一个显著特点是看起来比较自然,我们可以看到分支的末梢非常精细。这是由于前面提过的分数像素级抗锯齿技术。

图6-14 在画布上递归绘制的树

尽管外观复杂、细节丰富,实现代码却非常简单:

修改drawTree()的初始值,你会发现初始值的微小变化可以给出非常不同的结果。不推荐使用远超过12的depth值(倒数第二个参数),除非你非常有耐心!

/*这里是drawTree()函数的代码*/

在第2章中,我们开发了DHTML sprite动画系统,并用它创建了各种图形演示。在第5章中,我们用这个系统制作了一个DHTML视频游戏。我们尽量将绘制sprites的细节“隐藏”在DHTMLSprite对象中,使得应用程序可以很容易实现和使用一个不同的sprite系统。现在我们将在演示中使用一个新的CanvasSprite对象,它利用了性能更强的画布元素。

CanvasSprite是DHTMLSprite对象的一个直接替换。除了加了一个画布环境参数(ctx)外,params对象的所有参数都和以前一样被传入:

警告

注意我们使用移位运算符(x>>0,y>>0)确保渲染位置为整数。Firefox和Opera浏览器在分数像素位置绘制时性能会有很大影响。这对普通绘图影响不大,但对高速图形应用,将非常影响性能。

下面的代码中粗体标注的是让CanvasSprite工作所需做的其他修改。你可以参考第2章中DHTMLSprite的代码进行比较。

在下面的例子中我们将看到一个更实用的画布应用程序:一个伪3D聊天应用程序(如图6-15所示)。这个例子将演示如何将画布和其他HTML5特性如WebSockets结合。

图6-15 一个使用HTML画布和WebSockets的伪3D图形聊天应用

除画布外,另一个同样令人振奋(但可能比较不知名)的HTML5元素是WebSockets。虽然这本书是关于图形的,但还是值得讨论一下为什么WebSockets对现代Web应用程序意义重大,以及如何将它们与画布集成。

通常服务器和客户端浏览器之间是用HTTP协议传递数据的,但HTTP有一定的局限性(和新的WebSockets相比),使它不适合高速、双向的网络通信。

HTTP是单行线

HTTP的模式是:客户端Web浏览器向服务器请求数据,服务器满足其要求。服务器不能在没有被请求的情况下“推送”信息到客户端。

它的开销很大

HTTP数据携带了大量的头信息。请求一个字节的数据可能会导致发送数百个字节的、额外的、“看不见的”头信息也被发送。头信息通常包含被传送数据的性质,如内容类型、缓存、编码等。

它的连接都是非持久的

为每个HTTP请求,都必须重新建立连接,发送数据,再关闭连接。这好比是电话交谈时每说完一句话就得重新拨号。

你可以使用Comet/长轮询等编程技术,模拟持久、双向的连接,从而提高HTTP的性能。虽然这些技术可以提供一些改善,但有可能服务器无法支持这些技术所需要的超高HTTP连接数;如Apache这样的服务器在处理此类连接时就不是特别有效率。最终你会发现:对多人游戏和其他快速通信的应用程序而言,HTTP的网络传输效率太低,即使用前面的技术还是无力回天。

WebSockets通过真正持久和双向的连接来解决这些问题。客户端可以在任何时候给服务器发送数据,反之亦然。此外,数据开销非常小,因为一旦建立了连接就不需要传递头数据。只是在数据前后各加了一个0字节(0x00)和0xFF表示结束和终止。

目前有Firefox 4+、Google Chrome 4+、Opera 10.70+和Safari5支持WebSockets。不过由于WebSockets通信相关的安全问题,使得Firefox和Opera的开发者关闭了默认的WebSockets功能,而其他浏览器厂商可能也会采取同样的行动。你可以在下面的链接找到更多细节:http://www.ietf.org/mail-archive/web/hybi/current/msg04744.html

在解决安全问题之前,默认WebSockets功能看来还处于不太稳定的状态。然而,我们可以现在就试验WebSockets协议,为未来做好准备。

在Firefox 4和Opera 11上的WebSockets

幸运的是,Firefox和Opera用户可以在开发中打开WebSockets功能。

Firefox4:

1.在浏览器的地址栏输入about:config。

2.查找和更改network.websocket.override-security-block标志。

Opera:

1.在浏览器的地址栏输入opera:config。

2.在首选项编辑器,打开“用户首选项”部分,并设置Enable WebSockets。

我们的聊天应用程序大致由4个主要部分组成:

当用户连接到聊天页面时,将自动为他创建一个颜色随机的化身。然后用户可以在页面上点击来移动化身,也可以在聊天框中输入文字。化身的移动和聊天文字将反映给所有其他的连接用户。也就是说,每个用户看到的都是相同的页面,但只能控制自己的化身。

1.套接字服务器

套接字服务器需要处理连接,并在连接的客户端之间传输信息。它必须运行在所有客户端可以连接到的服务器上。

套接字服务器的编程语言可以是任何流行的服务器端语言,如PHP、Java或Python。JavaScript程序员可以关注node.js,它是一个基于服务器的JavaScript实现和使其适合高效网络编程的一些相关库。

这个例子里我选择了PHP,因为几乎所有基于Linux的托管主机上都已安装了PHP。

提示

套接字服务器(server.php)实际上由两部分组成:一个通用的套接字处理类(WebSocketServer),可用于各种应用;一个聊天应用程序特定的回调函数(process()),这是为我们的聊天应用程序定制的。请从本书的代码中阅读server.php的源代码。

本书不可能展开讨论PHP。PHP语言比较简单易学,网上有大量的学习资源。如果你打算仔细看看套接字服务器代码,请注意下面几个PHP语法:

套接字服务器至少要执行以下操作:

2.在本地安装一个网站托管环境

除非你有专门或虚拟主机的root权限,否则是不可能让套接字服务器工作的。正确配置的共享网站托管环境都会有一个防火墙,防止使用任何的通信端口。幸运的是,你可以安装一个本地环境来运行服务器端代码。

安装一个网站托管环境曾经是痛苦而漫长的过程。幸好“Apache Friends”XAMPP软件将所需模块(Apache和PHP)合并到一个下载文件中,你可以在几分钟之内将其安装到Windows、Mac或Linux系统上。你可以去http://www.apachefriends.org/ en/xampp.html下载XAMPP软件。

图6-16显示了XAMPP的控制面板。注意PHP是以透明方式运行的,在控制面板中不显示。

图6-16 你可以用XAMPP在本地轻松安装一个完整的网站托管环境

要通过XAMPP启动套接字服务器,单击XAMPP控制面板上的Shell按钮。这将呈现一个命令行,使你可以运行套接字服务器(如图6-17所示)。输入php path-to-socket-server\server.php来运行套接字服务器,然后按Enter键。

图6-17 从XAMPP命令行运行套接字服务器

你需要将server.php的路径改为这个文件在你本地机器上的位置。

套接字服务器现在正在等待聊天应用JavaScript端的连接。

最后,我们需要在浏览器中运行实际的聊天网页(JavaScript)。最简单的方法是在浏览器中指定页面的文件系统位置,如file:///C:/professorcloud.com/book/canvas/canvas- websockets-chat.htm。

当我们使用file:///协议时,我们从浏览器直接访问HTML文件,并没有用到Apache。

你还可以打开多个浏览器来测试多个聊天网页的实例,自己和自己聊天。

测试聊天应用程序更精细的一个方式是通过网络(Web服务器)来运行聊天服务器、提供聊天页面。这里涉及的步骤有:

(1)在Web服务器上,修改Apache的httpd.conf文件中的一个虚拟主机条目,将Web服务器的IP地址映射到Web服务器上聊天应用程序的位置。定义的第一个虚拟主机是Web服务器IP地址的默认主机。

(2)通过XAMPP控制面板启动Apache。

网络上其他计算机的用户可以通过在合适的浏览器上输入Web服务器的IP地址,连接到聊天应用程序。

下面是关于如何在不同操作系统上设置XAMPP的文档:

3.相机

相机对象决定了聊天区域的透视图。它包含了3个应用函数:

setFOVandYPos()

传入相机的视野(FOV, Field of View)的角度和垂直位置。从视野计算相机距离使你可以改变画布大小,而不影响呈现的视图(假设高宽比不变)。我们使用125度的视野和−128的相机y位置。

worldToScreen()

从传入的世界坐标计算画布屏幕上的坐标。它给化身计算尺度使得它们在远处显得比较小,有效地模拟透视效果。

screenToWorld()

这个函数和 worldToScreen()相反,从画布上的位置得到对应的世界坐标。screenToWorld()将鼠标单击的位置转化为用户化身的新世界坐标位置。我们使用toFixed()方法防止返回值过于精细。例如,188.42620390960207将被188.426替代,因为前者需要更长的网络传输时间且没有太多必要。

4.化身

化身是客户端的图形表示。它们各自以随机颜色出现,以区别于其他化身。我们通过单击鼠标将其移动要新位置上。它们由分别代表头和身体的两个矢量形状组成。我们对化身用径向渐变填充以增加其深度效果,使用深色描边来将其和背景分离。

5.聊天文字

聊天的文字将在“发言的”化身上方出现,并随着用户输入更多文字逐渐向屏幕上方移动。为了使文字更清晰并加入聊天泡泡的效果,我们用白色填充的圆角矩形环绕文字,并用化身的颜色给这个矩形描边。

当化身聊天时,textScroller对象管理和绘制生成的文字。addText()方法将新的文字添加到列表的开始,同时删除5句之前的文字。这创建了垂直的滚动效果,文字向画布上方移动时最顶层的文字将丢失。这个方法接受化身的水平位置作为文字的中心位置,以及化身的颜色。

drawText()方法遍历文字列表,并绘制每一行文字。为使文字更加突出,我们在文字周围显示白色填充的圆角矩形,并用化身颜色对其描边。我们使用画布的 measureText()方法来计算文本和圆角矩形的宽度。

6.背景

drawBackground 对象绘制一个渐变的蓝天和绿地。蓝天和绿地都被渐变为白色以给人一种三维的深度感。

7.初始化

initAndGo()函数执行各种设置工作,如建立事件处理程序、连接到服务器。最后它执行移动和绘制头像与文字的循环:

8.页面代码

下面是聊天应用程序的HTML页面布局,被保存在一个名为canvas-webSockets- chat.htm的文件中:


相关图书

深入浅出Spring Boot 3.x
深入浅出Spring Boot 3.x
JavaScript核心原理:规范、逻辑与设计
JavaScript核心原理:规范、逻辑与设计
JavaScript入门经典(第7版)
JavaScript入门经典(第7版)
JavaScript函数式编程指南
JavaScript函数式编程指南
PHP、MySQL和JavaScript入门经典(第6版)
PHP、MySQL和JavaScript入门经典(第6版)
JavaScript学习指南(第3版)
JavaScript学习指南(第3版)

相关文章

相关课程