《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的Date对象的创建和属性
JavaScript的Date对象的创建和属性 今天我们来学习一下js中的Date对象。Date 对象主要用于处理日期与时间。 1.Date对象的声明/创建 // 1.创建日期 var oDate=new Date(); console.log(oDate); // 2.定义自定义的日期 // new Date(year,month,day,hour,minute,second,millisecond); var oDate3=new Date(2020,5,27,12,0,0); console.log(oDate3);
|
JavaScript 前端开发
JavaScript字符串对象的创建和属性
JavaScript字符串对象的创建和属性 字符串对象同样是JavaScript的内置对象,用来储存和处理文本。 1.字符串的创建 // 创建字符串 // 1.字面量的方式 "" '' `` var str="hello"; var str1='world'; var str2=`hello China`;//模板字符串 // 2.使用 new 关键字创建 构造函数 var str3=new String("hello Nanjing"); console.log(typeof str);// String
|
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
121 0
JavaScript 入门基础 - 运算符(三)
|
存储 JSON JavaScript
JavaScript 入门基础 - 变量 / 数据类型(二)
JavaScript 入门基础 - 变量 / 数据类型(二)
87 0
JavaScript 入门基础 - 变量 / 数据类型(二)
|
JavaScript 前端开发 物联网
JavaScript 入门基础 / 概念介绍(一)
JavaScript 入门基础 / 概念介绍(一)
112 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 前端开发
【JavaScript】面向对象——创建多个对象的四种方法(详解)
【JavaScript】面向对象——创建多个对象的四种方法(详解)
146 0
|
JavaScript 前端开发
js 模块化基础和模块规范AMD、CMD、ES6模块
js 模块化基础和模块规范AMD、CMD、ES6模块
83 0
|
JavaScript 前端开发
原生js制作选项卡详解,适合无基础的人学习
原生js制作选项卡详解,适合无基础的人学习
109 0
原生js制作选项卡详解,适合无基础的人学习