HTML5 和 JavaScript 项目教程(四)

原文:HTML5 and JavaScript Projects

协议:CC BY-NC-SA 4.0

九、美国各州游戏(USStatesGame):构建多活动游戏

在本章中,您将学习以下内容:

  • 如何为一个游戏建立一个用户界面,包括不同类型的玩家移动,包括拼拼图

  • 如何使用鼠标重新定位棋子

  • 如何获取一幅图像,把它分成小块,并确定这些小块的坐标来制作拼图

  • 如何编码和检索拼图游戏的当前状态

  • 如何使用localStorage存储和检索信息,包括在localStorage不允许的情况下使用trycatch

介绍

本章的项目是一个教育游戏,在这个游戏中,玩家/学生响应文本提示,在美国地图上单击一个州,通过键入名称来命名一个用边框表示的州,或者将随机放置在屏幕上的州再次放在一起。图 9-1 为开启画面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-1

美国各州比赛的开幕画面

我遵循惯例,展示了一张阿拉斯加和夏威夷位置不正确,大小也不成比例的地图。还要注意,罗德岛比实际要大,所以有足够的空间点击它。这个游戏给玩家提供了不同的可能性。图 9-2 显示了点击查找状态按钮的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-2

提示是找到华盛顿

当我点击俄勒冈时,我看到了如图 9-3 所示的内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-3

对错误选择的反应

当我点击正确的选项时,应用程序做出适当的响应,如图 9-4 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-4

对正确答案的回应

我认为给玩家提供分散所有州的选项会有所帮助。点击标签为展开状态的按钮后,您会看到如图 9-5 所示的内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-5

各州分散开来

玩家可以使用“恢复原始地图/压缩地图”按钮,或者继续播放展开的状态。点击状态名称按钮会产生一个由一个随机选择的状态组成的提示,并被一个边框包围,如图 9-6 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-6

待命名州的边界

注意中间右边(大西洋海岸)非常小的州特拉华州周围的双线边界。这展示了一种情况,其中分散的状态将对玩家产生真正的影响。图 9-7 显示了我输入正确答案后的反应。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-7

提交正确答案后的响应

该应用程序还以拼图游戏的形式为玩家提供活动。点击 Do Jigsaw 按钮后,您将看到类似图 9-8 的内容。我说“类似”是因为这些状态是使用伪随机处理排列的,所以它们每次都会以不同的方式出现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-8

各州为了拼图而乱成一团

玩家现在可以使用鼠标以与第八章中描述的拼图转视频游戏相同的方式拖放棋子。图 9-9 显示了我正在进行的工作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-9

拼图正在进行中

请注意,我已经整理了阿拉斯加和夏威夷、西部的五个州、南部的七个州、新英格兰的所有地区以及纽约和新泽西。反馈说伊利诺伊州甚至更多的州都不在位置上。反馈可以改进,但严格来说问题不在于编程。

这对我来说是一个具有挑战性的难题。为了完全公开,也因为它展示了游戏的一个特性,我点击了 Save & Close Jigsaw 按钮,这让我可以看到所有的状态都回到原位。然后我点击恢复最后一个拼图,回到我在的地方。有了这个工具,我能够得到图 9-10 中所示的内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-10

不完全正确

反馈表明北达科他州有问题。作弊之后——也就是点击 Save & Close Jigsaw,看着完成的地图——我意识到北达科他州和堪萨斯州这两个相似的矩形需要交换。图 9-11 显示了正确的布置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-11

正确组装的拼图玩具

请注意,阿拉斯加和夏威夷的位置没有仔细检查。这个谜题被认为是完整的。

在介绍了这个教育游戏的特性之后,我将描述实现的关键需求。

关键要求

教育游戏的关键要求包括为玩家提供不同类型的活动。对于拼图游戏活动,应用程序提供了保存和恢复功能。此功能可用于查看已完成的拼图,或将拼图搁置一段时间,然后做其他事情。游戏制作器的任务是提供用户界面的特征和游戏从一种类型的活动到另一种类型的方式。

该应用程序需要一个完整的美国地图,每个州都可以点击。我在“简介”部分描述的第一类活动是让游戏显示一个州的名称,并提示玩家点击它。应用程序必须能够确定响应是对还是错,并提供反馈。

我演示的下一种活动是相反的。地图上的一个州会以某种方式标记出来,并提示玩家输入名称。挑出一个州有不同的方法。我选择在要命名的州周围加一个边框。程序必须读入玩家输入,并确定名字是否正确。

在实施了这两类活动之后,我想到我们有一些非常小的国家。然后我决定提供展开特性和撤销它的能力。这对其他地图也很有用。我还修改了代表小罗德岛的图片,使其变得更大。

最后,我决定提供一种方法,看看人们是否能把各州放在一起。该应用程序提供了一个拼图游戏,其中状态被随机放置在屏幕上,玩家使用鼠标来重新定位它们。在这一点上,我意识到我需要一些不同于 HTML5 的拖放功能的东西。如果你还没有这样做,你现在可以阅读第八章,了解如何实现拼图游戏。美国各州游戏有两个额外的要求:我需要建立一种方法来进入和退出拼图模式,以便所有的按钮都工作,所以玩家可以点击一个州。我还需要一种方法来保存一个不完整的拼图。这对于第八章拼图转视频项目中的猴栏视频来说是不必要的,但是对于一个有 50 块拼图来说是必要的。我也认为这是一个有教育意义的游戏,所以给玩家一个机会看看完成的地图,也休息一下是合适的。

HTML5、CSS、JavaScript 特性、编程技术和图像处理

实现教育州游戏的特性和技术,在很大程度上,是你以前见过的。然而,把它们放在一起会很棘手,所以在本章和前几章的材料之间会有一些冗余。

获取片段的图像文件并确定偏移量

50 个州的图像文件是本章下载的一部分。但是,由于您可能想要制作自己的地图拼图,我将描述拼图块的关键特征以及检查定位和恢复必须记录的完整地图所需的信息。

您需要为每个拼图块生成图像文件,也就是说,为我的游戏生成美国的每个州的图像文件。由于没有一个状态是严格的矩形,并且图像文件必须是矩形,所以图像将是每个状态的边界框,实际状态之外的区域是透明的。没有特殊待遇,以适应夏威夷或上下密歇根州的岛屿。

制作代表各州的单张地图的首要任务是获得一张美国地图(或你选择的国家或地区)并选择你最喜欢的图像处理程序。我使用了 Adobe Flash,这是我制作第一个美国游戏示例时流行的,但我将使用在线图像编辑工具 pixlr 来说明这个过程。源代码中的数字来自我最初的实现,不会是这里提到的数字。图 9-12 显示美国地图。阿拉斯加和夏威夷定位不准。当我的代码检查玩家完成的工作时,我通过简单地不检查这两种状态的定位来巧妙应对这个挑战。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-12

pixlr 中的原始完整地图图像

下一个任务是确定每个州的相对位置信息。所需的信息是每个州的边框左上角的相对位置。这个点可能不在状态上,但会确定正确的位置。在图 9-13 中,我使用选框工具在伊利诺斯州周围画了一个方框。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-13

伊利诺伊周围的盒子

这样做的时候,我从导航面板上记下起始位置的 x 和 y 坐标,即盒子的左上角,如图 9-14 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-14

导航面板

注意

这些不是伊利诺伊州上角的坐标,而是我截图过程中产生的。导航器面板显示鼠标的位置。

下一个任务是使用 pixlr 工具栏上“编辑”下的下拉菜单中的“第一次复制”,然后使用“文件”下的“新图像”,将选区复制到新图像中。图 9-15 显示了出现的面板。请注意,我已经为图像命名为 Illinois,并给出了从剪贴板中取出图像并保持透明度的指令。我需要做一些事情来创建透明区域。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-15

面板创建一个新的图像,给它一个名称和说明

pixlr 程序现在有两个图像,我需要移动大地图来得到新的图像。我还使用了视图下的缩放功能来放大它。如图 9-16 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-16

伊利诺伊州新形象控股公司

我现在使用魔杖(有时也称为魔术棒)工具,并点击浅绿色伊利诺伊州。这仅选择伊利诺伊州,如图 9-17 所示,使用颜色。它不一定是地图上唯一的浅绿色区域,而只是与相邻区域不同。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-17

使用魔杖工具选择伊利诺伊州

我想要的是把所有东西都剪下来,除了伊利诺伊州的形状。这是通过编辑/反转选择来执行的。如图 9-18 所示。对了,我把这个文件存成了带透明度的 PNG,命名为Illinois1,就是为了不让自己搞混。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-18

反向选择:除了伊利诺伊州的形象

然后我编辑/剪切并制作出白色背景下的伊利诺伊州形状,实际上是透明的。图 9-19 显示了我保存的图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-19

伊利诺伊州的形象对透明背景

这些是每个国家的必要步骤。

我创建了保存图像文件名以及水平(x)和垂直(y)偏移数据的数组。我还创建了一个数组,列出了各州的全名。这是四个平行的阵列。另一种方法是用下划线系统地保存文件,以避免任何内部中断——例如,North_Carolina.gif。我可以编写代码,用空格替换下划线,以便显示游戏和检查玩家的答案。然而,我决定直接说出这些名字。描述了保存程序状态所需的所有内容的四个并行数组的创建之后,现在是时候回顾如何创建元素了。

动态创建元素

第六章和第八章分别涉及动态生成 HTML 标记——也就是说,在运行时。你可能创建的州游戏和其他地图游戏也将采用这种技术。这项工作在函数setupgame中完成。

代码决定了来自nums变量的多少元素——也就是拼图块——被设置为states数组的长度。例如,如果你用 10 个国家建造一个拼图,nums将被设置为 10。一个for循环用于为每个状态构建一个元素。每个元素都有一个生成的唯一 ID 值。任何元素的属性innerHTML都被设置为标记。代码使用数组变量statesstatesxstatesy中的信息。与上一章中的情况一样,代码将数字转换为字符串,然后连接字符串"px"以产生用于设置元素的style.topstyle.left属性的值。代码如下:

function setupgame() {
   var i;
   var x;
   var y;
   var uniqueid;
   var s;
   for(i=0;i<nums;i++) {
      uniqueid = "a"+String(i);
      s = document.createElement('state');
      s.innerHTML = (
                   "<img src='"+states[i]+"' id='"+uniqueid+"'/>");
      document.body.appendChild(s);
      thingelem = document.getElementById(uniqueid);
      x = statesx[i] +310;
      y = statesy[i] + 200;
      thingelem.style.top = String(y)+"px";
      thingelem.style.left = String(x)+"px";
      stateelements.push(thingelem);
   }
  questionfel = document.getElementById("questionform");
   questionfel.style.left = "100px";
   questionfel.style.top = "500px";
   questionfel.question.value = " ";
   questionfel.feedback.value = "  ";
}

该元素由自定义类型'state'创建。它的innerHTML被设置为适当的值。使用statesxstatesy数组中的偏移值完成定位(对应于我在第八章中命名为piecesxpiecesy的数组)。setupgame函数的第二部分定位已经出现在body元素中的表单。该表格将用于识别和命名活动。

整体用户界面

是时候展示应用程序的body元素了,因为它将显示各种操作的按钮:

<body id="body" onLoad="init();">
<button onClick="spread();">Spread out states </button>
<button onClick="restore();">Restore original /compress map </button>
<button onClick="setupfindstate();">Find the state </button>
<button onClick="setupidentifystate();">Name the state</button>
<button onClick="setupjigsaw();">Do jigsaw</button>
<button onClick="restorepreviousjigsaw();">Restore last jigsaw in process </button>
<h1>USA</h1>
<form id="questionform" name="questionform" onSubmit="return checkname();">
State name: <input type="text" name="question" value="   " size="40"/>
<input name="submitbut" type="submit" value="       " size="30"/>
Feedback: <input type="text" name="feedback" value="   " size="40" />
</form>
</body>

HTML 标记在屏幕顶部生成六个按钮(回头参考图 9-1 )。顶部的每个按钮都调用一个功能;在接下来的几节中,我们将详细介绍每一种方法。底部的表格以不同的方式用于三种不同类型的活动。这是一个设计决策;我试图有效地利用屏幕空间,避免多种形式的混乱,这可能会给玩家带来困惑。

要求玩家点击一个状态的用户界面

玩家单击 Find State 按钮后,应用程序会生成一个问题。在选择州之前,程序会删除最后选择的州周围可能存在的任何边框。如果玩家刚刚执行了 name a 状态活动,就会出现这种情况。如果这是玩家的第一个活动,代码不会产生错误,而只是将第 0 个状态的边界设置为空,这就是它已经存在的状态。这是一个好习惯,让任何活动开始做这种类型的内务。这使得应用程序在将来更容易更改或升级。同样,如果前一个问题也是一个识别问题,代码也不会产生错误。这种从一个活动到另一个活动的过渡必须被注意,以使游戏能够顺利进行。当玩家进入下一个活动时,我们不希望任何状态有边界。

setupfindstate函数在状态中随机选择。全局变量 choice 保存随机选择的值。然后,该函数为对应于一个状态的每个元素设置事件处理。对玩家的提示放在表单的问题字段中。

function setupfindstate(){
   var i;
   var thingelem;
   stateelements[choice].style.border="";
   choice = Math.floor(Math.random()*nums);
   for (i=0;i<nums;i++) {
    thingelem = stateelements[i];
    thingelem.addEventListener('click',pickstate,false);
   }
   var nameofstate = names[choice];
   questionfel.question.value = "Click on "+nameofstate;
   questionfel.feedback.value = "  ";
   questionfel.submitbut.value = "";
}

玩家对此活动的适当反应是点击地图上的一个州。当玩家点击任何状态时,JavaScript 事件处理被设置为调用pickstate函数。这个函数的任务是确定玩家的选择是否正确。为此,我的代码使用了传递给函数的事件信息中的信息和由setupfindstate设置的全局变量choice中的值。pickstate的代码是

function pickstate(ev) {
    var picked = Number(ev.target.id.substr(1));
   if (picked == choice) {
   questionfel.feedback.value = "Correct!";
   }
   else {
      questionfel.feedback.value = "Try Again.";
   }
 }

现在我需要提醒您我是如何为每个状态元素设置 ID 字段的。我使用了索引值 0 到 49,并在开头添加了一个一个。增加一个一个并不是绝对必要的。当我想到我可能会创建其他元素集时,我这样做了。pickstateev参数有一个目标属性,引用接收点击事件的目标。那个目标的 ID 将是a0,或者a1,或者a2,等等。String方法substr从参数开始提取字符串的子串,所以substr(1)返回 0、1、2 等等。我的代码将字符串转换成数字。现在可以将它与全局变量choice中的值进行比较。

你可以决定限制玩家尝试和/或提供提示的次数。

要求玩家命名一个州的用户界面

在玩家选择命名一个州的活动后,调用setupidentifystate函数。任务是在地图上的一个州周围放置一个边界,并提示玩家输入名称。对于这个操作,与上一个不同,我的代码为 submit 按钮输入了一个值。该函数还删除了单击状态的事件处理。

function setupidentifystate(){
   stateelements[choice].style.border="";
   stateelements[choice].style.zIndex = "";
   choice = Math.floor(Math.random()*nums);
   stateelements[choice].style.border="double";
   stateelements[choice].style.zIndex = "20";
   questionfel.question.value = "Type name of state with border HERE";
   questionfel.submitbut.value = "Submit name";
   questionfel.feedback.value = "  ";
   var thingelem;
   for (i=0;i<nums;i++) {
    thingelem = stateelements[i];
    thingelem.removeEventListener('click',pickstate,false);
   }
}

玩家的动作由checkname函数检查。这已经被设置为表单的onsubmit属性。函数checkname实际上有双重功能:如果当前活动正在拼图,checkname通过将州恢复到它们的原始位置,即整个美国的原始地图,来结束该活动。如果玩家没有在玩拼图游戏,checkname会检查玩家是否输入了所选州的正确名称。checkname中的代码如下:

function checkname() {
   if (doingjigsaw) {
      restore();
   }
   else {
   var correctname = names[choice];
   var guessedname = document.questionform.question.value;

   if (guessedname==correctname) {
      questionfel.feedback.value = "Correct!";
   }
   else {
      questionfel.feedback.value = "Try again.";

   }
   return false;
   }
}

请注意,我没有限制尝试的次数,也没有对拼写错误给出任何暗示或容忍。

展开碎片

在保持状态的位置关系的同时展开它们的任务很简单,尽管我用常数做了一些实验来得到我想要的效果。想法是以系统的方式使用偏移值。偏移表示从地图大致中心点的距离。我的代码扩展了除阿拉斯加和夏威夷之外的所有州的偏移值。我认为阿拉斯加和夏威夷是最后两个州。代码如下:

function spread() {
   var i;
   var x;
   var y;
   var thingelem;
   for (i=0;i<nums-2;i++) {  // don't move alaska or hawaii

      x = 2.70*statesx[i] +410;
      y = 2.70*statesy[i] + 250;
      thingelem = stateelements[i];
      thingelem.style.top = String(y)+"px";
      thingelem.style.left = String(x)+"px";
   }
}

恢复状态只是将它们重新定位在statesxstatesy数组中指示的值上。下面将在“保存和重新创建拼图游戏的状态并恢复原始地图”一节中解释restore功能。

设置拼图玩具

设置 jigsaw 活动包括在屏幕上随机定位状态,并为鼠标操作设置事件处理。这还意味着关闭默认的拖放事件处理,并关闭屏幕顶部的按钮。屏幕底部的问题表单上的提交按钮将保持可操作,该按钮将执行保存拼图状态的操作,如下一节所述。停止 jigsaw 活动、恢复地图并返回到其他活动的唯一方法是单击按钮。

新创建的 ID 为fullpagediv是为了防止拖放默认动作而创建的,它在样式部分被设置为不覆盖包含表单的屏幕底部。CSS 是

#fullpage
{
   display:block;
   position:absolute;
   top:0;
   left:0;
   width:100%;
   height:90%;
   overflow: hidden;
   z-index: 1;
}

回想一下,在 CSS 中,分层是通过属性z-index完成的。在 JavaScript 中,属性是zIndexsetupjigsaw功能如下:

function setupjigsaw() {
  doingjigsaw = true;
   stateelements[choice].style.border="";
  var i;
  var x;
  var y;
  var thingelem;
    for (i=0;i<nums;i++) {
      x = 100+Math.floor(Math.random()*600);
      y = 100+Math.floor(Math.random()*320);
      thingelem = stateelements[i];
      thingelem.style.top = String(y)+"px";
      thingelem.style.left = String(x)+"px";
      thingelem.removeEventListener('click',pickstate,false);
     }
  d.onmousedown = startdragging;
  d.onmousemove = moving;
  d.onmouseup = release;
  var df = document.createElement('div');
  df.id = "fullpage";
  bodyel.appendChild(df);
   questionfel.question.value = "";
   questionfel.submitbut.value = "Save & close jigsaw";
   questionfel.feedback.value = "  ";
   questionfel.style.zIndex = 100;
}

玩家用鼠标重新定位拼图玩具。回到第八章了解鼠标事件的使用说明。每次玩家放开鼠标按钮时,都会检查完整性。release函数调用我命名为checkpositions的函数。checkpositions谜题计算棋子实际位置与存储在statesxstatesy数组中的偏移量在 x 轴和 y 轴上的平均差值。然后,代码检查与相应平均值的差值是否大于tolerance值。一旦发现一个片段不合适,该函数就停止迭代。对于第八章中非常简单的六块拼图,当这种情况发生时,我给玩家的反馈只是显示“继续工作”对于美国各州的比赛,我想做更多的事情。我决定做的是报告第一个状态,其中 x 或 y 的差值大于平均值。当大多数部分都不在适当的位置时,这些信息不是特别有用,所以这是一个改进的机会。

保存和重新创建拼图游戏的状态并恢复原始地图

正如我前面提到的,结束 jigsaw 活动的唯一方法是单击表单上的 submit 按钮。如果全局变量doingjigsawtrue,那么restore函数被调用。restore功能将关闭鼠标的事件处理并移除fullpage div。我意识到,即使是我也不可能在一次会议中不作弊地完成拼图——也就是说,看着完成的拼图。然而,我越来越擅长了。这就是促使我实现保存和恢复功能的原因。

定义应用程序状态的问题自然取决于应用程序。保存拼图游戏的状态需要代码来编码每个拼图块的位置。对于拼图游戏,需要存储的是每个元素的style.topstyle.left属性。我将使用 HTML5 的localStorage特性,这是一个 cookies 版本。接下来我会描述localStorage。这个程序的目标是用一个字符串保存所有的信息。我首先做的是用&style.topstyle.left连接成一个字符串。然后,我使用以下代码行将每个字符串放入一个数组中:

xydata.push(thingelem.style.top+"&"+thingelem.style.left);

当所有 50 个字符串都放入数组中时,我的代码使用join方法将所有内容合并到一个大数组中,并用我选择的分隔符(;)将它们分开。这是使用localStorage存储的字符串。

在 HTML5 中,localStorage是 cookies 的变体。值以名称/值对的形式存储在玩家(客户端)的计算机上。一个localStorage项目与浏览器相关联。使用 Firefox 时存储的拼图状态在使用 Chrome 时将无法使用。对于localStorage项的名称,我使用名称jigsaw,对于值,使用join操作的结果。

localStorage设施可能不工作。例如,玩家可能已经使用浏览器设置来阻止对 cookies、localStorage或其他类似特征的任何使用。一个localStorage项目与一个特定的网络域相关联。Chrome 允许在本地计算机上设置和检索程序。当我最初构建这个应用程序时,Firefox 抛出了一个检索数据的错误。我的代码使用trycatch在出现问题时给出警告声明。图 9-20 显示了当使用本地计算机上的文件时,尝试恢复使用 Firefox 保存的拼图游戏的结果。如果玩家/用户关闭了 cookies 的使用,也会发生这种情况。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-20

尝试在 Firefox 中本地使用 localStorage 时显示警告

继续,有两个不同的功能:restorerestorepreviousjigsaw。记住restore功能有双重功能:它在棋子铺开后恢复原始地图在玩家完成拼图活动后恢复原始地图。

function restore() {
   var i;
   var x;
   var y;
   var thingelem;
   var df;
   var lsname = "jigsaw";
   var xydata = [];
   var stringdata;
   if (doingjigsaw) {
      doingjigsaw = false;
       d.onmousedown = "";
         d.onmousemove = "";
         d.onmouseup = "";
         df = document.getElementById("fullpage");
       bodyel.removeChild(df);
       for (i=0;i<nums;i++) {
          thingelem = stateelements[i];
         xydata.push(thingelem.style.top+"&"+thingelem.style.left);
       }
       stringdata = xydata.join(";");
       try {
         localStorage.setItem(lsname,stringdata);
        }
       catch(e) {
         alert("data not saved, error given: "+e);
       }
   }
   for (i=0;i<nums;i++) {
      x = statesx[i] +310;
      y = statesy[i] + 200;
      thingelem = stateelements[i];
      thingelem.style.top = String(y)+"px";
      thingelem.style.left = String(x)+"px";
   }
}

restorepreviousjigsaw函数试图以jigsaw的名字读入作为一个长字符串存储在localStorage中的数据;将字符串解码为 50 个字符串的数组,每个字符串保存topleft信息;并利用这些信息来定位棋子。然后,该函数为鼠标事件设置事件处理,并设置fullpage div。最后,该函数设置提交按钮的标签,以指示该按钮保存并关闭谜题。代码如下:

function restorepreviousjigsaw() {
   var i;
   var lsname = "jigsaw";
   var xydata;
   var stringdata;
   var ss;   // will hold combined top and left for a state
   var ssarray;
   var thingelem;
   try {
   stringdata = localStorage.getItem(lsname);
   xydata = stringdata.split(";");
   for (i=0;i<nums;i++) {
     ss = xydata[i];
     ssarray = ss.split("&");
     thingelem = stateelements[i];
     thingelem.style.top = ssarray[0];
     thingelem.style.left = ssarray[1];
   }

   doingjigsaw = true;
   stateelements[choice].style.border="";
   d.onmousedown = startdragging;
                d.onmousemove = moving;
                d.onmouseup = release;
                 var df = document.createElement('div');
                df.id = "fullpage";
                bodyel.appendChild(df);
   questionfel.question.value = "";
   questionfel.submitbut.value = "Save & close jigsaw";
   questionfel.feedback.value = "  ";
   questionfel.style.zIndex = 100;
   }
   catch(e) {
      alert("Problem in restoring previous puzzle. Click on Do jigsaw.");}
}

构建应用程序并使之成为您自己的应用程序

您可以通过改进和构建 states 应用程序来创建自己的项目,可能会给出提示或记录分数,或者将应用程序用作世界不同地方的模型。对于不同的地图,请注意我对阿拉斯加和夏威夷的特殊处理。您可能想要删除出现的nums-2。您可以添加另一个带有首都名称的并行数组,并使命名首都和标识带有指示首都的州成为附加活动。您还可以使用它作为模型来识别任何图或图片的部分(例如,身体的部分)。请注意,每个活动都有一个设置函数和一个检查响应的函数。

你可以使用第八章中所描述的方法,通过手指触摸来完成这个项目。美国各州对手机来说似乎太多了,但对平板电脑来说可能是可行的。您可以使用第五章中所示的方法将内容提取到外部文件中。如果你觉得很勇敢,你可能还想尝试使用 SVG(可缩放矢量图形)来创建一个矢量版本的地图。

该应用程序演示了您可以用于其他项目的个别功能。下面是州游戏中功能的非正式概述/总结:

  • init用于初始化,包括调用setupgame

  • setupgame构建状态元素并定位表单。

  • setupfindstate设置点击状态功能,pickstate检查玩家的反应。

  • setupidentifystate设置名称的输入,checkname检查响应。

  • setupjigsaw设置拼图玩具。函数startdraggingmovingrelease以及offsetdraw处理玩家使用鼠标移动棋子的动作。checkpositions函数和doaverage一起检查拼图是否完成。

  • spread展开棋子,restore将棋子恢复到原始地图位置。restore功能也使用localStorage保存拼图游戏的状态。

  • restorepreviousjigsawlocalStorage中提取信息,按原样设置拼图。

更正式的说法是,表 9-1 列出了所有的函数,并指出它们是如何被调用的以及它们调用了什么函数。请注意,由于函数被指定为对象类型的方法,因此会调用几个函数。

表 9-1

功能 美国各州游戏项目

|

功能

|

调用/调用者

|

打电话

|
| — | — | — |
| init | 由<body>标签中的onLoad属性的动作调用 | setupgame |
| setupgame | 由init调用 |   |
| pickstate | 由setupfindstate中的addEventListener调用调用 |   |
| spread | 通过按钮调用 |   |
| restore | 通过按钮和checkname调用 |   |
| restorepreviousjigsaw | 通过按钮调用 |   |
| setupfindstate | 通过按钮调用 |   |
| setupidentifystate | 通过按钮调用 |   |
| checkname | 作为表单中onSubmit的动作调用 | restore |
| checkpositions | 通过释放鼠标调用(mouseup事件) | doaverage |
| doaverage | 由checkpositions调用 |   |
| setupjigsaw | 通过按钮调用 |   |
| release | 通过设置restorepreviousjigsawsetupjigsaw中的事件来调用 | checkpositions |
| startdragging | 通过设置restorepreviousjigsawsetupjigsaw中的事件来调用 | offset |
| moving | 通过设置restorepreviousjigsawsetupjigsaw中的事件来调用 | draw |
| draw | 通过移动鼠标调用(mousemove事件) |   |
| offset | 由startdragging调用 |   |

表 9-2 显示了基本应用程序的代码,每一行都有注释。

表 9-2

美国各州游戏项目的完整代码

|

代码行

|

描述

|
| — | — |
| <!DOCTYPE html> | 文档类型标题 |
| <html> | html标签 |
| <head> | head标签 |
| <title>USA States game</title> | 完整标题 |
| <style> | style标签 |
| img {position:absolute;} | 绝对定位的所有图像元素 |
| form {position: absolute; z-index: 10;} | 绝对定位的表单 |
| body{ height:100%; margin: 0;} | 身体造型占据整个高度 |
| #fullpage | 已创建的样式指令div |
| { display:block; position:absolute; top:0; left:0; width:100%; height:90%; overflow: hidden; z-index: 1; } | 占据整个宽度和几乎整个高度;下面一层 |
| </style> | 结束style标签 |
| <script type="text/javascript"> | script标签 |
| var names = [ | 各州的名称;许多带有状态信息的并行数组中的一个;这里的顺序和分组并不重要,但顺序必须相同,阿拉斯加和夏威夷排在最后 |
| "Illinois","Iowa","Missouri","Oregon","Michigan", |   |
| "Indiana","Vermont","New Hampshire","Maine","South Dakota","North Dakota", |   |
| "Ohio","Wisconsin","Kentucky","Tennessee", |   |
| "North Carolina","South Carolina","Georgia","Alabama","Mississippi", |   |
| "Virginia","West Virginia","Maryland","Delaware","Pennsylvania","New Jersey","New York", |   |
| "Rhode Island", "Connecticut","Massaschusetts","Louisiana","Arkansas","Minnesota", |   |
| "Florida","Kansas", |   |
| "Arizona","California","Colorado","Idaho","Montana","Nebraska", |   |
| "Nevada","New Mexico","Texas","Oklahoma","Utah","Washington","Wyoming","Hawaii","Alaska" |   |
| ] | names数组的结尾 |
| var states = [ | 图像文件地址数组 |
| "illinois.gif", |   |
| "iowa.gif", |   |
| "missouri.gif", |   |
| "oregon.gif", |   |
| "michigan.gif", |   |
| "indiana.gif", "vermont.gif","newhampshire.gif","maine.gif","southdakota.gif","northdakota.gif", |   |
| "ohio.gif","wisconsin.gif","kentucky.gif","tennessee.gif", |   |
| "northcarolina.gif","southcarolina.gif","georgia.gif","alabama.gif","mississippi.gif", |   |
| "virginia.gif","westvirginia.gif","maryland.gif","delaware.gif", |   |
| "pennsylvania.gif","newjersey.gif","newyork.gif", |   |
| "rhodeislandbig.gif","connecticut.gif","massachusetts.gif","louisiana.gif","arkansas.gif","minnesota.gif", |   |
| "florida.gif","kansas.gif", |   |
| "arizona.gif","california.gif","colorado.gif","idaho.gif","montana.gif","nebraska.gif", |   |
| "nevada.gif","newmexico.gif","texas.gif","oklahoma.gif","utah.gif","washington.gif","wyoming.gif","hawaii.gif","alaska.gif" |   |
| ]; | 图像文件地址数组结束 |
| var statesx = [ | 水平(x)偏移量数组 |
| 88.65,60.15,65.40, |   |
| -81.70,90.40, |   |
| 107.40,171.95,181.00,183.00,21.10,22.60, |   |
| 121.70,78.90,103.65,99.40, |   |
| 132.20,138.95,125.45,110.45,93.90, |   |
| 138.95,138.95,151.65,171.95,144.20,174.20,147.95, |   |
| 187.75,179.35,177.60,77.40,73.65,54.15, |   |
| 115.70,32.35, |   |
| -44.95,-86.85,-8.15,-47.20,-32.15,21.10, |   |
| -66.70,-11.15,-4.40,22.60, -36.70,-72.50,-15.65,-300.95,-230.30 |   |
| ]; | statesx数组的结尾 |
| var statesy = [ | 垂直(y)偏移量数组 |
| -26.10,-29.85,-8.45, |   |
| -64.75,-59.05, |   |
| -22.70,-66.00,-67.30,-85.65,-47.15,-70.30, |   |
| -27.90,-55.30,-3.60,12.90, |   |
| 5.20,21.45,26.40,27.90,29.65, |   |
| -13.20,-17.10,-19.85,-20.85,-36.40,-31.35,-61.30, |   |
| -41.85,-41.85,-50.85,47.10,21.15,-72.70, |   |
| 55.45,-2.85, |   |
| 15.15,-35.75,-11.85,-76.70,-76.30,-23.85, |   |
| -27.60,18.15,22.65,19.65,-22.35,-83.45,-41.75,31.55,-171.30 |   |
| ]; | statesy数组的结尾 |
| var doingjigsaw = false; | 指示是否执行jigsaw的标志 |
| var bodyel; | 用于保存对主体的引用 |
| var nums = states.length; | 状态数 |
| var stateelements = []; | 将保存动态创建的元素 |
| var questionfel; | 用于保存对表单的引用 |
| function init(){ | init功能的标题 |
| setupgame(); | 调用setupgame |
| bodyel = document.getElementById("body"); | 设置用于添加fullpage div的参考 |
| } | 关闭init功能 |
| function setupgame() { | setupgame功能的标题 |
| var i; | 用于索引 |
| var x; | 对于 x 值 |
| var y; | 对于 y 值 |
| var uniqueid; | 对于为每个元素创建的唯一 ID |
| var s; | 保留每个新创建的元素 |
| for(i=0;i<nums;i++) { | 遍历各州 |
| uniqueid = "a"+String(i); | 定义一个 ID |
| s = document.createElement('state'); | 创建元素 |
| s.innerHTML = (``"<img src='"+states[i]+``"' id='"+uniqueid+"'/>"); | 将新创建的元素的 HTML 标记内容设置为具有所示属性的图像 |
| document.body.appendChild(s); | 附加到正文 |
| thingelem = document.getElementById(uniqueid); | 获取参考 |
| x = statesx[i] +310; | 计算水平坐标 |
| y = statesy[i] + 200; | 计算垂直坐标 |
| thingelem.style.top = String(y)+"px"; | 将style.top设置为 x |
| thingelem.style.left= String(x)+"px"; | 将style.left设置为 y |
| stateelements.push(thingelem); | 添加到stateelements数组 |
| } | 关闭for回路 |
| questionfel = document.getElementById(“questionform”); | 设置对表单的引用 |
| questionfel.style.left = "100px"; | 水平放置表单 |
| questionfel.style.top = "500px"; | 垂直放置表单 |
| questionfel.question.value = " "; | 清除问题字段 |
| questionfel.feedback.value = "  "; | 清除反馈字段 |
| } | 关闭setupgame功能 |
| function pickstate(ev) { | pickstate功能的标题 |
| var picked = Number(ev.target.id.substr(1)); | 提取并计算玩家选择的州的指数 |
| if (picked == choice) { | 与选择相比 |
| questionfel.feedback.value = "Correct!"; | 显示正确的反馈 |
| } | 关闭条款 |
| else { | 其他 |
| questionfel.feedback.value = "Try Again."; | 显示反馈以重试 |
| } | 关闭条款 |
| } | 关闭pickstate功能 |
| function spread() { | spread功能的标题 |
| var i; | 用于索引 |
| var x; | 对于 x 值 |
| var y; | 对于 y 值 |
| var thingelem; | 对于元素 |
| for (i=0;i<nums-2;i++) { | 遍历 48 个州 |
| x = 2.70*statesx[i] +410; | 拉伸 x 并添加常数 |
| y = 2.70*statesy[i] + 250; | 拉伸 y 并添加常数 |
| thingelem = stateelements[i]; | 获取第 i 个元素 |
| thingelem.style.top = String(y)+"px"; | 设置style.top |
| thingelem.style.left= String(x)+"px"; | 设置style.left |
| } | 关闭for回路 |
| } | 关闭spread功能 |
| function restore() { | restore功能的标题 |
| var i; | 用于索引 |
| var x; | 对于 x |
| var y; | 为了你 |
| var thingelem; | 对于元素引用 |
| var df; | 用于移除fullpage |
| var lsname = "jigsaw"; | localStorage的名称 |
| var xydata = []; | 用于储蓄 |
| var stringdata; | 用于储蓄 |
| if (doingjigsaw) { | 检查doingjigsaw是否为true |
| doingjigsaw = false; | 设置为false |
| d.onmousedown = ""; | 移除事件处理 |
| d.onmousemove = ""; | 移除事件处理 |
| d.onmouseup = ""; | 移除事件处理 |
| df=``document.getElementById("fullpage"); | 参考 |
| bodyel.removeChild(df); | 移除df |
| for (i=0;i<nums;i++) { | 迭代状态 |
| thingelem = stateelements[i]; | 获取对第 i 个状态元素的引用 |
| xydata.push(thingelem.style.top+"&"+thingelem.style.left); | 创建一个组合了顶部和左侧设置的字符串,并将其添加到xydata数组 |
| } | 关闭for回路 |
| stringdata = xydata.join(";"); | 从数组生成一个字符串 |
| try { | 尝试(因为localStorage可能有问题) |
| localStorage.setItem(lsname,stringdata); | 设置localStorage项 |
| } | 结束try子句 |
| catch(e) { | catch条款 |
| alert("data not saved, error given: "+e); | 出错信息 |
| } | 关闭catch子句 |
| } | 如果doingjigsaw关闭 |
| for (i=0;i<nums;i++) { | 迭代状态 |
| x = statesx[i] +310; | 将 x 设置为原始 x 坐标 |
| y = statesy[i] + 200; | 将 y 设置为原始 y 坐标 |
| thingelem = stateelements[i]; | 获取对第个状态的引用 |
| thingelem.style.top = String(y)+"px"; | 设置style.top |
| thingelem.style.left= String(x)+"px"; | 设置style.left |
| } | 关闭for回路 |
| } | 关闭恢复功能 |
| function restorepreviousjigsaw() { | restorepreviousjigsaw功能的标题 |
| var i; | 用于索引 |
| var lsname = "jigsaw"; | 用于localStorage的名称 |
| var xydata; | 将用于提取数据 |
| var stringdata; | 将用于提取数据 |
| var ss; | 将为一个状态按住组合的顶部和左侧 |
| var ssarray; | 将用于提取数据 |
| var thingelem; | 第 i 个状态元素的引用 |
| try { | 尝试 |
| stringdata = localStorage.getItem(lsname); | 以“jigsaw”的名称获取保存在localStorage中的数据 |
| xydata = stringdata.split(";"); | 从stringdata生成一个数组 |
| for (i=0;i<nums;i++) { | 迭代状态 |
| ss = xydata[i]; | 提取xydata的第 i 个元素 |
| ssarray = ss.split("&"); | 拆分该字符串以获得两个值 |
| thingelem = stateelements[i]; | 获取第 i 个元素 |
| thingelem.style.top = ssarray[0]; | 设置style.top为第 0 项 |
| thingelem.style.left = ssarray[1]; | 将style.left设为第一项 |
| } | 关闭for回路 |
| doingjigsaw = true; | 准备拼图 |
| stateelements[choice].style.border=""; | 移除任何边框 |
| d.onmousedown = startdragging; | 设置事件处理 |
| d.onmousemove = moving; | 设置事件处理 |
| d.onmouseup = release; | 设置事件处理 |
| var df = document.createElement('div'); | 创建一个div |
| df.id = "fullpage"; | 给它一个 IDfullpage |
| bodyel.appendChild(df); | 附加到正文 |
| questionfel.question.value = ""; | 清除问题字段 |
| questionfel.submitbut.value = "Save & close jigsaw"; | 设置提交按钮的标签 |
| questionfel.feedback.value = "  "; | 清除反馈字段 |
| questionfel.style.zIndex = 100; | 将表单设置在顶部 |
| } | 关闭try子句 |
| catch(e) { | 捕捉 |
| alert("Problem in restoring previous puzzle. Click on Do jigsaw.");} | 显示警告框 |
| } | 关闭restorepreviousjigsaw功能 |
| var choice = 0; | 持有正确答案的全局变量 |
| function setupfindstate(){ | setupfindstate功能的标题 |
| var i; | 用于索引 |
| var thingelem; | 元素引用 |
| stateelements[choice].style.border=""; | 移除最后选择的边框,如果有的话 |
| choice = Math.floor(Math.random()*nums); | 随机选择问题 |
| for (i=0;i<nums;i++) { | 遍历各州 |
| thingelem = stateelements[i]; | 设置对第 i 个元素的引用 |
| thingelem.addEventListener('click',pickstate,false); | 为此元素设置事件处理 |
| } | 关闭for回路 |
| var nameofstate = names[choice]; | 使用choice作为names数组的索引 |
| questionfel.question.value = "Click on "+nameofstate; | 设置提示 |
| questionfel.feedback.value = "  "; | 清除反馈 |
| questionfel.submitbut.value = ""; | 提交按钮不用于此任务 |
| } | 关闭setupfindstate功能 |
| function setupidentifystate(){ | setupidentifystate功能的标题 |
| stateelements[choice].style.border=""; | 删除以前的边框 |
| stateelements[choice].style.zIndex=""; | 把这个状态放在下一个选择的下面 |
| choice = Math.floor(Math.random()*nums); | 随机选择 |
| stateelements[choice].style.border="double"; | 在选择状态周围设置边框 |
| stateelements[choice].style.zIndex="20"; | 将此元素置于其他元素之上,这样边框将位于顶部 |
| questionfel.question.value = "Type name of state with border HERE"; | 设置提示,指示在哪里键入答案 |
| questionfel.submitbut.value = "Submit name"; | 为按钮设置标签 |
| questionfel.feedback.value = "  "; | 清除反馈字段 |
| var thingelem; | 用于保存对元素的引用 |
| for (i=0;i<nums;i++) { | 迭代状态 |
| thingelem = stateelements[i]; | 设置为第 i 个元素 |
| thingelem.removeEventListener('click',pickstate,false); | 移除事件处理 |
| } | 关闭for回路 |
| } | 关闭setupidentifystate功能 |
| function checkname() { | checkname功能的标题 |
| if (doingjigsaw) { | 如果玩家在玩拼图游戏。。。 |
| restore(); | 。。。调用恢复 |
| } | 结束子句 |
| else { | 否则 |
| var correctname = names[choice]; | 这是正确的名字 |
| var guessedname = document.questionform.question.value; | 这是玩家输入的内容 |
| if (guessedname==correctname) { | 玩家是正确的吗? |
| questionfel.feedback.value = "Correct!"; | 显示反馈 |
| } | 结束子句 |
| else { | 其他 |
| questionfel.feedback.value = "Try again."; | 显示反馈 |
| } | 结束子句 |
| return false; | 返回false防止刷新(可能不需要) |
| } | 结束如果不拼图条款 |
| } | 关闭checkname功能 |
| function checkpositions() { | checkpositions功能的标题 |
| var i; | 索引 |
| var x; | 对于 x |
| var y; | 对于 y |
| var tolerance = 20; | 定位允许的边距 |
| var deltax = []; | 将保持 x 差异 |
| var deltay = []; | 将保持 y 差异 |
| var delx; | 用于计算 |
| var dely; | 用于计算 |
| for (i=0;i<nums-2;i++) { | 迭代前 48 个州;不检查阿拉斯加或夏威夷 |
| x = stateelements[i].style.left; | x 是这个州的左边 |
| y = stateelements[i].style.top; | y 是这个州的榜首 |
| x = x.substr(0,x.length-2); | 移除px |
| y = y.substr(0,y.length-2); | 移除px |
| x = Number(x); | 转换为数字 |
| y = Number(y); | 转换为数字 |
| delx = x - statesx[i]; | 计算 x 偏移的差值 |
| dely = y - statesy[i]; | 计算与 y 轴偏移量的差值 |
| deltax.push(delx); | 添加到deltax数组 |
| deltay.push(dely); | 添加到deltay数组 |
| } | 关闭for回路 |
| var averagex = doaverage(deltax); | 计算所有 x 差异的平均值 |
| var averagey = doaverage(deltay); | 计算所有 y 差异的平均值 |
| for (i=0;i<nums;i++) { | 重复 |
| if ((Math.abs(averagex - deltax[i])>tolerance) &#124;&#124; (Math.abs(averagey-deltay[i])>tolerance)) { | 检查 x 差异或 y 差异是否大于各自平均值的公差 |
| break; | 如果是,退出循环 |
| } | 关闭条款 |
| } | 关闭for回路 |
| if (i<nums) { | 环路是否过早中断? |
| questionfel.feedback.value = names[i]+" and maybe more out of position"; | 设置反馈以显示被发现不在正确位置的状态 |
| } | 关闭条款 |
| else { | Else 循环没有过早结束;可以在这里检查夏威夷和阿拉斯加 |
| questionfel.feedback.value = "GOOD"; | 显示反馈 |
| } | 关闭条款 |
| } | 关闭checkpositions功能 |
| function doaverage(arr) { | doaverage功能的表头;参数是一个数组 |
| var sum; | 在计算中用作累加器 |
| var i; | 用于索引 |
| var n = arr.length; | 数组长度 |
| sum = 0; | 初始化为零 |
| for(i=0;i<n;i++) { | 迭代元素 |
| sum += arr[i]; | 添加第 i 个值 |
| } | 关闭for回路 |
| return (sum/n); | 返回除以数字 n 的总和 |
| } | 关闭doaverage功能 |
| function setupjigsaw() { | setupjigsaw功能的标题 |
| doingjigsaw = true; | 将标志设置为true |
| stateelements[choice].style.border=""; | 删除任何以前的边框 |
| var i; | 用于索引 |
| var x; | 对于 x 值 |
| var y; | 对于 y 值 |
| var thingelem; | 参考状态元素 |
| for (i=0;i<nums;i++) { | 迭代状态 |
| x = 100+Math.floor(Math.random()*600); | 为 x 选择随机值 |
| y = 100+Math.floor(Math.random()*320); | 为 y 选择随机值 |
| thingelem = stateelements[i]; | 设置第 i 个元素 |
| thingelem.style.top = String(y)+"px"; | 顶部位置 |
| thingelem.style.left =String(x)+"px"; | 左侧位置 |
| thingelem.removeEventListener('click',pickstate,false); | 移除事件处理 |
| } | 关闭for回路 |
| d.onmousedown = startdragging; | 设置事件处理 |
| d.onmousemove = moving; | 设置事件处理 |
| d.onmouseup = release; | 设置事件处理 |
| var df = document.createElement('div'); | 创建div |
| df.id = "fullpage"; | 给它身份证 |
| bodyel.appendChild(df); | 添加到正文 |
| questionfel.question.value = ""; | 清除问题字段 |
| questionfel.submitbut.value = "Save & close jigsaw"; | 更改提交按钮上的标签 |
| questionfel.feedback.value = "  "; | 清除反馈字段 |
| questionfel.style.zIndex = 100; | 将表单设置在顶部 |
| } | 关闭setupjigsaw功能 |
| var d = document; | 保留文档 |
| var ie= d.all; | Internet Explorer 检查;请注意,应用程序尚未检查最新的 Internet Explorer 版本 |
| var mouseDown = false; | 将标志初始化为false |
| var curX; | 当前 x |
| var curY; | 当前 |
| var adjustX; | 用于拖动 |
| var adjustY; | 用于拖动 |
| var movingobj; | 被拖动的对象 |
| function release(e){ | 释放功能的标题 |
| mouseDown = false; | 将标志设置回false |
| checkpositions(); | 对正在完成的谜题调用检查 |
| }; | 关闭release功能 |
| function startdragging(e) { | startdragging功能的标题 |
| var o; | 用于计算偏移量 |
| var j; | 用于保存对元素的引用 |
| var i; | 用于索引 |
| curX = ie ? e.clientX+d.body.scrollLeft : e.pageX; | 计算光标在 x 轴上的位置 |
| curY = ie ? e.clientY+d.body.scrollTop  : e.pageY; | 计算光标在 y 轴上的位置 |
| for (i=0; i<nums;i++) { | 迭代状态 |
| j = stateelements[i]; | 获取第 i 个元素 |
| o = offset(j); | 确定偏移量 |
| if (curX >= o.x && curX <= o.x + j.width && curY >= o.y && curY <= o.y + j.height) | 检查鼠标是否在第个元素上 |
| { break; } | 如果是,离开for循环 |
| } | 子句结束 |
| if (i<nums) { | for循环是否过早退出? |
| movingobj = stateelements[i]; | 将第 i th 设置为移动对象 |
| adjustX = curX- o.x; | 以 x 为单位的数量偏离鼠标光标 |
| adjustY = curY- o.y; | y 件中的数量偏离鼠标光标 |
| mouseDown = true; | 将标志设置为true:运动中的物体 |
| } | 鼠标悬停在对象上的 Close 子句 |
| } | 关闭startdragging功能 |
| function moving(e) { | moving功能的标题 |
| if (!mouseDown) return; | 如果没有移动任何对象,则返回 |
| if (ie) | 检查是否设置了ie标志 |
| draw(e.clientX+d.body.scrollLeft, e.clientY+d.body.scrollTop); | 使用这些值进行绘制 |
| else | 其他 |
| draw(e.pageX, e.pageY); | 使用这些值进行绘制 |
| } | 关闭moving功能 |
| function draw(x, y) { | draw功能的表头;这将移动/拖动状态 |
| var js = movingobj.style; | 提取指向样式的点 |
| js.left = (x - adjustX) + "px"; | 将样式更改为新的 x(左)值 |
| js.top  = (y - adjustY) + "px"; | 将样式更改为新的 y(顶部)值 |
| } | 关闭draw功能 |
| function offset(obj) { | offset功能的表头;添加obj从祖先开始的所有偏移 |
| var left = 0; | 向左初始化 |
| var top  = 0; | 初始化顶部 |
| if (obj.offsetParent) | 有家长吗? |
| do { | 然后 |
| left += obj.offsetLeft; | 向左增量 |
| top  += obj.offsetTop; | 增量顶部 |
| } while (obj = obj.offsetParent); | 有家长就继续走 |
| return {x: left, y: top}; | 返回数组的左值和上值 |
| } | 关闭offset功能 |
| </script> | 结束脚本标记 |
| </head> | 结束标题标签 |
| <body id="body" onLoad="init();"> | Body 标签,onLoad设置为init(); |
| <button onClick="spread();">Spread out states </button> | 展开状态的按钮 |
| <button onClick="restore();">Restore original /compress map </button> | 恢复原始地图的按钮 |
| <button onClick="setupfindstate();">Find the state </button> | 开始Find the state任务的按钮 |
| <button onClick="setupidentifystate();">Name the state</button> | 开始Name the state任务的按钮 |
| <button onClick="setupjigsaw();">Do jigsaw</button> | 开始拼图的按钮 |
| <button onClick="restorepreviousjigsaw();">Restore last jigsaw in process </button> | 按钮来恢复保存的拼图 |
| <h1>USA</h1> | 前往屏幕上的美国拼图 |
| <form id="questionform" name="questionform" onSubmit="return checkname();"> | form标签,用onSubmit设置为checkname调用 |
| State name: <input type="text" name="question" value="   " size="40"/> | 州名的标签和位置 |
| <input name="submitbut" type="submit" value="       " size="30"/> | 提交按钮;值现在为空 |
| Feedback: <input type="text" name="feedback" value="   " size="40" /> | 反馈的标签和位置 |
| </form> | 结束form标签 |
| </body> | 结束body标签 |
| </html> | 结束html标签 |

测试和上传应用程序

这个项目可以使用 Chrome 和 Firefox 进行本地测试(在你的家用电脑上),尽管在某一点上,正如我提到的,这是不正确的。这个应用程序需要 50 个代表州的文件,所以一定要上传它们(或者任何与应用程序的地图部分相对应的文件)。

摘要

在这一章中,你学习了如何为玩家构建一个以不同类型的问题为特色的教育游戏。HTML5 的特性和编程技术包括以下内容:

  • 构建包含文本或视觉提示的用户界面。玩家的反应包括点击屏幕上的元素和输入文本。进入拼图模式后,玩家的动作是拖动和重新定位屏幕上的元素。

  • 使用拆分和连接方法对信息进行编码和解码。

  • 保存和恢复正在进行的工作,包括使用try...catch构造。

  • 重用上一章解释的技术:

    • 动态创建 HTML 标记以在屏幕上创建片段元素

    • 将拼图块随机放置在屏幕上

    • 确定指示拼块如何装配在一起的坐标值,并使用这些值以及定义的公差来检查拼图是否正确装配在一起

    • 操纵片段元素的位置以展开片段并将其恢复到原始位置

在第十章,也就是最后一章,我们探索了准备一个在不同设备上工作的 web 文档的要求,这被称为响应设计,以及使应用程序更广泛地可访问的初始步骤

十、响应式设计和可访问性

在本章中,您将:

  • 学习使您的交互式应用程序可在各种设备上使用的技术

  • 了解如何让人们只使用键盘和屏幕阅读器就能访问您的应用程序

  • 进行随机的多项选择,此外,将这些任务组合起来

  • 查看动态创建 HTML 标记的其他示例

介绍

过去,人们使用台式机或笔记本电脑来使用计算机应用程序!现在,许多人希望在他们的平板电脑或智能手机上查看和使用计算机应用程序,包括网页。此外,许多人希望在所有三类设备上访问一个网站,并有类似的体验。他们还可以选择修改台式机或笔记本电脑上的窗口尺寸,或者改变移动设备的方向。准备一个使用 HTML、CSS 和 JavaScript 制作的项目以适应设备(和设备的状态)被称为响应式设计。另一个不同但相似的目标是准备一个可供各种用户访问的项目。在这种情况下,一个关键的挑战是使应用程序适合使用屏幕阅读器的有视觉障碍的人和/或只能使用键盘的人。在这一章中,我描述了一些有助于实现这些目标的技术,重点是一些具体的例子。

图 10-1 显示了一个调整到设备尺寸的 HTML 和 JavaScript 项目的截图。该程序通过在台式机或笔记本电脑上用鼠标或在平板电脑或手机上触摸来循环播放一系列图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-1

显示程序的打开屏幕

按下鼠标按钮并向下移动或用手指向下触摸并向下移动会使下一张图片逐渐显示,直到接近底部。图 10-2 为正在更改中的图片。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-2

从一张图片到下一张图片的变化正在进行中

当鼠标或手指足够近时,整个下一张图片就会出现。类似地,用户/玩家可以在屏幕上向上移动鼠标或手指,并获得先前出现的图片。我鼓励读者尝试源代码。

图 10-3 显示了一个竞猜游戏的截图,可以用鼠标或触摸或键盘单独操作。这一组四个国家是从 G20 国家中随机选择的,相应的首都是混在一起的,所以每次您都会看到一组不同的项目。该测验可以仅使用键盘和屏幕阅读器程序进行,tab 键将玩家从一个项目带到另一个项目,使其适合视觉能力有限或没有视觉能力的人和/或不能使用鼠标或触摸的人。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-3

国家/首都测验的开始屏幕

“动作”和“分数”字段显示了到目前为止的表现。这些框会改变颜色,并移动到匹配框的旁边,让视觉能力强的人觉得更有趣。当匹配正确时,使用黄色/金色。如说明所示,当玩家正确匹配四次时,将播放一段视频。图 10-4 为截图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-4

成功完成测验的屏幕截图

视频有声音,因此视障人士也能获得奖励。

注意

屏幕阅读器很复杂,并提供自定义使用的选项。我使用的一个屏幕阅读器单独“说出”国家和首都的名称,但另一个添加了术语“组”,这很烦人。可以使用 Tab 键和 Shift 键来来回听国家和首都的名称。然而,屏幕阅读器会阅读整个屏幕,包括我的浏览器工具栏上的所有内容,当点击文档末尾的 Tab 键时,这种情况会重复出现。我的例子展示了如何在动态生成的 HTML 标记中包含标签信息。我强烈建议您在继续探索屏幕阅读器和键盘操作的使用时,先研究静态 HTML 页面,然后再研究具有动态生成的 HTML 元素的程序。

关键要求

在深入研究具体的技术特性之前,开发人员需要考虑计划中的应用程序的最重要的目标受众以及应用程序在不同情况下的可行性,这一点非常重要。有一个叫做 mobile first 的概念,它建议,如果要在移动设备上使用某些东西,最好的方法是首先设计和规划移动实现,而不是为桌面设计和实现,然后进行调整。从问题规范开始,包括最常见的设备和用户,并制定解决方案是一个好策略。教师和书籍作者经常做一些完全不同的事情:从我们想要解释的概念和功能开始,设计我们认为使用这些功能的有趣程序。

当你设计一个网络应用程序时,考虑某些程序,比如那些具有地理定位功能的程序,最适合移动设备是很重要的。相比之下,需要大量文本输入的程序最适合台式机和笔记本电脑。拼图玩具不适合视力有障碍的人。不过国家/首都的小测验,我本来是做给鼠标或者触控用的,可以改编成键盘操作。考虑不同的屏幕和不同的受众是确定什么对你的应用程序至关重要的一种有价值的方法,并且致力于响应性设计和提高可访问性的过程可以使所有的受众受益。

在这一章中,我将重点介绍如何适应屏幕尺寸,确保除了鼠标之外或代替鼠标使用触摸功能,以及支持屏幕阅读器和至少某些应用程序的纯键盘操作。我还想让用户调整大小,并再次调整到任意宽度和高度的窗口。

我将简要地提到对各种网站有用的特性,通常有一个静态的设计。

屏幕大小和尺寸

您可能希望看到按名称检查特定设备或设备类型的代码,但在许多情况下,这不是推荐的方法。相反,如果要检查的关键属性包括屏幕宽度和屏幕高度,则直接检查这些尺寸。在 HTML 元素属性、CSS 规则和指令以及 JavaScript 代码中有多种方法可以做到这一点。在 HTML、CSS 和 JavaScript 特性一节中,您可以学习,或者至少被介绍了许多细节。

触控

移动设备通常没有鼠标,而是依靠触摸。将触摸解释为鼠标点击是“免费的”,也就是说,不需要额外的编码,将在测验示例中演示。Reveal 应用程序基于按下鼠标、移动鼠标和抬起鼠标的操作序列,它需要 JavaScript 代码来支持触摸。该技术是设置触摸事件来模拟适当的鼠标事件。

屏幕阅读器和选项卡

存在各种各样的屏幕阅读器工具。我使用运行 MacOS High Sierra 的 iMac 上的内置 VoiceOver 功能来测试这个测试程序。有视觉障碍的人和不能操作鼠标的人需要通过键盘完成所有的事情。这包括提供支持使用 Tab 键的编码。对屏幕阅读器和键盘的最佳支持的一般建议是良好的整体组织,将文本分成更小的部分,并为用户看不到的部分提供标签。

HTML、CSS 和 JavaScript 特性

HTML 和 CSS 一起提供了支持响应式设计和可访问性的方法。在涉及更多交互和动态行为的情况下,可能有必要使用 JavaScript,我将重点讨论示例中的 JavaScript 技术。

当标记

标签为浏览器、搜索引擎和其他网络程序提供关于文档的信息。没有显示任何内容。charset meta标签

<meta charset="UTF-8">

指定要使用的字符集。UTF-8 指定是默认的,表示 1 到 4 字节的 Unicode 标准。Unicode 的目的是支持世界上所有的语言,尽管可能不完全是这样,但大多数语言,包括日语和中文,都是受支持的。尽管 Unicode 是默认设置,但如果没有这个meta标记,web 控制台上仍会显示一条警告消息,因此包含它将防止您在访问 web 控制台时看到该消息。

建议使用以下meta标签将宽度设置为器件宽度:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

我在 Reveal 示例中使用了以下代码,允许用户在移动设备上缩放窗口。这仅适用于移动设备。

<meta name="viewport"
content="width=device-width, user-scalable=yes, initial-scale=1.0, minimum-scale=1.0, maximum-scale=2.0" />

如果图像或视频元素没有给定宽度或高度属性设置或给定固定量,使窗口变小将导致滚动。垂直滚动被认为是可以接受的,但水平滚动是不能接受的。下一节将描述产生预期效果的技术。

HTML 和 CSS 使用百分比和自动

以像素为单位指定img等元素的宽度和高度是一种标准做法。如果只使用 HTML 为body元素中的元素指定了一个,则修改另一个以保持纵横比。在style部分,使用 CSS,可以使用术语auto。这是默认设置,但我喜欢明确地提到它,主要是作为对我的提醒。

以像素为单位指定宽度或高度的一种变化是将维度指定为包含元素的百分比。包含元素可以是body元素或div或语义标签或其他东西。块显示元素的默认宽度尺寸,如div,是屏幕的 100%。可以指定另一个百分比,比如 50%或 80%。一个例子是在正文中包含

<img id="animal" width="50%" src="monkey.jpg">

这将设置图像的宽度占屏幕的 50%,高度保持纵横比。在“样式”部分,以下任一项

#animal {width:50%; height: auto;}
#animal {width:50%;}

会产生同样的效果。

如果应用程序窗口被操作(比如说,在桌面上)为比计算的高度短,那么图像将被切掉,并且将出现滚动条用于垂直滚动。如果想要的效果是在宽度(或高度)上设置一个界限,但不将图像拉伸到超出其原始尺寸,可以使用max-widthmax-height属性。一般来说,垂直滚动比水平滚动更容易被接受,所以通常只需指定宽度或max-width就可以获得响应。这是我在国家/首都测验中制作奖励视频时使用的方法。

百分比可与宽度和/或max-width一起使用,为元素设置网格布局。我鼓励你尝试这些特性。许多 W3Cschool 示例的 TRY-IT 特性很有帮助。

您将看到我如何使用 JavaScript 修改宽度和高度。

CSS @media

Web 开发人员可以在style元素中设置@media查询。这些提供了一种方法来检查设备的属性,并为某些条件指定样式指令。例如,在我的购物网站上,我指定某些元素属于一类,名为col。如果屏幕足够宽,我希望这些元素以列的形式分布在窗口中。然而,如果屏幕宽度很小,我不想要求水平滚动,而是让col元素垂直显示,假设用户将垂直滚动。下面的@media指令产生了这种效果:

@media all and (max-width: 640px)
   {.col {display: block; width: 100%;}}

如前所述,这是为测试手机等狭窄设备而建议的技术。@media特性也可以用于为计算机和设备的屏幕指定不同的格式,术语print用于打印网页,术语speech用于屏幕阅读器。

一个@media查询可以有修饰符notonly。举个例子,

@media only screen and (max-width: 600px) {
    body {
        background-color: lightblue;
    }
}

为在屏幕上使用的 body 元素中的所有内容生成浅蓝色的背景色(这是已知的颜色名称之一(见 https://www.w3schools.com/Colors/colors_names.asp )。更多的例子和解释请参考 https://www.w3schools.com/CSSref/css3_pr_mediaquery.asp

HTML alt 属性和语义元素

元素的属性为屏幕阅读器提供信息。如果文件丢失或下载缓慢,将显示alt属性的值。在正常情况下建议使用alt元素,检查可访问性的程序将指示任何没有alt元素的img标签。思考什么是alt属性可能是开发网页的一个重要练习。请注意,我在这个例子中的代码没有显示img元素,这只是用来确保图片被完全下载。因此,我认为包含alt属性并不合适。

语义元素可以提供屏幕阅读器可以使用的信息。当在大型项目中与其他人一起工作时,术语headerfootermainsectionarticle等是有意义的。它们没有必须提供的特定格式。

HTML tabIndex

依赖屏幕阅读器或者不能或不习惯使用鼠标或触摸的人依赖于使用 Tab 键来浏览文档。可以为任何元素设置tabIndex属性。按 tab 键将用户带到 Tab 键顺序中的下一个元素(按数字顺序从低到高进行)。按 Tab 键和 Shift 键可以反转方向。元素的tabindex可以在准备 HTML 文档时设置,也可以在动态创建 HTML 标记时通过编码产生。在 quizTab 应用程序中,我包含了以下语句

d.innerHTML = (
"<div tabIndex='"+String(2+i)+"' class="thing" id='"+uniqueid+"'>placeholder</div>");

该代码为国家名称的tabindex产生连续的值。

在 web 页面的操作过程中,可以更改tabindex,尽管在我的例子中我没有这样做。玩(参加)测验确实意味着多次浏览项目,这确实意味着再次听到指示,并且再次进入浏览器的地址栏,并且在某些情况下,听到浏览器中选项卡所代表的所有活动站点。

宽度和高度属性的 JavaScript 用法

任何计算机或设备的浏览器都会调整文本的线宽以适应窗口。然而,我也想调整说明中的字体大小。指令使用我的代码根据计算选择的字体大小显示。使用以下语句设置fontsz数组:

var fontsz = ["14px","16px","18px","20px","24px"];

字体的大小在init函数中使用已经分配了代码为window.innerWidth.cwidth变量进行设置

  fs = Math.floor (cwidth/200);
  fs = Math.min(fs,4);
  bodyel.style.fontSize = fontsz[fs];

在 Reveal 示例中,我给自己设定的挑战是图像要适合窗口,不需要任何滚动,同时保持比例。我使用的属性包括窗口的window.innerWidthwindow.innerHeight,图像的widthnaturalWidthheightnaturalHeight。“自然”属性代表图像的原始尺寸。它们不能被改变。对于 Reveal 示例,我已经确保所有的图像都有相同的尺寸,所以我只需要做一组计算。代码检查宽度是否小于屏幕宽度并调整高度,然后确保高度小于屏幕高度并调整宽度。您可以回到第八章,了解在drawImage方法中使用的计算值的变化。

动态创建元素

Reveal 示例中的图像序列是通过将每个图像绘制到 canvas 元素中来动态实现的。对于任何涉及网络上的图像或其他媒体的作品,确保文件下载完成是至关重要的。我通过在主体中包含img元素,但在样式部分将可见性设置为隐藏来实现这一点。然后,我的代码调用一个名为init的函数来完成创建 canvas 元素的所有工作,将每个图像绘制到它的 canvas 元素中,并将第一个图像绘制到主体中设置的 canvas 中。

国家/首都测验也动态创建元素。这些是长方形,上面有国家和首都的名称。HTML 标记是用为idclasstabindex设置的属性创建的。id值将索引保存在facts数组中,用于确定玩家是否正确匹配了国家和首都。

从列表中选择

测验示例随机选择两种情况。如何做出一个随机的选择是很简单的。然而,对于这个程序,我需要从 20 个国家的事实数组中随机选择四个国家/首都对,但不允许重复。然后,对于每个国家和首都对,由于我不希望每个首都的名称与其国家名称相对,所以我需要代码从第二列中代表位置的四个位置中随机选择每个首都的位置。这也需要做到不重复。注意:可能会有这样的情况,一个首都确实结束于其国家的对面,但大多数时候不会发生。参见图 10-3 中墨西哥城、安卡拉、华盛顿特区和布宜诺斯艾利斯的位置。

我解决这个问题的第一步是让facts数组保存一些东西来告诉我是否有一个事实被接受了。facts数组是一个数组的数组,内部数组有三个元素:country、capital 和 true/false。false 设置意味着事实没有被选择,true 意味着它已经被选择。slots数组将保存四个首都名称的索引。我将使用-100 的初始设置来表示一个槽没有被占用。它实际上可以是任何小于零的数。当选择一个槽时,slots数组中的相应值被设置为facts数组中国家/首都的索引值。请注意,我可以在这里使用任何非负数,因为我(我的代码)不使用该值,但我在考虑未来可能的应用。

我在这两种情况下使用的编码结构是一个do/while循环。do/while结构可以在很多情况下使用,所以请记住它。用一般术语来描述:括号中的代码至少被调用一次。然后评估术语while后面括号中的条件。如果为真,则再次执行括号中的代码。括号中可以有多个语句。一种伪代码的思考方式是

do { one or more statements }
   while (repeat if this condition is true )

选择facts的检查由以下代码完成:

do {c = Math.floor(Math.random()*facts.length);}
             while (facts[c][2]==true)

如果已经选择了子数组facts[c]中表示的事实,将重复对变量c的赋值。

这段代码完成了对选择插槽的类似检查。

do {s = Math.floor(Math.random()*nq);}
             while (slots[s]>=0)

表示该位置已被占用的值由大于或等于零的slots[s]表示,因此如果该位置已被占用,将重复随机选择。

鼠标事件、触摸事件和按键事件

响应式设计有两个主要问题需要解决。我已经描述了检查和修改元素以适应窗口大小。第二个考虑是提供触摸而不是鼠标事件。触摸事件的处理通过模拟鼠标事件来完成。鼠标事件大概已经被定义了。正如我已经提到的,某些触摸事件的处理不需要任何额外的编程。这些都是简单的事件,比如点击一个元素。但是,mousedownmousemovemouseup等事件需要翻译。这是因为计算需要鼠标或触摸的准确位置来从源画布绘制到显示的画布。

init函数中,addEventListener方法涉及五个事件。如果这段代码在没有这些事件的设备上执行,那么引用任何不可能发生的事件都没有问题。

  canvas.addEventListener("mousedown",startreveal,true);
  canvas.addEventListener("touchstart", touchHandler, true);
  canvas.addEventListener("touchmove", touchHandler, true);
  canvas.addEventListener("touchend", touchHandler, true);
  canvas.addEventListener("touchcancel", touchHandler, true);

touchHandler函数执行的任务是确定要模拟哪个鼠标事件(使用 switch 语句),创建事件(使用 new MouseEvent),然后调度它。MouseEvent函数使用一个关联数组(也称为字典),其中设置了某些属性。对于这个例子,我让其他属性采用默认值。

function touchHandler(event)
{
  var touches = event.changedTouches;
  if (touches.length>1) {
    return false;
  }
  var first = touches[0];
  var type = "";
  switch(event.type)
    {
        case "touchstart": type = "mousedown"; break;
        case "touchmove":  type="mousemove"; break;
        case "touchend":   type="mouseup"; break;
        default: return;
    }
   var simulatedEvent = new MouseEvent(type,{
           screenX: first.screenX,
           screenY: first.screenY,
           clientX: first.clientX,
           clientY: first.clientY
  });

    first.target.dispatchEvent(simulatedEvent);
    event.preventDefault();
}

注意

构造函数MouseEvent相对较新,取代了document.createEvent("MouseEvent")的使用,后者现在被标记为不推荐使用,这意味着不鼓励使用它,将来可能不会被识别。工具的变化是我们需要接受的。事实上,与旧方法相比,新方法有一个显著的优势:对参数使用了一个关联数组,而不是由位置指示的一长串参数,其中大多数参数采用默认值。

请注意,如果有多点触摸手势,则不会发生任何事情,同时请注意,任何默认操作都会被阻止。我知道至少有一个商业纸牌游戏——一个 iPad 应用程序——没有做到这一点,所以当移动一张牌时,整个棋盘可能会移动。

与鼠标和触摸的问题无关:如果 Reveal 程序中的用户用鼠标或手指在图像的右边或下面(屏幕的更下方)按下,什么都不会发生。通过在startreveal函数中使用以下代码,可以忽略这种不良行为:

var startxy = getCoords(ev);
   if (startxy[0]>pwidth) return;
   if (staryxy[1]>pheight) return;

类似地,如果玩家在测验应用程序中点击两个国家名或两个大写字母,程序不会提供特殊的反馈,但会将第二个项目放在第一个项目的旁边。它不会被视为正确答案,因为两个id值不匹配。

在构建自己的应用程序时,您必须决定在我们称之为不良行为的情况下提供什么反馈(如果有的话)。

对于测验示例,触摸(点击)事件的事件处理被解释为手机和平板设备的鼠标点击。然而,我给自己设定了支持键盘操作的挑战。我所做的是设置keyup事件来调用我的pickelement函数,但是在那个函数中,如果 keycode 是 9,即 Tab 的 keycode,就返回。因此,使用键盘的玩家可以切换到每个国家和首都项目,听到屏幕阅读器说出名称,然后按 Return 键选择一个项目,或者切换到下一个项目。

构建 Reveal 应用程序并使其成为您自己的应用程序

揭示程序从以下事件和动作序列开始。

  1. 当文档被完全加载时,包括图像,调用init函数。在玩家重新加载和调整大小之后,调用init函数。请注意,由于 style 元素中的指令,这些图像是不可见的。

  2. init函数决定窗口的尺寸,并使用该信息选择字体的大小。

  3. init函数调用setupimages函数。

  4. setupimages函数进行计算以确保图像适合窗口,保持纵横比。它为每个图像创建一个画布元素。

  5. 返回到init功能,设置mouseDown和所有触摸事件。第一个图像被绘制到 canvas 元素中,从画布绘制到画布。设置nextprev变量。

显示下一张图片的动作由函数startrevealrevealingstopreveal处理,被叫和主叫关系如表 10-1 所示。我决定允许用户上下滑动并改变方向。我还决定,如果垂直水平在顶部或底部的蒙混因素之内,就完成过渡。我的意图是通过程序中嵌套的if/else语句实现的。

表 10-1

揭示的功能关系

|

功能

|

由…调用

|

祈求者

|
| — | — | — |
| init | body标签中的onloadonresize属性 | setupimages |
| setupimages | init |   |
| touchHandler | initaddEventListener的调用 |   |
| getCoords | startreveal, revealing |   |
| startreveal | 在initstopreveal调用addEventListener | getCoords |
| revealing | startrevealaddEventListener的调用 | getCoords |
| stopreveal | startrevealaddEventListener的调用和揭示中的直接调用 |   |

表 10-2 显示了 reveal 程序的注释代码。

表 10-2

显示程序代码

|

密码

|

描述

|
| — | — |
| <!DOCTYPE HTML> | 页眉 |
| <html> | html标签 |
| <head> | head标签 |
| <title>Reveal next</title> | 完整的标题元素 |
| <meta name="viewport" | 视口起点 |
| content="width=device-width, user-scalable=yes,``initial-scale=1.0, minimum-scale=1.0,``maximum-scale=2.0" /> | 指示用户更改的处理方式 |
| <meta charset="UTF-8"> | 将字符集指定为 unicode |
| <style> | 样式标签 |
| body { | 身体指令 |
| font-family: Garamond, serif; | 字体是 Garamond,如果可用,否则衬线 |
| font-size: 24px; | 笔记大小可能会改变 |
| overflow: hidden; | 如果太大,没有溢出和滚动 |
| } | 关闭几何体指令 |
| div#images {display:none;} | 不显示任何图像;用于绘制到画布元素中的图像 |
| </style> | 关闭style元素 |
| <script> | script元素的开始 |
| var ctx; | 将保存画布元素的上下文 |
| var fudge = 40; | 不需要用户一直向上或向下才能看到下一幅图像;这是一个“忽悠因素”,意思是给用户一点空间 |
| var canvas; | 将容纳画布 |
| var pwidth; | 图片的宽度 |
| var pheight; | 图片的高度 |
| var cwidth; | 窗口宽度 |
| var cheight; | 窗口高度 |
| var current = 0; | 从第 0 张到第张图片开始 |
| var prev = 3; | 从索引 3 处的前一张图片开始 |
| var next = 1; | 下一张图片在索引 1 处 |
| var rect; | 用于获取鼠标坐标 |
| var revealflag = false; | 旗 |
| var lastdrawn; | 跟踪最后一张图片 |
| var lasty; | 最后一个 y 值 |
| var moving = false; | 设置以指示移动鼠标 |
| var canvases = []; | 会为所有的图片准备画布 |
| var fontsz = ["14px","16px","18px","20px","24px"]; | 可能的字体大小 |
| function init() { | init功能的标题 |
| var fs; | 用于确定字体大小 |
| canvas=document.getElementById("canvas"); | 指向画布元素的指针 |
| bodyel = document.getElementById("body"); | 指向主体元素的指针 |
| ctx = canvas.getContext("2d"); | 画布的上下文 |
| ctx.font = "24px serif"; | 默认字体和大小 |
| cwidth = window.innerWidth; | 窗口宽度 |
| cheight = window.innerHeight; | 窗口高度 |
| fs = Math.floor (cwidth/200); | 计算字体大小 |
| fs = Math.min(fs,4); | 最小值为 4 |
| bodyel.style.fontSize = fontsz[fs]; | 将正文中指令的字体大小设置为fontsz数组的fs元素 |
| canvas.width = cwidth; | 设置画布宽度 |
| canvas.height= cheight; | 设置画布高度 |
| rect = canvas.getBoundingClientRect(); | 用于确定鼠标坐标 |
| var noOfImgs = document.getElementsByTagName('img').length; | 确定文档中img元素的数量 |
| setupimages("noodles", noOfImgs); | 执行任何缩放后,调用将设置图像(图片)画布的函数 |
| canvas.addEventListener("mousedown",``startreveal,true); | 设置mousedown事件 |
| canvas.addEventListener("touchstart",``touchHandler, true); | 所有触摸事件都会调用touchHandler |
| canvas.addEventListener("touchmove",``touchHandler, true); |   |
| canvas.addEventListener("touchend",``touchHandler, true); |   |
| canvas.addEventListener("touchcancel",``touchHandler, true); |   |
| ctx.drawImage(canvases[0],0,0); | 将第一张(第 0 张到第 1 张索引)图片绘制到画布上 |
| current = 0; | 设置电流;这条语句和接下来的两条语句是onresize调用init所必需的 |
| prev = 3; | 设置prev |
| next = 1; | 设置next |
| } | 关闭init |
| function setupimages (base, lim){ | setupimages功能的表头;基数表示图像名称的开头,而lim表示图像的数量 |
| var dref; | 对第一幅图像的引用;这个用来计算比例因子 |
| var can; | 会指向每一张画布 |
| var canctx; | 将保存每个画布的上下文 |
| canvases = []; | 将保存创建的画布和图像,所有图像都缩放到适当的大小;你可以把它们想象成保存图像的缓冲器 |
| var img; | 将依次按住每个img |
| dref = document.getElementById("dummy"); | 获取对第一幅图像的引用 |
| if (dref.naturalWidth) { | 这提供了图像的原始宽度 |
| dref.width = dref.naturalWidth; | 将宽度设置为该值 |
| pratio = dref.naturalHeight/dref.naturalWidth; | 计算纵横比 |
| } | 关闭if |
| else { | 当naturalWidth不存在时不太通用,这可能是某些浏览器的情况 |
| pratio = dref.height/dref.width; | 计算比率 |
| } | 关闭 else |
| dref.width = Math.min(dref.width,cwidth-fudge); | 现在可能重置宽度 |
| dref.height = pratio * dref.width; | 设置高度以匹配可能修改的宽度 |
| dref.height = Math.min(dref.height,cheight-fudge); | 现在可能修改高度 |
| dref.width = dref.height * (1/pratio); | 设置宽度以匹配可能修改的高度 |
| pwidth = dref.width; | 设置变量以备后用 |
| pheight = dref.height; | 设置变量以备后用 |
| for(var i=1;i<=lim;i++){ | 通过将 1、2、3、4 加到基数上,按名称引用图像文件;这个循环缩放图像(重复第一个循环的操作);还要注意画布数组中的项目位于索引位置 0,1,2,3 |
| img = new Image(); | 创建一个Image对象 |
| img.width = pwidth; | 设置宽度 |
| img.height = pheight; | 设置高度 |
| img.src=base+String(i)+".jpg"; | 使用基数和数字设置src |
| can = document.createElement("canvas"); | 创建画布 |
| can.width = cwidth; | 设置其宽度 |
| can.height = cheight; | …和高度 |
| canctx = can.getContext('2d'); | 将canctx设置为上下文 |
| canctx.drawImage(img,0,0,pwidth,pheight); | 将图像绘制到画布上 |
| canvases.push(can); | 推入canvases数组 |
| } | 关闭for回路 |
| } | 关闭setupimages功能 |
| function touchHandler(event) { | touchHandler的标题 |
| var touches = event.changedTouches; | 获取触摸数组 |
| if (touches.length>1) { | 如果不止一个 |
| return false; | 返回;不要为多点触控手势做任何事情 |
| } | 关闭if |
| var first = touches[0]; | 进行第一次(也是唯一一次)接触 |
| var type = ""; | 初调 |
| switch(event.type)    { | 开启事件类型 |
| case "touchstart": type = "mousedown"; break; | 将类型变量设置为相应的鼠标事件 |
| case "touchmove":  type="mousemove"; break; |   |
| case "touchend":   type="mouseup"; break; |   |
| default: return; | 什么也不做 |
| } | 关闭开关 |
| var simulatedEvent = new MouseEvent(type,{``screenX: first.screenX,``screenY: first.screenY,``clientX: first.clientX,``clientY: first.clientY``}); | 创建计算类型的MouseEvent;从触摸事件的位置设置位置 |
| first.target.dispatchEvent(simulatedEvent); | 调度事件将被视为实际事件 |
| event.preventDefault(); | 停止对触摸事件的任何默认响应 |
| } | 关闭touchHandler |
| function getCoords(ev){ | 用于getCoords拾取鼠标位置的标题 |
| var mx; | 将保持水平 |
| var my; | 将保持垂直 |
| mx = ev.clientX-rect.left; | rect变量已经设置;计算mx |
| my = ev.clientY-rect.top; | 计算my |
| return [mx,my]; | 返回一个数组 |
| } | 关闭getCoords |
| function startreveal(ev){ | startreveal的标题 |
| var startxy = getCoords(ev); | 获取鼠标的坐标 |
| if (startxy[0]>pwidth) return; | 如果鼠标或触摸在图像的右边,则没有动作 |
| if (startxy[1]>pheight) return; | 如果鼠标或触摸位于图像下方,则无动作 |
| lasty = Math.max(startxy[1],fudge); | 从顶部开始至少要有一段距离 |
| canvas.addEventListener("mousemove",``revealing,true); | 设置mousemove |
| canvas.addEventListener("mouseup",``stopreveal,true); | 设置mouseup |
| canvas.removeEventListener("mousedown",``startreveal,true); | 停止监听mousedown |
| revealflag = true; | 设置标签 |
| } | 关闭startreveal |
| function revealing(ev){ | 显示标题 |
| var slice; | 将指示从下一张图片中提取多少(垂直量) |
| var curxy; | 将保持鼠标位置 |
| if (!revealflag) return; | 如果不在显示阶段,返回 |
| curxy = getCoords(ev); | 获取鼠标坐标 |
| cury = curxy[1]; | 设置cury |
| if (moving){ | 检查移动标志 |
| if (cury>=lasty){ | 如果比上次更低 |
| if (cury<(pheight-fudge)){ | 如果它不在底部 |
| slice = Math.max(1,cury-lasty) | 计算高度 |
| ctx.drawImage(canvases[next],0,lasty,pwidth,``slice,0,lasty,pwidth,slice); | 从下一张画布开始绘制 |
| lastdrawn = next; | 为下一步行动做准备 |
| lasty = cury; |   |
| } | 关闭if |
| else { | 马上去准备下一个 |
| lastdrawn = next; | 设置lastdrawn |
| stopreveal(ev); | ev通过与被通过eventhandler |
| } | 为cury<(pheight-fudge)关闭 else |
| } | 关闭cury>=lasty |
| else { | Else cury< lasty,如此向上移动 |
| if (cury>fudge){ | 如果仍然在软糖区之外 |
| slice = Math.max(1,lasty-cury); | 计算切片 |
| ctx.drawImage(canvases[prev],0,cury,pwidth,``slice,0,cury,pwidth,slice); | 从prev图像绘制 |
| lastdrawn = prev; | 将lastdrawn设置为prev |
| lasty = cury; | 设置lasty |
| } | 关闭if |
| else { | 否则在软糖区,所以完成过渡 |
| lastdrawn = prev; | 设置lastdrawn |
| stopreveal(ev); | 调用stopreveal |
| } | 为cury>fudge关闭 else |
| } | 为cury>=lasty关闭 else |
| } | 移动时关闭 |
| else { | 第一部电影 |
| moving = true; | 设置moving |
| if (cury>=lasty){ | 检查方向 |
| prev = current; | 向上移动,因此设置 prev |
| if (cury<(pheight-fudge)){ | 检查上面是否有软糖 |
| slice = Math.max(1,cury-lasty); | 设置切片 |
| ctx.drawImage(canvases[next],0,lasty,pwidth,``slice,0,lasty,pwidth,slice); | 从下一个开始绘制 |
| lastdrawn = next; | 设置lastdrawn |
| lasty = cury; | 设置lasty |
| } | 如果高于软糖,则关闭 |
| else { | 其他 |
| lastdrawn = next; | 设置lastdrawn |
| stopreveal(ev); | 立即转到stopreveal;通过的ev与成为的eventhandler一致 |
| } | 关闭cury<(pheight-fudge)else |
| } | 关闭cury>=lasty移动false |
| else { | 向上移动图像 |
| next = current; | 设置下一个 |
| if (cury>fudge){ | 如果大于软糖 |
| slice = Math.max(1,lasty-cury); | 计算切片 |
| ctx.drawImage(canvases[prev],0,cury,pwidth,``slice,0,cury,pwidth,slice); | 绘制切片 |
| lastdrawn= prev; | 设置lastdrawn |
| lasty = cury; | 设置lasty |
| } | 如果高于软糖,则关闭 |
| else { | 否则(在软糖区) |
| lastdrawn = prev; | 设置 lastdrawn |
| stopreveal(ev); | 立即转到stopreveal |
| } | 关闭 else for if cury>fudge |
| } | 为cury>=lasty关闭 else |
| } | 如果移动,则关闭 else |
| } | 关闭功能 |
| function stopreveal(ev) { | stopreveal的标题 |
| revealflag = false; | 重置〔??〕 |
| moving = false; | 重置移动 |
| ctx.drawImage(canvases[lastdrawn],0,0); | 绘制完整的图像 |
| current = lastdrawn; | 设置电流 |
| next = current+1; | 下一个增量 |
| if (next==canvases.length) next = 0; | 如果在最后,将旁边设置为 0 |
| prev = current-1; | 预测集 |
| if (prev<0) prev=canvases.length-1; | 如果 prev 太低,设置为最后一个指数 |
| canvas.removeEventListener("mousemove",``revealing,true); | 停止监听mousemove |
| canvas.removeEventListener("mouseup",``stopreveal,true); | 停止监听mouseup |
| canvas.addEventListener("mousedown",``startreveal,true); | 为startreveal设置事件 |
| } | 关闭stopreveal |
| </script> | 结束script元素 |
| <body id="body" onload="init();"``onresize="init();"> | Body 标签,带有onloadonresize的设置 |
| Mouse/touch down,``slowly drag mouse/finger down or up the photo,``then mouse/touch up. | 说明 |
| <canvas id="canvas" width="100%"``height="50%" > | 画布;可以缩放 |
| Your browser doesn't support canvas | 标准消息 |
| </canvas> | 画布结束标签 |
| <div id="images"> | div拿着图像 |
| <img src="noodles1.jpg" id="dummy"/> | 第一个用于比例计算 |
| <img src="noodles2.jpg"> |   |
| <img src="noodles3.jpg"> |   |
| <img src="noodles4.jpg"> |   |
| </div> | 关闭div |
| </body> | 关闭body |
| </html> | 关闭html |

测试和上传 Reveal 应用程序

Reveal(我也称之为 uncover)应用程序需要一组相同维度的图像,尽管这个维度不需要是我为吃面的女孩准备的。你可以有不同数量的图像,而不是我的四个。这是通过在setupimages函数中使用document.getElementsByTagName调用来支持的。当然,如果您选择包含其他图像,您将需要调用持有img元素的div来代替文档。

构建国家/首都测验,并使其成为您自己的测验

通过使用Math.random从事实表中选择四个国家/地区大写字母对,并检查以确保不重复任何一对,来设置智力竞赛节目。元素是为国家和首都动态创建的,首都在窗口中出现的顺序是随机的。这些元素是在设置了tabIndex的情况下创建的。当每个元素被创建时,addEventListener被点击事件*调用,*被keyup事件调用。功能及其关系如表 10-3 所示。请注意,这里没有touchhandler事件,因为设备上的浏览器可以正确解释使用触摸的点击事件。更一般地说,设置tabIndex属性提供了选项卡功能,而不需要任何额外的 JavaScript 代码。

表 10-3

用于测验的函数关系

|

功能

|

由…调用

|

祈求者

|
| — | — | — |
| init | 由body标签中的onload属性执行的操作 | setupgame |
| setupgame | init |   |
| pickelement | 在setupgameaddEventListener多次动作 |   |

带注释的代码如表 10-4 所示。

表 10-4

国家/首都测验代码

|

密码

|

描述

|
| — | — |
| <!DOCTYPE html> | 标准标题 |
| <html> | html标签 |
| <head> | head标签 |
| <title>Quiz with Reward!</title> | 完整标题 |
| <style> | style标签 |
| country {position:absolute;left: 0px; top: 0px; border: 2px; border-style: double; background-color: white; margin: 5px; padding: 3px; visibility:hidden;} | 格式化国家区块 |
| capital {position:absolute;left: 0px; top: 0px; border: 2px; border-style: double; background-color: white; margin: 5px; padding: 3px; visibility:hidden;} | 格式化资本块 |
| #vid {position :absolute; visibility:hidden; z-index: 0; max-width: 50%; height: auto;} | 使视频隐藏到播放时间;设置宽度限制 |
| main {display:block;} | 主线前后的力线断开 |
| </style> | 结束style标签 |
| <script type="text/javascript"> | Script标签 |
| var facts = [ | 保存测验信息的变量;内部数组中的第三个位置用于指示该事实是否已被选择用于该游戏 |
| ["China","Beijing",false], |   |
| ["India","New Delhi",false], |   |
| ["European Union","Brussels",false], |   |
| ["United States","Washington, DC",false], |   |
| ["Indonesia","Jakarta",false], |   |
| ["Brazil","Brasilia",false], |   |
| ["Russia","Moscow",false], |   |
| ["Japan","Tokyo",false], |   |
| ["Mexico","Mexico City",false], |   |
| ["Germany","Berlin",false], |   |
| ["Turkey","Ankara",false], |   |
| ["France","Paris",false], |   |
| ["United Kingdom","London",false], |   |
| ["Italy","Rome",false], |   |
| ["South Africa","Pretoria",false], | 注:南非有三个首都;我选择了比勒陀利亚。这是一个匹配的游戏,所以玩家永远不会看到其他两个城市的名字 |
| ["South Korea","Seoul",false], |   |
| ["Argentina","Buenos Aires",false], |   |
| ["Canada","Ottawa",false], |   |
| ["Saudi Arabia","Riyadh",false], |   |
| ["Australia","Canberra",false] |   |
| ]; | 封闭的外部事实阵列 |
| var thingelem; | 用于每个块项目 |
| var nq = 4; | 游戏中提问的问题数量 |
| var elementinmotion; | 用于指示将被移动到下一个玩家匹配项的选定元素 |
| var makingmove = false; | 当玩家选择两个方块时设置为真 |
| var inbetween = 150; | 列间距 |
| var col1 = 0; | 第一列的开始 |
| var row1; | 在 init 中设置;第一行开始 |
| var rowsize = 60; | 为每个项目分配的垂直空间 |
| var slots = new Array(nq); | 将索引保存到资本项目的事实数组中 |
| function init(){ | init功能的标题 |
| row1= .6* window.innerHeight; | 设置row1为总窗高的表达式;如果高度太小,将需要垂直滚动 |
| setupgame(); | 调用setupgame |
| } | 关闭init |
| function setupgame() { | setupgame的标题 |
| var i; | 用于for循环 |
| var c; | 用于计算随机选择 |
| var s; | 用于计算大写字母的候选槽 |
| var mx = col1; | 起始 x 位置 |
| var my = row1; | 起始 y 位置 |
| var d; | 将保存对国家/地区的已创建元素的引用 |
| var uniqueid; | 用于保存所有生成的 id |
| for (i=0;i<facts.length;i++) { | 重复所有的事实 |
| facts[i][2] = false; | 设置为未使用 |
| } | 关闭for回路 |
| for (i=0;i<nq;i++) { | 循环所有插槽 |
| slots[i] = -100; | 设置槽值 |
| } | 关闭循环 |
| for(i=0;i<nq;i++) { | 循环,直到选择了nq个不同的事实 |
| do {c = Math.floor(Math.random()*facts.length);} | Do获取一个随机值 |
| while (facts[c][2]==true) | 如果这一事实成立,重复do条款 |
| facts[c][2]=true; | 现在,选择了一个未采用的事实,将其标记为采用 |
| uniqueid = "c"+String(c); | 生成一个 ID |
| d = document.createElement('country'); | 创建一个元素 |
| d.innerHTML = ( | 设置innerHTML |
| "<div tabIndex='"+String(2+i)+"' class="thing" id='"+uniqueid+"'>placeholder</div>"); | …成为响应style指令的divclass 'thing' |
| document.body.appendChild(d); | 附加到正文(以便显示) |
| thingelem = document.getElementById(uniqueid); | 设置thingelem来引用新创建的元素 |
| thingelem.textContent=facts[c][0]; | 让它的背景成为国家 |
| thingelem.style.top = String(my)+"px"; | 将其定位在my垂直位置… |
| thingelem.style.left = String(mx)+"px"; | … mx水平位置 |
| thingelem.addEventListener('click',pickelement); | 为click设置事件处理 |
| thingelem.addEventListener('keyup',pickelement); | 为keyup设置事件处理 |
| thingelem.style.visibility="visible"; | 将可见性设置为可见 |
| uniqueid = "p"+String(c); | 现在创建一个新的唯一 ID |
| d = document.createElement('cap'); | 创建一个元素 |
| d.innerHTML = ( | 设置innerHTML |
| "<div tabIndex="0" class="thing" id='"+uniqueid+"'>placeholder</div>"); | 物品的div;代码将改变tabIndex |
| document.body.appendChild(d); | 附加到正文,这样它就可见了 |
| thingelem = document.getElementById(uniqueid); | 设置thingelem以引用该元素 |
| thingelem.textContent=facts[c][1]; | 设置其内容 |
|   | 从空位中随机选择一个 |
| do {s = Math.floor(Math.random()*nq);} | 务必将s设为随机值 |
| while (slots[s]>=0) | 如果slots[s]的当前值大于或等于零,则意味着该槽被占用,因此重复do子句以获得新值s |
| slots[s]=c; | 该槽未被占用,因此将其设置为值c |
| thingelem.tabIndex = String(6+s); | 设置其tabIndex |
| thingelem.style.top = String(row1+s*rowsize)+"px"; | 使用s指示的位置将其垂直放置 |
| thingelem.style.left = String(col1+inbetween)+"px"; | 所有大写字母都有相同的水平位置作为第二列 |
| thingelem.addEventListener('click',pickelement); | 为click设置事件处理 |
| thingelem.addEventListener('keyup',pickelement); | 为keyup设置事件处理 |
| my +=rowsize; | 增加my以进入下一行 |
| } | 关闭for回路 |
| document.f.score.value = "0"; | 将分数设置为 0 |
| return false; | 返回,防止文档的任何刷新(可能不需要) |
| } | 关闭setupgame |
| function pickelement(ev) { | pickelement的标题 |
| if (ev.keyCode ===9) {return;} | 如果keycode是 tab 键,立即返回 |
| var thisx; | 将保存指示水平位置的字符串 |
| var thisxn; | 移除px后将保持新的水平位置 |
| var sc; | 得分 |
| if (makingmove) { | 如果makingmove为真 |
| if (this==elementinmotion) { | 如果这是选择的第一个项目,即同一个块被点击两次,则认为这不是一个好的移动,并返回等待第一次点击 |
| elementinmotion.style.backgroundColor = "white"; | 把它变白 |
| makingmove = false; | 重置〔??〕 |
| return; | return |
| } | 如果关闭 |
| thisx= this.style.left; | this块是被选择的第二个不同的项目;获得水平位置 |
| thisx = thisx.substring(0,thisx.length-2); | 移除px |
| thisxn = Number(thisx) + 115; | 转换为数字并添加一些空格 |
| elementinmotion.style.left = String(thisxn)+"px"; | 重置elementinmotion以移动元素 |
| elementinmotion.style.top = this.style.top; | 设置垂直坐标以保存垂直标高 |
| makingmove = false; | 重置〔??〕 |
| if (this.id.substring(1)``==elementinmotion.id.substring(1)) { | 现在,通过比较"c""p"之后的id字符串部分,检查这是否是一个好的匹配 |
| elementinmotion.style.backgroundColor = "gold"; | 设置为gold |
| this.style.backgroundColor = "gold"; | 设置为gold |
| document.f.out.value = "RIGHT"; | 输出信息 |
| sc = 1+Number(document.f.score.value); | 增量分数 |
| document.f.score.value = String(sc); | 显示分数 |
| if (sc==nq) { | 检查这是否意味着nq已经匹配 |
| v = document.getElementById("vid"); | 如果有,获取视频 |
| v.style.top = String(row1+4*rowsize+20)+"px"; | 位于项目正下方;如果高度很小,视频将离屏,需要垂直滚动才能看到;它会被听到。 |
| v.style.visibility = "visible"; | 使视频可见 |
| v.style.zIndex="10000"; | 放在项目的顶部(这是任何改变把视频放在顶部;现在它在街区之下) |
| v.play(); | 播放视频 |
| } | 如果sc==nq关闭 |
| } | 如果 id 匹配则关闭 |
| else { | 否则(糟糕的举动) |
| document.f.out.value = "WRONG"; | 显示错误 |
| elementinmotion.style.backgroundColor = "white"; | 设置为白色 |
| } | 关闭 else |
| } | 如果是第二项,则关闭 |
| else { | Else(选择的第一个项目) |
| makingmove = true; | 设置makingmove标志 |
| elementinmotion = this; | 保存此参考供以后使用 |
| elementinmotion.style.backgroundColor="tan"; | 设置颜色 |
| } | 关闭 else |
| } | 关闭pickelement功能 |
| </script> | 关闭script部分 |
| </head> | 关闭head部分 |
| <body onLoad="init();"> | body标签;加载时通知调用init |
| <main tabIndex="1"> | tabIndex设定主要元素 |
| <h1>G20 Countries and Capitals </h1> | 标题 |
| <br/> | 强制换行 |
| This is a quiz for matching country and capital.``There are 4 countries and 4 capitals. | 说明 |
| Click (or tab and then press enter)``to pick a country or capital and``then click (or tab and then press enter)on corresponding capital or country.``There will be a video (with sound) if you match all 4\. You can tab through``all the elements repeated times. | 说明,续 |
| <p> | 段落标签 |
| Reload for new game. </p> | 更多说明 |
| </main> | 关闭main元素 |
| <p> | 段落标签 |
| <form name="f" > | 一个form元素 |
| Action: <input name="out" type="text"``value="RIGHT OR WRONG"/><br/> | 将显示玩家移动的结果 |
| Score: <input name="score" type="text"``value="0"/> | 分数,从 0 开始 |
| </form> | 关闭表单 |
| </p> | 结束段落 |
| <video id="vid" controls="controls"``preload="auto" width="50%"``alt="Fireworks video"> | 视频标签;注释alt |
| <source src="sfire3.webmvp8.webm" type='video/webm; codec="vp8, vorbis"'> | 保存不同格式的视频,从webm开始;注意:我保留了这些文件的长名称 |
| <source src="sfire3.mp4"> | MP4 格式 |
| <source src="sfire3.theora.ogv"``type='video/ogg; codecs="theora, vorbis"'> | OGV 格式 |
| Your browser does not accept the video tag. | 旧浏览器的标准消息 |
| </video> | 关闭video |
| </body> | 关闭body |
| </html> | 关闭html |

测试和上传国家/首都测验应用程序

您可以决定在您的列表中包括哪些国家,或者,如果您想将测验更改为不同的内容,您需要制定和创建定义内容的字符串对。您也可以选择不同的视频作为成功完成测验的奖励。如果你想让这个测验为视障人士和其他人服务,你会想要选择一个包含响亮、欢快音乐的视频。

测试并上传拼图转向视频应用程序

在第八章中,你学习了如何创建一个简单的拼图游戏,当拼图游戏完成时,它会变成一个视频剪辑。我在 jigsaw 程序中添加了本章中讨论的对触摸响应的增强功能,并将其包含在本章的源代码中。如果您在台式机或笔记本电脑上检查这段代码,您不会发现任何不同。但是,如果您将代码与基本图像和视频文件一起上传到您自己的网站,您应该会看到它在移动设备上工作。

正如我在第八章中所指出的,要注意移动设备上的苹果操作系统可能要求用户点击所有视频的播放按钮。这被苹果认为是一个特性,而不是一个 bug。要求点击确实给了设备所有者阻止下载视频的机会,这需要时间和电池电量,并可能产生费用。我已经在第二章和第三章讨论了 Chrome 的自动播放政策。对于拼图到视频的项目,我更喜欢从拼图到视频的转换是自动的,这就是在台式机或笔记本电脑上。你需要意识到这个问题,因为将来浏览器可能会有变化。

您可以使用自己的视频制作自己的游戏,提取第一帧作为图像文件作为基础。

摘要

在这一章中,你探讨了对于扩大你的作品的受众至关重要的问题。

响应式设计的关注点包括适应不同屏幕的大小和形状,以及提供触摸和鼠标操作。描述了示例中没有使用的某些 HTML 和 CSS 特性。

对可访问性的关注包括当鼠标或触摸不可行时提供对键盘操作的支持。这包括设置制表符索引,即使在动态创建元素时也可以这样做。如果视频中的音频存在且合适,播放视频作为成功完成测验的奖励对视障人士有效。

这里描述的应用程序和增强的 jigsaw 转换成视频是建立在您在本书中学到的一切之上的,包括动态构建元素,使用数组和图像,以及设置事件和事件处理。我希望你喜欢这种体验,并开始构建自己的项目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值