《JavaScript高效图形编程(修订版)》——第2章 DHTML基础 2.1创建DHTML sprite

简介: 定义了bouncySprite对象后,我们可以初始一些对象,并在setInterval()或settTimeout()控制下调用它们的 moveAndDraw()方法。更好的方法是创建一个对象可以初始化和处理任意数量的bouncySprite。

本节书摘来自异步社区《JavaScript高效图形编程(修订版)》一书中的第2章,第2.1节,作者:【美】Raffaele Cecco著,更多章节内容可以访问云栖社区“异步社区”公众号查看

第2章 DHTML基础

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

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

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

2.1 创建DHTML sprite

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

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

  • 用一个简单的函数调用和图像索引(index)来改变其图像(动画)。
  • 在内部管理自身的DOM元素。
  • 不改变DOM的情况下隐藏和显示自己。
  • 移除其DOM元素并进行必要的清理。

2.1.1 图像动画
没有动画的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这样计算:
screenshot

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

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

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

screenshot

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

提示:
图2-1中的容器图像是一个32位PNG文件,支持百万颜色和一个用于透明度的alpha通道。不过,32位PNG不适用于IE6,因为透明区域会变成不透明的灰色。一个解决方案是将图像存为8位的调色板PNG。这可以在IE6中正确显示,不过半透明区域会完全消失并显示粗糙的边缘。
2.1.2 封装和画图抽象
将所有DOM操作细节,封装在DHTMLSprite中,隐藏在使用它的应用之外,会使代码更简单更易维护;应用可以集中于应用逻辑而不是画图细节。由于应用逻辑和画图细节的分离,将应用转为另一个画图方法如HTML5 Canvas元素或SVG变得更简单,甚至可以使应用程序根据浏览器能力选择合适的画图方法。

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

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

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

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

screenshot

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

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

screenshot

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

screenshot

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

screenshot

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

  • 局部变量mathFloor()指向Math.floor()函数,我们通过前者来调用后者。
  • index变量只乘一次。

screenshot

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

screenshot

2.1.5 一个简单的sprite应用程序
下面是一个基本的HTML页面,它初始化并显示了两个sprite。

screenshot

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

screenshot

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

screenshot

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

screenshot

screenshot

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

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

screenshot

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

screenshot

screenshot

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

screenshot

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

screenshot

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

screenshot

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

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

screenshot

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

screenshot

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

screenshot

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

screenshot

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

screenshot

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

screenshot

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

screenshot

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

screenshot

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

screenshot

相关文章
|
JavaScript
JS基础之解构赋值
解构赋值 在js中,我们经常会将对象或者数组里面的一部分数据作为参数传递给函数,如果我们使用传统的.方法会很麻烦。
|
移动开发 JavaScript 前端开发
JavaScript基础知识梳理-上
对JavaScript基础知识梳理-上
228 31
JavaScript基础知识梳理-上
|
存储 JavaScript 前端开发
JavaScript基础(一篇入门)
JavaScript基础(一篇入门)
217 0
JavaScript基础(一篇入门)
|
JavaScript 前端开发
JavaScript 入门基础 - 运算符(三)
文章目录 JavaScript 入门基础 - 运算符(三) 1.什么是运算符 2.表达式和返回值 3.算术运算符概述 4. 赋值运算符 5.递增和递减运算符 5.1 递增和递减运算符概述 5.2 递增运算符 5.2.1 前置递增运算符 5.2.2 后置递增运算符 5.2.3 后置和前置运算符的区别 6. 比较运算符 7. 逻辑运算符 7.1 逻辑运算符概述 7.2 逻辑与 7.3 逻辑或 7.4
152 0
JavaScript 入门基础 - 运算符(三)
|
存储 JSON JavaScript
JavaScript 入门基础 - 变量 / 数据类型(二)
JavaScript 入门基础 - 变量 / 数据类型(二)
107 0
JavaScript 入门基础 - 变量 / 数据类型(二)
|
JavaScript 前端开发 物联网
JavaScript 入门基础 / 概念介绍(一)
JavaScript 入门基础 / 概念介绍(一)
140 0
JavaScript 入门基础 / 概念介绍(一)
|
JavaScript 前端开发 Java
JavaScript的基础使用
JavaScript的基础 一、javascript简介 JavaScript简称js,最初由网景(现在的Mozilla)公司创建,由于商标冲突原因,其标准版本命名为ECMAScript,但是一般人们还是叫JavaScript,只在谈标准的时候说到ECMAScript这个名字。值得注意的是JavaScript与java没有任何关系,就像雷峰塔(神话中镇压白娘子的塔)和雷锋。此外js(JavaScript)和jsp(java servlet pages)也没有关系。   js的工作分为两部分,一部分属于js语言本身的特性,而另一部需要依靠宿主环境(web浏览器)才能完成。 二、javascri
JavaScript的基础使用
|
JavaScript 前端开发
vue js文字跑马灯基础版本
提供两种解决思路
286 1
|
JavaScript 前端开发
原生js制作选项卡详解,适合无基础的人学习
原生js制作选项卡详解,适合无基础的人学习
138 0
原生js制作选项卡详解,适合无基础的人学习
|
JavaScript 前端开发
JavaScript之function基础定义与调用
引入 前端人都多多少少听说过一句话:“HTML是网页的骨架,CSS是网页的皮囊,而JS是网页的灵魂”。 而在我们学习JS这一“灵魂”的同时,如果要给JS也找一个“灵魂”,我觉得就是接下来我们会一起学习的函数 之前的学习文章里我也曾提到过,一般我们不会嵌套太多层循环,如果有需要,我们更倾向于写一些自定义函数,然后以调用的方式来达到相应的目的 在大型开发中,我们也往往会采用定义函数并调用组件的形式来让我们代码的可读性更强。