HTML5 解决方案:面向 HTML5 开发者的基本技巧(三)
原文:HTML5 Solutions Essential Techniques for HTML5 Developers
七、HTML5 画布
我们在前一章中看到了如何使用 HTML5 画布绘制 API。我们现在将更深入地操作 canvas,看看它如何用于动态渲染图形、游戏图形或其他视觉图像。正如您之前所了解的,canvas 是一个矩形区域,您可以将它添加到 HTML5 页面中,并且可以使用它通过几种可用的方法来呈现和操作图形。在这一章中,你将看到你能用 canvas 和它的其他 API 做什么。
苹果最初创建 canvas 元素是为了使用 Mac OSX WebKit 在 Safari 浏览器中制作 dashboard widgets。后来它被其他浏览器采用,并最终成为 WHATWG (Web 超文本应用技术工作组,[
whatwg.org/html](http://whatwg.org/html)
)接受的标准。
在第六章中,我们介绍了如何使用 canvas 元素绘制形状和文本。然而,你可以做的不仅仅是用画布绘制静态位图。canvas 元素为 HTML 提供了广泛的可能性——您可以渲染图形并使其具有交互性、处理图像、制作动画、构建游戏等等。
在 canvas 元素出现之前,每当您想在 HTML4 中这样做时,您都必须依赖第三方专有插件,如 Flash、Silverlight 等。现在,您可以通过 canvas 元素在 HTML5 文档中使用所有这些功能。
通过 canvas 元素可以实现很多事情,我们不可能在一章中涵盖所有内容。尽管如此,在这一章中,你将看到如何使用 canvas 元素在浏览器中轻松地添加丰富的图形。我们涵盖了 canvas 元素的以下方面:
- 画布 API 概述
- 如何检测画布元素和画布文本支持
- 基于屏幕的标准坐标系和变换
- 如何在画布中操作像素
- 如何对图形元素应用阴影和模糊
- 如何使用 canvas 元素构建动画
解决方案 7-1:了解 canvas APIs
如前一章所见,canvas 元素是一个矩形区域,允许您在 web 浏览器中动态呈现图形和位图图像。因此,它提供了广泛的图形可能性,允许您控制其内容的所有像素,并以多种方式操纵它们。当我们谈论渲染图形时,我们首先必须指出画布本身没有绘制或渲染功能,并且您将在其中渲染的一切都将通过使用 JavaScript 代码来实现。
涉及到什么
你可以在你的 HTML5 页面中声明一个<canvas>
元素,如第六章中的所示,如下所示:
<canvas id="aCanvas" width="640", height="480"> Your browser doesn't support the canvas element! </canvas>
这将声明一个宽 640 像素、高 480 像素、ID 为“aCanvas”的<canvas>
元素。您的画布的任何子元素都将被您的浏览器视为后备内容,只有当您的浏览器不兼容画布时,它才会显示。HTML5 规范建议您的回退内容尽可能匹配您的画布内容。
要在画布上做任何事情,首先需要访问<canvas>
元素,并从那里访问它的绘图上下文对象,在那里进行实际的绘图和像素处理。如前所述,您将使用 JavaScript 代码完成所有这些工作。由于 canvas 元素可以通过 DOM 获得,就像任何其他元素一样,您将通过它的“id”属性来访问 canvas:
var canvas=document.GetElementbyId('aCanvas');
您还可以通过编程方式添加画布,方法是使用 JavaScript 直接将画布插入 DOM,然后添加到页面:
`
`这将产生完全相同的结果:一个 640 像素乘 480 像素的画布,ID 为“a canvas”。
提示:设置画布的宽度和高度会自动重置整个画布元素。如果在运行一个应用的过程中,你需要清空你的画布,只需要重新设置它的高度和宽度就可以很容易做到。
在撰写本文时,HTML5 规范中只支持 2D 上下文,所以我们将只讨论这个上下文。然而,值得注意的是,通过 WebGL 支持,3D 上下文的使用已经取得了广泛的进展,这显示了在 canvas 元素中处理 3D 图形的巨大前景。我们期待它很快也能上市,开启新的和令人惊叹的 HTML5 可能性。
通过使用从 DOM 中检索的 canvas 对象的getContext()
方法,获取对 2D 上下文的引用,如下所示:
var context=canvas.getContext('2d');
然后,通过其上下文,您可以使用其绘图 API 中可用的工具集,例如:
- 变换(包括缩放和旋转)
- 阴影
- 复杂的形状,包括贝塞尔曲线和圆弧(见第六章
关于可用的 2D 上下文 API 方法的完整列表和概述,请参考这个有用的 canvas 2D 上下文备忘单:[
www.nihilogic.dk/labs/canvas_sheet/HTML5_Canvas_Cheat_Sheet.pdf](http://www.nihilogic.dk/labs/canvas_sheet/HTML5_Canvas_Cheat_Sheet.pdf)
需要理解的重要一点是,画布使用即时模式渲染器。这意味着,当您调用一个方法在画布中进行绘制时,浏览器将在继续下一行代码之前立即呈现该更改。因此,无论何时您想要在画布上更改任何内容,您都必须重新发出在画布上使用的所有绘图命令,即使更改只影响一个元素。也就是说,canvas 元素提供了两种方法,让您可以随时存储和重置画布的状态。(画布绘制状态是已应用的所有样式、剪辑和变换值的快照。)
- save(): 这将保存当前的样式、剪辑和变换值。
-
restore(): 这将把样式和转换值重置为您上次在上下文中调用
save()
方法时的值。如果你还没有保存任何东西,那么这个方法就没有任何作用。
例如,假设您在画布上下文上画了一个圆,其strokeStyle
设置为灰色,然后您在上下文上调用save()
方法,并用白色strokeStyle
画另一个圆。如果您随后调用restore()
,两个圆圈都将有一个灰色的笔划,并且您上次保存画布状态时定义的所有样式属性现在都将被恢复。请记住,我们处于即时呈现模式,浏览器会在浏览代码的过程中呈现更改。在处理 canvas 转换方法时,我们将更清楚地看到如何使用这些信息。
如何建造它
-
Create a canvas in a regular HTML5 page:
`Canvas `
专家提示
每个画布只能有一个上下文。这意味着一旦你在上面画了东西,如果你要做任何改变,即使是对许多元素中的一个元素,整个场景都要被重画。有时,使用几个画布作为层,一个在另一个之上,以便只重画特定事件所需的内容,这可能会很有趣。
您可以在一个小示例中看到这一概念,在该示例中,您绘制了两个基本的重叠图形,并更改了每个图形的值,而不必通过使用单独的层来重绘另一个图形。(这里我们不会费心去画刻度值,只会用一个普通的图形。)
-
在常规
<div>
元素中创建两个高度和宽度相同的画布。 -
The two canvases are here, but right now they are just next to another like any regular HTML element. To make them overlap each other, you’ll use CSS to position them and set their z-index. You can either put this directly in your HTML5 page inside a
<style></style>
tag, or in an external CSS file. In this example, let’s just have everything on the same page for code readability.
#graphs{ position:relative; width:500px; height:220px; } #first_layer{ z-index: 1;
` position:absolute;
left:20px;
top:0px;
}#second_layer{
z-index: 2;
position:absolute;
left:20px;
top:0px;
}`要分别修改两个画布,请在画布下方添加一些按钮来调出不同的值:
<button id="graph1" onClick="drawGraph(context,data,'#ccc')">graph1 - 2001</button>
<button id="graph2" onClick="drawGraph(context,data2,'#ccc')">graph1 - 2005</button> <button id="graph3" onClick="drawGraph(context2,data3,'red')">graph2 - 2001</button> <button id="graph4 onClick="drawGraph(context2,data4,'red')">graph2 - 2005</button>
现在添加您的 JavaScript 代码来定义您的
drawGraph()
函数。首先检索两个画布及其各自的上下文区域,然后定义您将需要的变量:`
现在,您可以编写呈现简单图形所需的函数。下面的函数将在指定的上下文中创建一个矩形,其大小和宽度也在参数中定义。
`function drawBars(ctx,sx,sy,w,h,color){
ctx.beginPath();
ctx.rect(sx,sy, w, h);
ctx.closePath();
ctx.fillStyle=color;
ctx.strokeStyle=“#333”;
ctx.lineWidth=.2;
ctx.globalAlpha=.7;
ctx.fill();
ctx.stroke();
}`
现在让我们使用这个函数,它将在上下文中绘制出您想要的图形。您可以看到它有三个参数:您想要呈现这个图形的上下文、您想要使用的值和您想要用于图形的颜色。因为这是您需要的最后一个函数,所以现在可以关闭脚本了:
`function drawGraph(ctx,values,style){
ctx.clearRect(0,0,500,500);
for(var i=0;i<values.length;i++){
drawBars(ctx,i*(barswidth+5),200-values[i],barswidth,values[i],style);
}
}
`
首先,该函数将使用clearRect()
方法清除上下文,在空白画布上开始,然后它将使用所需的值绘制图形。因此,每次你点击一个按钮,它只会用你想要的值重新绘制选中的图形,如图 7-2 所示。当然,您可以在一个层中实现相同的视觉效果,但这意味着要重新绘制每个事件的所有内容,甚至是具有相同值的其他图形。根据您的需要,您可以使用任何一种方法。使用多个画布作为层可以增强动画和游戏的性能,这有助于提高您的应用在移动浏览器上的性能。
**图 7-2。**使用多个画布作为图层的图形
解决方案 7-2:检测画布和画布文本支持
每当您想要交付 web 内容时,确保兼容性是首要任务。尽管 HTML5 获得了主流浏览器的进一步支持,但所有浏览器(尤其是旧版本)都不支持 canvas 元素。另外,有些支持 canvas,但仍然不支持 canvas 文本 API。
处理浏览器不支持 canvas 的情况的基本方法是在你的<canvas></canvas>
标签中添加后备内容,正如你在第六章中看到的,尽可能匹配你的 canvas 内容。然而,这可能并不总是准确的,并且您可能会受到限制,因为回退内容不能提供您的画布所做的一切,例如交互式内容。因此,您可能希望在加载页面时动态检查浏览器支持,并提供比依赖 canvas 标记的后备内容更符合您需求的其他内容。您甚至可以选择相应地修改整个页面,并创建一个合适的替代页面来确保更好的用户体验。例如,您可以用 JavaScript 编写一个替代内容,它比简单的图像或纯文本更匹配您的画布内容。
涉及到什么
检查浏览器是否支持 canvas 元素的一种简单方法是在检索 canvas 对象及其上下文时进行。如果浏览器不支持 canvas 元素,那么当通过 DOM 检索 canvas 对象时,您将只有一个基本的对象,没有任何特定于 canvas 的内容。所以如果你试图调用getContext()
方法,它不会返回任何函数。您可以编写一小段代码来实现这一点:
` var canvas=document.getElementById(‘canvas’);
if(!!canvas.getContext){
}`
因此,如果浏览器不支持 canvas 元素,您可以添加任何替代元素。否则,就像往常一样检索画布上下文。
else{ var context=canvas.getContext('2d'); }
如果你的浏览器支持 canvas 元素,并不一定意味着它支持 canvas text API。因此,如果您想在画布上绘制任何文本,也需要检查这一点。遵循相同的过程:调用特定于 canvas text API 的方法,并查看它返回的内容。如果它不返回任何函数,那么它就不被支持。
if(!!context.fillText){ //the Canvas text API is supported and you can proceed with your code } else{ // you can add an alternative text, or add any other element to replace it through the DOM. }
如何建造它
-
创建两个简单的函数来检查 canvas 和 canvas 文本支持。首先,编写一个
canvasSupport
函数。原理是创建一个画布,通过 DOM 访问它的上下文对象。如前所述,如果 canvas 元素不受支持,canvas 对象将无法调用getContext()
方法,它将返回一个未定义的值。然后你可以使用 JavaScript 双负技巧将返回值强制转换为布尔值(真或假):如果为真,则意味着定义了上下文,并且支持画布;否则就不是了。
function canvasSupport(){ return !!document.createElement("canvas").getContext; }
-
通过从画布上下文调用
fillText()
方法,创建一个函数来检查画布文本支持。如果你的浏览器不支持它,它将返回一个未定义的值,而不是一个函数。同样,您将返回值强制转换为布尔值。 -
在加载页面事件上调用您的函数:`window.οnlοad=init;
function init(){
if(canvasSupport){
// proceed with the use of the canvas APIs
If(canvasTextSupport){
//proceed with your code using the Canvas text API
}
}else{
//add any alternative you wish in complement of the fallback content if you
wish.
}
}` -
现在看一下前面的例子,在这个例子中,您使用了几个画布作为层,以交互的方式显示图形。您仍然希望没有 HTML5 兼容浏览器的用户能够看到您的图表,因此您只需编写一个小的替代程序,让他们能够看到您不同图表的图片。在这里,他们不会看到 HTML5 中图形的超级拼版,但他们仍然会有经典的按钮和图像。这段代码只会在不兼容的浏览器上加载,如下:
`solution 7-2
专家提示
如果不想自己写函数检查浏览器支持,可以用 Modernizr。它是一个开源的 JavaScript 库,检测对许多 HTML5 特性(如地理定位、视频、本地存储等)以及 CSS3 的支持。由于页面上的元素很有可能比画布上的元素多,所以您可以使用它来处理您可能需要的各种支持检测。
至于画布和画布文字,真的很简单。首先包括库(在撰写本文时,我们使用的是最新版本):
`
if (Modernizr.canvas) {
var c=document.createElement(‘canvas’);
c.setAttribute(‘width’, ‘500’);
c.setAttribute(‘height’,‘500’);
c.setAttribute(‘id’, ‘canvas’); var context=c.getContext(‘2d’);
}
else{
//no canvas support
var alt_canvas=document.createElement(“div”);
alt_canvas.setAttribute(‘width’, ‘500’);
alt_canvas.setAttribute(‘height’,‘500’);
alt_canvas.setAttribute(‘id’, ‘alt_canvas’);
document.body.appendChild(alt_canvas);
}`
…对于画布文本支持:
if (Modernizr.canvastext) { // draw your text context.fillStyle="#000"; context.font="bold 15px Arial"; context.fillText("Some text drawn in your canvas.",20,40); } else { //no canvas text support var alt_canvas_text=document.createElement("div"); alt_canvas_text.setAttribute('width', '500'); alt_canvas_text.setAttribute('height','40');
alt_canvas_text.setAttribute('id', 'alt_canvas_text'); document.body.appendChild(alt_canvas); document.getElementById('alt_canvas_text').innerHTML="Some alternative text." }
您可以从以下地址下载 Modernizr 的最新版本以及用户文档:[
www.modernizr.com/](http://www.modernizr.com/)
。
解决方案 7-3:理解标准的基于屏幕的坐标系和画布变换
HTML5 canvas 还提供了允许你进行变换操作的方法,比如缩放、旋转、平移,甚至变换矩阵操作,它们都是关于 canvas 坐标系的。事实上,当您在画布上下文上执行变换时,您正在根据您的目标(重新缩放、移动形状等)变换整个上下文的坐标系,而不是在其上绘制的内容。在这个解决方案中,您将看到如何操纵坐标系来实现这一点。
涉及到什么
您可以在画布上下文上执行一些转换操作。它们中的每一个都需要修改上下文的坐标。流程是先修改坐标系,然后照常在上面画。简而言之,你修改的不是上下文的内容,而是上下文本身。
-
Scaling canvas objects: You achieve this by using the
scale()
method, which scales the canvas context itself; that is, the x and y-axis coordinates. This method takes two parameters: the scale factor in the horizontal direction, and the scale factor in the vertical direction. Those scale values are based on the unit size of 1:scale(1,1)
will keep the same scale as the original, values larger than 1 will increase it, and values smaller than 1 will decrease it. The values must always be positive.
context.scale(scaleX,scaleY);
注意:要获得比例缩放值,必须给出相等的 scaleX 和 scaleY 值。
-
*平移画布对象:*如果你想移动画布上绘制的形状或任何东西,首先使用
translate()
方法。它会将画布及其原点移动到网格中的另一个点,这个点是通过两个参数定义的。第一个参数是画布在水平轴上移动的量(以像素为单位)。第二个参数是它在纵轴上向上或向下移动的量。这个点就是新的原点(0,0),无论你画什么都以它为原点。 -
旋转画布对象:
rotate()
方法将上下文旋转到其参数给定的角度。角度值以弧度为单位。
context.rotate(angle);
-
使用转换矩阵:
transform()
方法将改变转换矩阵以应用其参数给定的矩阵,如下所示:
context.transform(a,b,c,d,e,f);
这些参数对应于图 7-3 中所示的矩阵变换值。
**图 7-3。**矩阵转换值
注意:只有对应于 a、b、c、d、e 和 f 的值可以修改。当一个矩阵没有引起变换时,你就有了所谓的“单位矩阵”
也可以用setTransform(a,b,c,d,e,f)
的方法。它将当前变换重置为单位矩阵(参见图 7-3 ,然后用其参数调用transform()
方法。
您还可以通过变换矩阵执行旋转、缩放和平移。好处是你可以一次全部执行。另一方面,如果您不熟悉 2D 矩阵系统,那么依次使用其他特定方法可能会更安全。
在应用任何变换之后,你可能想要回到原始坐标系进行下一次绘图或操作(除非你只是真的喜欢思维练习!).这就是画布的save()
和restore()
方法最有用的地方。(参见解决方案 7-1,了解save()
和restore()
方法)。在使用任何变换方法之前,您可以通过save()
方法保存画布状态来保留原始坐标系。然后,完成变换后,您只需从变换前阶段恢复画布状态,然后使用原始的常规坐标系执行任何新操作。我们建议您对想要执行的每个转换都使用这个过程。否则,它会很快变得复杂,因为每次转换都要重新定义坐标。
如何建造它
要了解如何使用坐标系统的变换,让我们使用所有变换方法创建一些绘图。此示例旨在展示这是如何工作的,而不是创建一个可用的绘图。
-
首先创建一个基本的 HTML5 页面,用一个画布和一个 onload 事件函数来检索画布及其上下文。
`solution 7-3/title>
这里你正在画正方形,在每一个正方形上你应用一个矩阵变换来旋转和平移它们,创建下图(见图 7-4 )。在这个循环结束时,画布上下文将被转换 70 次。因为您不再绘制任何东西,所以没有必要保存和恢复画布上下文。
**图 7-4。**具有形状和颜色变换的绘图
此示例的完整代码如下所示:
`
function draw(){
context.save();
context.translate(250,250);
for (var i=0;i<8;i++){
context.rotate(Math.PI2/8);
context.fillStyle = ‘rgb(’+(30i)+‘,’+(10i)+‘,’+(200-3i)+‘)’
context.beginPath();
context.arc(0,12.5,4,0,Math.PI*2,true);
context.fill();
}
context.restore();
context.translate(258,250);
for (var j=0;j<70;j++){
var sin=Math.sin(Math.PI/6);
var cos=Math.cos(Math.PI/6);
c=Math.floor(255/120j);
context.fillStyle = “rgb(”+c+“,”+c+“,”+c+“)”;
context.transform(cos,sin,-sin,cos,j2,j/2);
context.globalAlpha=.8;
context.lineWidth=.2;
context.strokeStyle=“#333”;
context.fillRect(20,20,25,25);
context.strokeRect(20,20,25,25);
}
context.restore();
}
专家提示
如果您想要从图像的中心旋转图像,您首先需要转换上下文以移动原点。然后应用旋转并相应地绘制图像,如下所示:
`var img=new Image();
img.src=‘yourImagePath.jpg’;
img.οnlοad=function(){
context.translate(img.width/2, img.height/2);
context.rotate(90*Math.PI/180); // here we apply a 90 degres rotation
context.drawImage(img,-img.width/2,-img.height/2,img.width, img.height);
}`
解决方案 7-4:像素操作
正如您在前面了解到的,画布 API 提供了让您操纵画布内任何像素的方法。您看到了您可以绘制形状并对它们应用变换,但是也有一些方法允许您在画布上一个像素一个像素地绘制或应用变化。
涉及到什么
您可以通过使用ImageData
对象来实现对画布上像素的操作。它表示您选择的画布上下文区域的当前状态的像素数据。它有以下属性:以像素为单位的宽度和高度,以及“data”——一个包含图像数据的每个像素的颜色分量的canvasPixelArray
元素;即红色、绿色、蓝色和 alpha(每个值从 0 到 255)。像素从左到右和从上到下排序。通过这些图像对象数据,您将能够在画布上执行逐像素操作。
对于像素操作,canvas API 提供了三种方法来执行以下操作:
-
Creating image data: Call the
createImageData()
method, which takes 2 parameters: width, and height of your image data in pixels. It will create a set of transparent black pixels that you can then manipulate by assigning values to the datacanvasPixelArray
.如果我们要做一个像素阵列的表示,它看起来会像图 7-5 所示的图像。
**图 7-5。**像素阵列
var canvas=document.getElementById('canvas'); var context=canvas.getContext('2d'); var imagedata=context.createImageData(canvas.width,canvas.height);
这将创建一个包含整个画布上下文的像素数据的
imagedata
对象。注意:您可以在创建图像数据时指定坐标,以选择画布上下文的特定区域。默认情况下,它们被设置为 0,0,因此如果您只给定画布的宽度和高度作为参数,您的图像数据将覆盖整个画布。
var canvasPixelArray=imagedata.data;
现在你可以通过
canvasPixelArray
访问每一个像素,你可以开始随心所欲地操作它。因为每个像素有四个颜色值(红色、绿色、蓝色和 alpha),所以滚动数组并为每个像素设置颜色值。以下代码将为 imagedata 对象的每个像素分配以下 RGBA 值:255、200、125 和 150。
` for (var i=0;i< canvasPixelArray.length;i+=4) {
canvasPixelArray [i]=255 // red channel
canvasPixelArray [i+1]=200; // green channel
canvasPixelArray [i+2]=125; // blue channel
canvasPixelArray [i+3]=150; // alpha channel}`
-
Retrieving a pixel array : You can also retrieve the pixel array from an existing canvas through the
getImageData()
method.它有四个参数——要检索的区域的 x 和 y 位置以及宽度和高度(以像素为单位)。
context.getImageData(x,y,width,height);
使用这种方法,您可以通过
drawImage()
方法访问您在画布上绘制的形状的像素数组,或者您在画布上下文中添加的图像的像素数组。然后你可以做任何种类的像素操作来创建像滤镜这样的东西。 -
*在上下文上绘制图像数据:*一旦你创建了一个
imageData
对象并对其进行了任何类型的像素操作,你就可以通过调用putImageData()
方法在上下文上绘制你的imagedata
。它有三个参数:imagedata 对象及其 x 和 y 坐标。
Context.putImageData(imagedata,0,0);
如何建造它
为了查看像素操作的实际示例,我们将拍摄一张照片,并对其应用基本的颜色过滤器,然后在画布上的原始照片旁边显示操作后的版本,如图 7-6 所示。(这个例子是为了可读性,并没有特别优化。)
图 7-6 。通过像素操作应用了不同颜色滤镜的照片
为此,请遵循以下步骤:
-
用画布创建一个基本的 HTML5 页面。
`solution 7-4/title> ` -
然后,在您的
<head></head>
标签中,检查画布支持,并在加载页面时加载一个图像。 -
当图像加载完成后,访问画布上下文,将照片添加到画布的左上角(原点)。然后通过调用
getImageData()
方法并传递画布上的照片坐标及其宽度和高度来获取照片的 imagedata 对象。通过这样做,imagedata 对象将只获得画布这个区域的像素数据。
`function imageLoaded(evt){context=document.getElementById(‘canvas’).getContext(‘2d’);
originalPic=evt.target;
context.drawImage(originalPic,0,0);
var imgd=context.getImageData(0,0, originalPic.width,originalPic.height);applyBluefilters(imgd);
applyRedfilters(imgd);
applyGreenfilters(imgd);}`
-
Now that you have your photo’s pixel data available, create some custom methods to apply basic color filters to them.
`function applyBluefilters(img){var pixelsArray=img.data;
for(var i=0;i<pixelsArray.length;i+=4){img.data[i]=img.data[i]/2;
img.data[i+1]=img.data[i+1]/2;
img.data[i+2]=img.data[i+2]*2.5;
img.data[i+3]=img.data[i+3];}
context.putImageData(img,170,0);
}`这里首先检索包含像素数据的数组。对于每个像素,首先获取其红色、绿色、蓝色和 alpha 值,并根据需要重新分配新值。为了实现这种蓝色滤镜效果,减少红色和绿色的值,并加入更多的蓝色。完成后,在上下文上绘制新的 imagedata。
-
根据您想要实现的效果,通过调整每个像素的 RGB 值来创建应用红色和绿色滤镜的方法。
??` }context.putImageData(img,0,170);
}function applyGreenfilters(img){
var pixelsArray=img.data;
for(var i=0;i<pixelsArray.length;i+=4){
r=img.data[i]*3;
g=img.data[i+1]/2;
b=img.data[i+2]/2;
a=img.data[i+3];
img.data[i]=r;
img.data[i+2]=b;
img.data[i+3]=a;
r=img.data[i]/2;
g=img.data[i+1]*4;
b=img.data[i+2]/2.5;
a=img.data[i+3];img.data[i]=r;
img.data[i+1]=g;
img.data[i+2]=b;
img.data[i+3]=a;}
context.putImageData(img,170,170);
}
` -
在你的图像上调用你的自定义方法
loadEvent()
。
这里显示了这个例子的完整代码。(您需要将图像“pic.jpg
”放在同一个文件夹中。)
`
function init(){
if(!!document.getElementById(‘canvas’).getContext){
var photo=new Image();
photo.οnlοad=imageLoaded;
photo.src=“photo_01.jpg”;
}
else{
//add anything you want for non-compliant browsers
}
}
function imageLoaded(evt){
context=document.getElementById(‘canvas’).getContext(‘2d’);
originalPic=evt.target;
context.drawImage(originalPic,0,0);
var imgd=context.getImageData(0,0,originalPic.width,originalPic.height);
applyBluefilters(imgd);
applyRedfilters(imgd);
applyGreenfilters(imgd);
}
function applyBluefilters(img){
var pixelsArray=img.data;
for(var i=0;i<pixelsArray.length;i+=4){
r=img.data[i]/2;
g=img.data[i+1]/2;
b=img.data[i+2]*2.5;
a=img.data[i+3];
img.data[i]=r;
img.data[i+1]=g;
img.data[i+2]=b;
img.data[i+3]=a;}
context.putImageData(img,170,0);
}
function applyRedfilters(img){ var pixelsArray=img.data;
for(var i=0;i<pixelsArray.length;i+=4){
r=img.data[i]*3;
g=img.data[i+1]/2;
b=img.data[i+2]/2;
a=img.data[i+3];
img.data[i]=r;
img.data[i+2]=b;
img.data[i+3]=a;
}
context.putImageData(img,0,130);
}
function applyGreenfilters(img){
var pixelsArray=img.data;
for(var i=0;i<pixelsArray.length;i+=4){
r=img.data[i]/2;
g=img.data[i+1]*4;
b=img.data[i+2]/2.5;
a=img.data[i+3];
img.data[i]=r;
img.data[i+1]=g;
img.data[i+2]=b;
img.data[i+3]=a;
}
context.putImageData(img,170,130);
}
专家提示
操纵像素会降低性能。如果你多次使用你的画布上下文的同一个 imagedata,并且你需要恢复它(在一个帧事件,鼠标事件,或者在一个循环中,等等),你可以使用toDataUrl
方法代替putImageData()
(见第六章,解决方案 6-8 关于这个方法的更多细节)。
savedImagedata=new Image() savedImagedata.src=canvas.toDataURL("image/png");
每当您需要恢复它时,请使用以下代码:
context.drawImage(savedImagedata,x,y) ;
解决方案 7-5:应用阴影和模糊
canvas API 还提供了在画布上绘制的任何东西(形状、路径、文本、图像等等)上创建漂亮的模糊阴影效果的方法。
涉及到什么
正如您到目前为止所了解的关于 canvas API 的大部分内容一样,向您绘制的元素添加阴影意味着在绘制时将这些效果应用到 canvas 上下文本身,从而将其应用到您正在绘制的内容。canvas API 提供了与阴影相关的上下文属性,可以让你定义它的颜色、位置和模糊程度。(如果你熟悉用 CSS 应用阴影效果,你会发现自己在这里非常熟悉的领域。):
-
shadowColor: 这个属性设置上下文阴影的颜色。创建上下文时,它是透明的黑色。
context.shadowColor="#333";
-
shadowOffsetX :该属性返回并设置水平阴影偏移量,以像素为单位。
context.shadowOffsetX=5;
-
shadowOffsetY: 该属性返回并设置垂直阴影偏移量,以像素为单位。
context.shadowOffsetY=5;
-
shadowBlur: 这个属性返回并设置模糊等级。其值必须大于 0。
context.shadowBlur=10;
设置上下文阴影属性后,它们将应用于您在画布上绘制的任何内容,直到您重置它们。
如何建造它
要查看如何在上下文中应用模糊阴影,让我们绘制简单的形状,圆角矩形,并在画布中对它们应用不同的阴影。它应该给出如图 7-7 所示的结果:
**图 7-7。**使用阴影
-
用画布创建一个基本的 HTML5 页面。首先在加载时检查画布支持,然后访问画布上下文。
??`solution 7-5/title> Your browser doesn't support HTML5 Canvas ! ` -
Now create a custom method to draw rounded squares using paths.
`function drawRoundRect(x,y,width,height,radius){context.beginPath();
context.moveTo(x +radius, y);
context.lineTo(x+width-radius, y);
context.quadraticCurveTo(x+width, y, x + width,y+radius);
context.lineTo(x+ width, y+height-radius);
context.quadraticCurveTo(x+width,y+height, x+width-radius, y+height);
context.lineTo(x+radius, y+height);
context.quadraticCurveTo(x,y+height, x, y+height-radius);
context.lineTo(x,y+radius);
context.quadraticCurveTo(x, y,x+radius,y);
context.closePath();//defining the gradient fill
gradient=context.createLinearGradient(x+width/2, y,x+width/2, y+height);
gradient.addColorStop(0., “rgb(218, 51, 163)”);
gradient.addColorStop(0.5, “rgb(167, 0, 118)”);
gradient.addColorStop(0.5, “rgb(192, 69, 160)”);
gradient.addColorStop(1.0, “rgb(192, 64, 163)”);
context.fillStyle=gradient;//defining the stroke color
context.strokeStyle=“#B3B3B3”;//rendering the shape on the context
context.fill();}`
该函数接受这些参数:正方形在画布网格上的 x 和 y 位置,它的宽度和高度,以及要应用的圆角半径。它使用二次曲线路径来创建圆角。你也可以用“类似 web 2.0”的渐变填充你的形状。
-
Create a custom method to apply blurred shadows to your context. By doing this, you can re-use it several times.
`function applyShadow(r,g,b,a,posX,posY,blur){context.shadowColor=‘rgba(’+r+‘,’+g+‘,’+b+‘,’+a+‘)’;
context.shadowOffsetX=posX;
context.shadowOffsetY=posY;
context.shadowBlur=blur;}`
这些参数是阴影颜色的红色、绿色、蓝色和 alpha 值,它相对于在上下文中绘制的元素的水平和垂直位置,以及高斯模糊级别。
-
When you apply shadow properties to your context, they will then apply to anything you draw on it. Thus you will make a small custom method to reset those values to default in case you want to draw anything without a shadow.
`function resetShadow(){context.shadowColor=‘rgba(0,0,0,0)’;
context.shadowOffsetX=0;
context.shadowOffsetY=0;
context.shadowBlur=0;}`
默认的上下文阴影颜色是透明的黑色,所以你只需要重置它。
-
现在使用这些方法,通过改变上下文阴影属性,用不同的阴影参数绘制形状。然后,画一些没有任何阴影的文字。
??` applyShadow(102,102,102,.8,2,2,10);
drawRoundRect(140,140,100,100,10);resetShadow();
context.fillStyle=‘#333’;
context.font=‘Bold 22px Arial’;
context.fillText(‘Using shadows’,60, 275);}
}`
因为这是一个简单的例子,当调用init()
函数时只需画出你的形状。
首先定义上下文阴影属性,使第一个形状为深灰色,alpha 值为 0.8,位置为–2,模糊度为 5。然后画出你的第一个圆角正方形。因为您希望下一个方块有不同的阴影,所以再次调用自定义的applyShadow()
方法,为上下文阴影设置一个新值,并绘制第二个方块。对你的最后一个方块使用相同的程序。
因为您希望您的文本没有任何阴影,所以调用resetShadow()
方法,它会将所有值重置为 0。然后,您可以使用阴影绘制文本。
此示例的完整代码如下所示:
`
function drawRoundRect(x,y,width,height,radius){
context.beginPath();
context.moveTo(x +radius, y);
context.lineTo(x + width-radius, y);
context.quadraticCurveTo(x + width, y, x + width, y+radius);
context.lineTo(x + width, y + height - radius);
context.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
context.lineTo(x + radius, y + height);
context.quadraticCurveTo(x, y + height, x, y + height - radius);
context.lineTo(x, y + radius);
context.quadraticCurveTo(x, y, x + radius, y);
context.closePath();
//defining the gradient fill
gradient = context.createLinearGradient(x+width/2, y,x+width/2, y+height);
gradient.addColorStop(0., “rgb(218, 51, 163)”);
gradient.addColorStop(0.5, “rgb(167, 0, 118)”);
gradient.addColorStop(0.5, “rgb(192, 69, 160)”);
gradient.addColorStop(1.0, “rgb(192, 64, 163)”);
context.fillStyle=gradient;
//defining the stroke color
context.strokeStyle=“#B3B3B3”;
//rendering the shape on the context
context.stroke();
context.fill();
}
function applyShadow(r,g,b,a,posX,posY,blur){
context.shadowColor=‘rgba(’+r+‘,’+g+‘,’+b+‘,’+a+‘)’;
context.shadowOffsetX=posX;
context.shadowOffsetY=posY;
context.shadowBlur=blur;
}
function resetShadow(){
context.shadowColor=‘rgba(0,0,0,0)’;
context.shadowOffsetX=0;
context.shadowOffsetY=0;
context.shadowBlur=0;
}
专家提示
画阴影真的很消耗处理能力。因此,我们建议如果你想达到一个平滑的效果,不要在动画中大量使用它。
解决方案 7-6:制作画布动画
canvas 的一个真正令人兴奋的特性是能够在 canvas 内部创建动画,使您的绘图移动。最大的优点是它让你可以直接在你的 HTML 页面中创建动画,而不需要依赖第三方插件。这是全新的吗?不完全是。在 HTML4 中,使用 CSS 和/或 SVG 结合 JavaScript 创建动画已经成为可能。你可以在网上找到一些惊人的例子。然而,有了画布的绘图 API,实现这一点变得容易得多,并且它有一些非常令人兴奋的可能性。
canvas 元素中的动画暗示了什么?如果你喜欢动画,你很可能听说过翻页书。如果没有,它们是一叠代表动画不同状态的图画(例如,一个人走路),你拿在手上,当你快速翻阅页面时,翻页书通过创造连续动作的幻觉使其内容移动。这就是你如何用 canvas 元素创建动画的方法;也就是说,通过在一段时间内用不同的内容(形状、图像或任何可以在画布上绘制的内容)重复重绘画布。
涉及到什么
让我们来看看在画布上制作动画的步骤:
-
设置一个循环,以规定的时间间隔重复。
-
在每个间隔:
- 在画布上用新的位置、形状等绘制出您需要的任何动画。
- 在画布上绘制图形。
- 清理画布。(显然,如果你不清理你的画布,每隔一段时间,每幅新的画都会叠加在另一幅上。)
- 重复循环。
-
如果你想让你的动画停止,只需清除间隔。(你也可以有一个无限循环。)
您可能已经猜到,处理画布的计时和重绘意味着使用 JavaScript。原理相当简单:创建一个循环间隔,并在指定的时间间隔重新绘制画布。
要执行上述过程,您可以使用 JavaScript 定时事件,具体来说,就是以下方法:
-
setInterval(callback,time)方法:
setInterval
每隔一定时间调用一个函数,以毫秒为单位定义,作为第二个参数。这可用于定期更新画布元素上所绘制内容的位置、颜色和/或样式。 -
clear interval(回调,时间)方法:
clearInterval()
清除间隔。
这里有一个例子:
`var context;
var canvas;
var posX=0;
var posY=0;
window.οnlοad=init;
function init(){
canvas=document.getElementById(‘canvas’)
context=canvas.getContext(‘2d’);
setInterval(drawShape,20); // calls the drawShape() function every 20 milliseconds.
}
function drawShape(){
if(posX<=canvas.width-30){
clearContext();
ctx.fillRect(posX++,posY,30,30);
}
else{
clearInterval(drawShape,20);
}
}
function clearContext(){
context.clearRect(0,0,canvas.width,canvas.height);
}`
在这个小例子中,我们每隔 20 毫秒用一个新的 x 位置重画一个正方形,方法是在每个新的时间间隔增加 x 位置并清除上下文,这样就给人一种它向右移动的错觉。因为我们希望正方形在到达画布右侧时停止移动,所以我们在每个循环间隔检查它的位置,当它到达边缘时,我们清除间隔。
-
*setTimeout(回调,时间)和 clearTimeout(回调,时间)方法:*这些方法用于在给定的时间后调用一个函数。下面的代码将具有与前面的例子相同的视觉效果,但是它使用了不同的方法:
`function init(){
canvas=document.getElementById(‘canvas’)
context=canvas.getContext(‘2d’);
drawShape();}
function drawShape(){
if(posX<=canvas.width-30){
clearContext();
context.fillRect(posX++,posY,30,30);
setTimeout(drawShape,20); // will call the drawShape function in 20
millisecond
}function clearContext(){
context.clearRect(0,0,canvas.width,canvas.height);
}` -
SetTimeOut() method: This can be used if you want to set several timings in your animation, as you can check how much time has passed once the function is called. This lets you to plan your animation based on time rather than on positions if needed. To clear the delay set by
setTimeOut()
, use theclearTimeOut()
method.注意:在所有方法中,时间都是以毫秒为单位定义的(1000 毫秒=1 秒)。
更新画布上下文渲染是在 HTML5 中构建动画所需要做的。之后,更多的是关于你如何用你的代码和我们提到的不同的 API 图形工具来管理它。
如何建造它
要构建一个球在画布上移动并检测墙壁碰撞的简单动画,请遵循以下步骤。
-
用画布创建一个基本的 HTML5 页面。
`solution 7-6 Your browser doesn't support the HTML5 Canvas element ! ` -
检查画布支持,并在页面加载时通过 DOM 访问画布及其上下文。您还可以定义代码动画所需的变量。
??`window.οnlοad=init;function init(){
if(!!document.getElementById(‘canvas’).getContext){
canvas=document.getElementById(‘canvas’);
context=canvas.getContext(‘2d’);}
else{
//add anything you want for non-compliant browsers
}
}` -
写一个函数来画你的球。当你想让它在画布上移动时,将其坐标设置为参数:
` function drawBall(x,y){context.beginPath();
context.arc(x,y,20, 0, Math.PI*2, true);
context.fillStyle=“red”;
context.fill();}`
-
Write a function to clear the context. As the animation in the canvas is redrawing the context with each new step, you need to reset your canvas to a blank area each time:
` function clearContext(){context.clearRect(0,0,canvas.width,canvas.height);
}`要清除上下文,在整个画布区域使用
clearRect()
方法。(记住clearRect
清除所有像素的指定区域。) -
Now you are ready to bring in some animation. For this, use the
setInterval
method to call ananimate()
function containing the new position of your ball on each new frame. As you want your animation to start when the page loads, add it in yourinit()
function.
`function init(){if(!!document.getElementById(‘canvas’).getContext){
canvas=document.getElementById(‘canvas’);
context=canvas.getContext(‘2d’);`设定间隔(动画,20);
} }
这将每 20 毫秒调用一次
animate()
函数。 -
现在让我们看看你需要改变的不同变量,以确保你的球在画布上移动。首先,设置
xpos
和ypos
变量,它们将在每个新的间隔定义球坐标。还要定义一个速度变量,一个dirX
变量,用来设置球在 x 轴上的方向,一个dirY
用来设置球在 y 轴上的方向。
var xpos=50; var ypos=50; var speed=3; var dirX=1; var dirY=1;
在开始时,你的球将被放置在(50,50)点上,它的速度将为每帧 3 个像素,并从那里下降。
现在,您已经拥有了编写animate()
函数使您的球移动的所有元素:
`function animate(){
clearContext();
xpos+=speed;
ypos+=speed;
drawBall(xpos,ypos);
}`
你要做的第一件事是通过调用你为此目的编写的小函数来清空你的画布。现在,您可以通过将速度值添加到球的最后位置来设置球的新 x 和 y 位置。使用drawBall()
函数用这些新值重画你的球。现在,在每个间隔,球的 x 和 y 坐标将增加 3 个像素,如果您加载页面,球将从画布的左上角开始,无限期地沿对角线下落。当它到达画布的边缘时,它就会消失。
现在我们希望球从画布边缘弹开。为此,在您的animate()
函数中添加一个简单的碰撞测试。这非常简单,你只需检查你的球的最后位置,看看它是否到达画布区域坐标(减去球的半径)。如果是这样,通过将方向变量乘以-1 来改变方向。
if (xpos>=canvas.width-20 || xpos<=20){ dirX*=-1; } if(ypos>=canvas.height-20 || ypos<20){ dirY*=-1; }
现在,您可以通过将速度乘以方向变量来设置球的方向。您的animate()
函数将完成这些任务:清除上下文、检查碰撞、更改 x 和/或 y 方向(如果需要的话)、在画布区域为您的球设置新坐标,最后使用新坐标在画布上绘制球。
`function animate(){
clearContext();
if (xpos>=canvas.width-20 || xpos<=20){
dirX*=-1;
}
if(ypos>=canvas.height-20 || ypos<20){
dirY*=-1;
}
xpos+=speeddirX;
ypos+=speeddirY;
drawBall(xpos,ypos);
}`
不要忘记关闭您的脚本标签。
`
`此示例的完整代码如下所示:
`
var canvas;
var context;
var xpos=50;//initial x position of the ball
var ypos=50;//initial y position of the ball
var speed=3;//speed of the ball
var dirX=1;//initial x direction of the ball
var dirY=1;//initial y direction of the ball
window.οnlοad=init;
function init(){
if(!!document.getElementById(‘canvas’).getContext){
canvas=document.getElementById(‘canvas’);
context=canvas.getContext(‘2d’);
setInterval(animate,20); // setting the interval
}
else{
//add anything you want for non-compliant browsers
}
}
function drawBall(x,y){
context.beginPath();
context.arc(x,y,20, 0, Math.PI*2, true);
context.fillStyle=“red”;
context.fill();
}
function animate(){
clearContext();
if(xpos>=canvas.width-20||xpos<=20){
dirX*=-1;
}
if(ypos>=canvas.height-20||ypos<20){
dirY*=-1;
}
xpos+=speeddirX;
ypos+=speeddirY;
drawBall(xpos,ypos);
}
function clearContext(){
context.clearRect(0,0,canvas.width,canvas.height);
}
专家提示
画布上的动画是一门非常宽泛的学科,要学的东西很多。然而,如果你想开始用 canvas 制作动画,这里有几个小技巧会对你非常有用。
重绘画布会消耗大量的 CPU 周期,所以在使用移动浏览器时,你必须考虑到这一点。这里有一些你可以做的事情来优化你的动画(或者你的游戏)。
在解决方案 7-1 中,你看到了使用几个画布作为层是可能的。嗯,对动画更有用。例如,如果您的动画只有一个正在变化的绘制元素,那么只重新绘制这个元素会非常有益,这样可以避免执行不必要的任务。这对于游戏也是相关的。
要清除上下文,有一个简单的技巧可以节省另一个绘制操作:如果您重置画布的宽度和高度,它会立即清除它。
在某些情况下,可以使用缓冲技术来避免画布闪烁,例如,在同一屏幕位置创建两个画布,并在绘制完成后使用 visibility 属性显示缓冲区。
总结
正如您所看到的,HTML5 canvas 及其 API 提供了一种新的、强大的方式来创建引人入胜的 web 内容和应用,同时本机增加了用户体验。事实上,你现在可以使用和操作图像和渐变;直接在 HTML 页面中绘制、转换和动画化内容,并随时更新,从简单的应用到游戏,为您提供无限的编码可能性。
在下一章,你将学习如何使用 HTML5 通信 API。
八、HTML5 通信 API
在本书的这一点上,已经很清楚 HTML5 提供了许多新的工具来创建应用,这些应用以一种更本地的方式与服务器交互。
在这一章中,我们将探讨避免浏览器沙箱安全限制的技术和解决方案,并使用一种新技术来创建从不同域进行通信的文档(跨文档消息传递),这涉及到 postMessage API 的使用。你将学习什么是跨源资源共享(CORS)以及如何使用它。很简单,CORS 是一种浏览器技术规范,它定义了 web 服务为来自相同起源策略下的不同域的沙箱脚本提供接口的方式。
接下来,我们探索客户机和服务器之间实时通信的新方法,这些方法允许您找出服务器开始与客户机交互的位置。这是由于服务器发送事件规范,该规范指示 API 打开 HTTP 连接以接收来自服务器的推送通知。
最后,我们将讨论 XMLHttpRequest Level 2 中的最新特性。
注意:XMLHttpRequest (XHR)是 web 浏览器脚本语言(如 JavaScript)中可用的 API。它用于将 HTTP 或 HTTPS 请求直接发送到 web 服务器,并将服务器响应数据直接加载回脚本。数据可能以 XML 文本或纯文本的形式从服务器接收(来自维基百科[
en.wikipedia.org/wiki/XMLHttpRequest](http://en.wikipedia.org/wiki/XMLHttpRequest)
) 。
了解后期消息 API
浏览器中加载的所有 web 应用都受浏览器沙盒安全性的影响,沙盒安全性是一种用于在受限环境中运行应用的安全机制。
这意味着,如果有人试图利用浏览器执行恶意代码,浏览器沙箱会阻止这些代码对主机造成损害。
这就是为什么 web 应用不能创建、修改或读取主机文件系统上的文件或任何信息。此外,它们不能加载和访问不同位置的页面上的脚本,这些页面不使用相同的协议、端口号和主机域。
当然,您可以通过各种技术来克服这种限制。一种是使用 web 服务器代理——不是直接对 web 服务进行 XMLHttpRequest 调用,而是对 web 服务器代理进行调用。然后,代理将请求传递给 web 服务,并将响应数据传递回客户端应用。
但是,使用 HTML5 和新的 postMessage API,您现在可以启用跨源通信,这样您的 web 应用就可以使用一种受控的机制来启用跨站点脚本,而无需使用任何类型的变通方法。
根据[
dev.w3.org/html5/postmsg/](http://dev.w3.org/html5/postmsg/)
,后消息语法如下:
window.postMessage(message, targetOrigin [, ports ])
消息参数包含要发布的消息,targetOrigin
表示为了调度事件,otherWindow
的来源必须是什么,ports
属性可以选择包含给定窗口的端口数组。
如果目标窗口的原点与给定的原点不匹配,则消息被丢弃。这可以防止信息泄露。
要向目标发送消息,不考虑来源,将目标来源设置为"*"
。要将消息仅限制到相同来源的目标,而不需要显式声明来源,请将目标来源设置为"/"
。
这里有一个实际的例子来解释这种机制:想象一个场景,其中,hostPage.htm
web 页面包含一个 iframe 元素,该元素包含embeddedPage.htm
页面。hostPage.htm
页面中的一个脚本调用了embeddedPage.htm
页面的窗口对象上的postMessage()
,然后一个消息事件在该对象上执行,并被标记为来自hostPage.htm
页面的窗口。
这将是在hostPage.htm
页面中编写的代码:
var element = document.getElementsByTagName('iframe')[0]; element.contentWindow.postMessage('Hello postMessage API', 'http://www.mydomain.com/');
当调用postMessage()
时,一个消息事件被分派到目标窗口。此接口具有公开数据属性的类型消息事件,该属性返回消息的数据。
您可以使用addEventListener()
为消息事件注册一个事件处理程序:
window.addEventListener('message', messageHandler); function messageHandler(e) { alert(e.data); }
在messageHandler
事件处理程序中,我们只显示一个警告窗口,其文本包含在数据属性中。但是,最好检查您收到的邮件是否来自预期的域。为此,可以使用 origin 属性,该属性返回消息的来源:
if (e.origin == 'http://www.mydomain.com/') {
然后我们可以检查消息文本:
if (e.data == 'Hello postMessage API') {
最后,通过使用 source 属性,您可以决定是否将消息发送回最初发送消息的 HTML 页面,该属性返回源窗口的WindowProxy
:
e.source.postMessage('Hello', e.origin); } else { alert(e.data); } }
确保邮件后通信的安全
这种跨站点脚本的新方法非常强大和有用,但是它可能会使服务器暴露于攻击之下。这就是为什么小心谨慎地使用 postMessage API 非常重要的原因:
不要在包含任何机密信息的消息中的targetOrigin
参数中使用*
:
window.postMessage('I'm sending confidential information', '*');
相反,请指定一个域,否则无法保证消息只传递给目标收件人:
window.postMessage('I'm sending confidential information', 'http://www.mydomain.com/');
始终检查 origin 属性,以确保只接受来自预期接收消息的受信任域的消息:
if (e.origin == 'http://www.mydomain.com/') { // whatever }
甚至:
if (e.origin !== 'http://www.mydomain.com/') return;
这样,您可以避免其他页面出于恶意目的欺骗此事件。
为了更加安全,请检查接收到的数据是否是预期的格式:
`if (e.origin == ‘http://www.mydomain.com/’)
{
if (e.data == ‘Hello postMessage API’)
{
}
}`
尽量避免使用innerHTML
或outerHTML
来注入接收到的数据消息,因为该消息可能包含一个脚本标签并立即执行。相反,使用textContent
属性来编写消息字符串。
解决方案 8-1:检查邮件 API 浏览器支持
postMessage API 允许您找到一种跨浏览器窗口传递基于文本的消息的方法。iframes、窗口和弹出窗口之间可以进行通信。以下浏览器支持邮件后 API:
- Internet Explorer 8.0 以上
- 火狐 3.0 以上版本
- Safari 4.0 以上版本
- 谷歌浏览器 1.0+
- Opera 9.5+版本
不管支持与否,检查加载页面的浏览器的 API 兼容性仍然是一个最佳实践,特别是因为如果不支持 postMessage API,应用可能会暴露出严重的问题。
涉及到什么
为了能够执行支持检查,使用 JavaScript typeof
操作符,它允许您检查其操作数的数据类型。表 8-1 显示了运算符类型返回的可能值列表。
表 8-1。 JavaScript 运算符的数据类型
| **数据类型** | **描述** | | :-- | :-- | | 数字 | 表示一个数字 | | 线 | 表示一个字符串 | | 布尔 | 表示布尔值 | | 目标 | 表示一个对象 | | 空 | 表示为空 | | 不明确的 | 表示未定义 | | 功能 | 表示一个函数 |使用typeof
操作符非常简单;您只需将命令放在您想要检查的变量之前,它将自动返回其数据类型:
var num = 1; var str = "Hello HTML5"; alert( typeof num ); // it returns number alert( typeof str ); // it returns string
为了检查 postMessage API 的浏览器支持,让我们创建一个条件来确保 postMessage 的typeof
不会返回一个未定义的值。
如何建造它
要检查浏览器对 postMessage API 的支持,您只需在编写使用新 API 的函数之前,在脚本块中插入以下 JavaScript 条件:
if (typeof window.postMessage != 'undefined') { alert ('The postMessage API is supported'); }
此代码必须位于页面的脚本块之间,如以下示例所示:
`
Solution 8-1: Checking for postMessageAPI browser support
`
为了检查对 postMessage API 的支持,您可以简单地点击按钮输入类型,JavaScript 函数将被执行。
跨文档信息传递和 CORS
在前面的段落中,我们讨论了加载到浏览器中的所有 web 应用如何受到其沙箱安全性的影响。这意味着浏览器不允许访问主机服务器之外的资源。如果一家公司有一个大型网站,包括几个运行特定活动的子域,如托管应用、数据库或部门数据,这可能是一个问题。
这就是为什么开发人员试图找到克服这一限制的方法。最流行的方法之一是创建服务器端代理,但是开发人员社区要求一种原生的跨域请求方法。
这导致 W3C 在许多浏览器中引入了一种新的方法来解决这个问题。它通常被称为 CORS,或跨源资源共享。
CORS 是一种浏览器技术规范,它定义了 web 服务为来自相同起源策略下的不同域的沙箱脚本提供接口的方式。
这种方法提供了自定义 HTTP 头的使用,它告诉浏览器如何与服务器通信。这样,浏览器和服务器就有足够的信息来判断请求或响应是否会失败。
想象以下场景:一个资源有一个简单的文本资源驻留在www.domainA.com
,它包含字符串“你好,CORS!”,并且您希望www.domainB.com
能够访问它。如果服务器决定请求应该被允许,服务器返回一个与Access-Control-Allow-Origin
头结合的响应给www.domainA.com
。
基本上,Access-Control-Allow-Origin
头返回相同的原点。(如果是公共资源,它将返回通配符*。)
此跨源共享标准用于为以下项目启用跨站点 HTTP 请求:
- 跨站点方法中 XMLHttpRequest API 的调用
- Web 字体(用于 CSS 中@font-face 中的跨域字体使用),以便服务器可以部署 TrueType 字体,这些字体只能跨站点加载并由允许这样做的网站使用
在本章中,您将使用 CORS 创建跨文档消息传递(解决方案 8-2)和跨源 XMLHttpRequest(解决方案 8-6)。
解决方案 8-2:在窗口和 iframes 之间发送消息
在上一节中,我们讨论了跨文档消息传递和 postMessage APIs,以及它们如何允许我们在不同的资源之间实现跨来源的通信。
在这个解决方案中,我们将展示如何让 iframe 与它的主机网页通信,主机网页将被有意地发布在不同的域上。
涉及到什么
您将用来创建父页面和不同域上发布的 iframe 之间的通信的对象是postMessage()
方法。
该方法的语法有三个参数:消息、targetOrigin
的 URL 和端口。
window.postMessage('Hello postMessage API', 'http://www.mydomain.com/');
当调用postMessage()
方法时,在目标窗口中调度一个消息事件。此接口有一个公开数据属性的事件类型消息,该属性返回消息的数据。
您可以使用addEventListener()
为消息事件注册一个事件处理程序:
window.addEventListener('message', messageHandler); function messageHandler(e) { alert('This is the origin of the message received: ' + e.origin + ' /n And this is the message: ' + e.data); }
在messageHandler
事件处理程序中,我们简单地显示了一个警告窗口,其文本包含在数据属性中。但是,最佳做法是使用 origin 属性检查您接收的消息是否来自预期的域,该属性返回消息的来源:
if (e.origin == 'http://www.mydomain.com/') { alert('This is the origin of the message received: ' + e.origin + ' /n And this is the message: ' + e.data); } else { // prevent from receiving this message as the target origin does not match ! }
让我们通过一个例子来讨论这个解决方案。
如何建造它
首先,你需要提供一个很好的应用上下文描述。首先,下载在以下域发布的Solution_8_2.html
文件:[
casario.blogs.com](http://casario.blogs.com)
,在文件夹([
casario.blogs.com/files/Solution_8_2.html](http://casario.blogs.com/files/Solution_8_2.html)
)中。
这个保存为chatIframe.html,
的文件充当 iframe 的容器。该文件发布在域[
www.comtaste.com](http://www.comtaste.com)
的演示文件夹:[
www.comtaste.com/demo/chatIframe.html](http://www.comtaste.com/demo/chatIframe.html)
中。
您将创建的示例允许在不同域上发布的两个文件进行通信。
让我们处理第一个文件——充当 iframe 容器的文件。从主体中声明的几个用户界面元素开始:
<body> <h2>Solution 8-2</h2>
插入一条消息,并点击按钮向下面的 iframe 发送消息:
`
Send Message
您声明了一个文本输入和一个按钮,允许用户向页面上的 iframe 发送消息。
然后从以下 URL 加载 iframe:[www.comtaste.com/demo/chatIframe.html](http://www.comtaste.com/demo/chatIframe.html)
。
您必须使包含在文本输入消息中的消息在用户点击sendBtn
按钮时被发送,因此插入一个脚本块并声明前两个事件处理程序:
<script type='text/javascript'> window.addEventListener("load", init, true); window.addEventListener("message", msgHandler, true);
第二个事件侦听器是在消息事件上注册的。该事件由窗口对象在接收到任何消息时执行。这是将从 iframe 接收消息并在文本输入中显示它的事件处理程序。
接下来,添加一个包含targetOrigin
的全局变量,意味着将接受消息的域(在我们的例子中,是 iframe 加载的页面所在的域[www.comtaste.com](http://www.comtaste.com)
):
var targetURL = 'http://www.comtaste.com';
注册按钮上的事件处理程序。单击init()
事件监听器,这将调用postMessage()
方法:
function init() { document.getElementById('sendBtn').addEventListener("click", sendMsg, true); } function sendMsg()
{ document.getElementById('chatFrame').contentWindow.postMessage(document.getElementById('messag e').value, targetURL); }
contentWindow
属性返回由 iframe 元素chatFrame
生成的窗口对象。调用postMessage()
,它包含以下参数:
-
消息 :
document.getElementById('message')
。一个值,它是文本输入控件中包含的文本。 -
targetOrigin :表示要调度的事件(
[
www.comtaste.com](http://www.comtaste.com)
)的来源contentWindow
是什么的值。为了表示没有偏好,使用了*通配符(尽管这不是最佳实践)。
使用事件消息的事件侦听器完成脚本块:
`function msgHandler(e)
{
if (e.origin == targetURL)
{
document.getElementById(‘message’).value = e.data;
} else {
alert('This message has been sent from an unknown domain: ’ + e.origin);
}
}`
在此事件监听器中,检查消息是否来自受信任的域([www.comtaste.com](http://www.comtaste.com)
)以将其插入文本输入消息中:
document.getElementById('message').value = e.data;
我们使用由消息事件传输的事件对象的数据属性,它包含插入到 iframe 中的消息。
以下是在域[
casario.blogs.com](http://casario.blogs.com)
上发布的Solution_8.2.html
文件的完整代码:
`
window.addEventListener(“load”, init, true);
window.addEventListener(“message”, msgHandler, true);
var output = document.getElementById(“result”);
var targetURL = ‘http://www.comtaste.com’;
function init()
{
document.getElementById(‘sendBtn’).addEventListener(“click”, sendMsg, true);
}
function sendMsg()
{
document.getElementById(‘chatFrame’).contentWindow.postMessage(document.
getElementById(‘message’).value, targetURL);
}
function msgHandler(e)
{
if (e.origin == targetURL)
{
document.getElementById(‘message’).value = e.data;
} else {
alert('This message has been sent from an unknown domain: ’ + e.origin);
}
}
Solution 8-2
Click on the button to send a message to the iFrame below:
Send Message
This is the message:
<iframe id=“chatFrame” src=“http://www.comtaste.com/demo/chatIframe.html”
style=“height:480px; width:640px” />
`
现在我们可以转到 iframe 中加载的文件的代码:chatIframe.html
。
代码几乎相同。唯一真正的区别是在全局变量targetURL
中,这次它必须包含 URL,在这个 URL 上,Solution_8_2.html
页面部件作为一个值被发布:
var targetURL = 'http://casario.blogs.com';
代码的其余部分保持不变,因为我们创建的通信是双向的,从父级(Solution_8_2.html
)到子级(chatIframe.html
)。
这里是在域[www.comtaste.com](http://www.comtaste.com)
上发布的chatIframe.html
文件的完整代码:
`
window.addEventListener(“load”, init, true);
window.addEventListener(“message”, msgHandler, true);
var targetURL = ‘http://casario.blogs.com’;
function init()
{
document.getElementById(‘sendStatus’).addEventListener(“click”, sendMsg, true);
sendMsg(document.getElementById(‘message’).value);
}
function sendMsg()
{
messageTxt = document.getElementById(‘message’).value;
window.top.postMessage(messageTxt, targetURL); }
function msgHandler(e)
{
if (e.origin == targetURL)
{
document.getElementById(‘message’).value = e.data;
} else {
alert(‘This message has been sent from an unknown origin domain:’ + e.origin);
}
}
Solution 8-2: This is the IFrame
Click on the button to send a message to the iFrame below:
Send Message
为了测试解决方案,您可以在不同的域上发布这两个文件。您需要记住的唯一事情是通过指定您的域来更改这两个文件的targetURL
全局变量的值。
图 8-1 显示了两个网页之间交换消息的最终结果。
**图 8-1。**两个网页之间交换的消息
解决方案 8-3:使用服务器事件技术编写实时 web 应用
使用 HTTP 协议的客户机和服务器之间的通信实现了请求-响应模型。该模型允许您从客户端发送消息,向服务器发送 HTTP 请求,并等待来自服务器的 HTTP 响应。在这种通信中,不可能知道服务器从哪里开始与客户机交互。
有了 HTML5,通信模型有了额外的新的强大功能来创建实时 web 应用。
涉及到什么
服务器发送事件规范定义了一个 API,用于打开 HTTP 连接以接收来自服务器的推送通知。引入新的 EventSource 接口来实现规范,使服务器能够通过 HTTP 或使用专用的服务器推送协议将数据推送到网页。
使用EventSource(url)
构造函数非常简单。它只接受一个参数,即 URL 参数,该参数指定要连接的 URL:
var sourceURL = new EventSource('yourServerSideScript');
现在可以在onmessage
事件上注册一个事件监听器了:
sourceURL.onmessage = function (event) { // whatever you want };
服务器端的消息使用文本/事件流 MIME 类型发送:
data: This is the first message. data: This is the second message data: it has two lines.
使用这种新方法进行客户端/服务器通信,而不是使用 iframe 或XMLHttpRequest
对象(AJAX 基于此)的方法,有助于节省便携式设备的电池使用。您可以在at [www.w3.org/TR/eventsource/#eventsource-push](http://www.w3.org/TR/eventsource/#eventsource-push)
部分找到更多信息。
无连接推送和其他功能
在受控环境中运行的用户代理,例如绑定到特定运营商的移动手机上的浏览器,可以将连接管理卸载到网络上的代理。在这种情况下,出于一致性的目的,用户代理被认为包括手机软件和网络代理。
建立连接后,移动设备上的浏览器可能会检测到它位于支持网络上,并请求网络上的代理服务器接管连接的管理。这种情况的时间表可能如下:
- 浏览器连接到远程 HTTP 服务器,并请求作者在 EventSource 构造函数中指定的资源。
- 服务器偶尔会发送消息。
- 在两个消息之间,浏览器检测到它处于空闲状态,除了与保持 TCP 连接活动有关的网络活动之外,它决定切换到睡眠模式以省电。
- 浏览器与服务器断开连接。
- 浏览器联系网络上的服务,并请求该服务,即“推送代理”,保持连接。
- “推送代理”服务联系远程 HTTP 服务器,并请求作者在 EventSource 构造函数中指定的资源(可能包括 Last-Event-ID HTTP 头,等等)。
- 浏览器允许移动设备进入睡眠状态。
- 服务器发送另一条消息。
- “推送代理”服务使用 OMA push 等技术将事件传送到移动设备,移动设备唤醒的时间只够处理事件,然后返回睡眠状态。
这可以减少总的数据使用量,因此可以节省大量电能。
如何建造它
为了能够看到运行中的EventSource()
构造函数,您必须使用任何服务器端语言创建一个服务器端文件:您可以使用 Java、Python、PHP 或任何其他 web 编程语言。在这个解决方案中,我们调用了一个 PHP 文件来读取包含一些信息的文本文件。一旦服务器端脚本读取了这些信息,它们就会通过EventSource()
发送到客户端,并被插入到 web 页面的一些列表项中。
通过在正文中添加元素开始创建 HTML 页面:
`
Messages sent from the Server
`
您只有一个标题和一个无序列表,它将由我们的事件源填充。现在,您可以插入脚本块,该脚本块将在窗口的 load 事件上有一个事件侦听器:
window.addEventListener("load", init, false);
在init()
事件处理程序中,我们首先使用getElementById()
方法创建一个对列表元素的引用,并创建一个将文本插入 HTML 列表的函数:
`function init() {
var myList = document.getElementById (‘msgList’);
function msgHandler (message)
{
var elementLI = document.createElement (‘li’);
elementLI.innerHTML = message;
if (myList.children.length)
{
myList.insertBefore (elementLI, myList.firstChild);
}
else
{
myList.appendChild (elementLI);
}
for (var i = 10, j = myList.children.length; i < j; i++)
{
myList.removeChild (myList.children [i]);
}
}`
您可以开始创建 EventSource 对象。
如果浏览器支持 EventSource,则调用构造函数:
if (typeof (window.EventSource) !== 'undefined') { var source = new EventSource ('sse.php');
在这个阶段,浏览器解析在指向 PHP 文件sse.php
的url
属性中指定的 URL。
您需要做的就是管理 EventSource 对象的onmessage
事件,该事件在服务器每次向客户端返回消息时被调用:
`source.onmessage = function (event)
{
var msgSplit = event.data.split (‘\n’);
if (msgSplit.length == 2) {
msgHandler (‘
‘" title="’ + msgSplit [2] + ‘“/”>’ + msgSplit [0] + ‘’);
return ;
}
msgHandler (event.data);
};
}
else
{
msgHandler (‘Your browser does not support the EventSource Interface’);
}`
以下是 HTML 页面的完整代码:
`
window.addEventListener(“load”, init, false);
function init() {
var myList = document.getElementById (‘msgList’);
function msgHandler (message)
{
var elementLI = document.createElement (‘li’);
elementLI.innerHTML = message;
if (myList.children.length)
{
myList.insertBefore (elementLI, myList.firstChild);
}
else
{
myList.appendChild (elementLI);
}
for (var i = 10, j = myList.children.length; i < j; i++) {
myList.removeChild (myList.children [i]);
}
}
if (typeof (window.EventSource) !== ‘undefined’)
var source = new EventSource (‘sse.php’);
source.onmessage = function (event) {
var msgSplit = event.data.split (‘\n’);
if (msgSplit.length == 2) {
msgHandler (‘
- 为了接收消息,人们监听消息事件:
-
channel.port1.onmessage = handleMessage; function handleMessage(event) { if (event.origin !== "http://casario.blogs.com") return; // event.data returns "This is my blog !" }
本质上,在消息通道中,您必须处理两个 onmessage 事件—每个端口一个:
-
MessageChannel.port1.postMessage
将调用 MessageChannel.port2.onmessage -
MessageChannel.port2.postMessage
将调用 MessageChannel.port1.onmessage
-
这个对象无疑是开启 Web 2.0 革命的对象之一。实际上,它是使用 AJAX 作为客户机/服务器通信技术的应用所基于的对象。
微软在 1999 年引入了 XMLHttpRequest,为微软 Outlook Google adopted 程序提供了一个高度交互的用户界面,但是作为一个网络应用。
只是在 object 创建服务(如 Gmail 和谷歌地图)之后,它才变得非常流行,我们开始见证一场真正的网络应用革命。
为什么这个物体如此特别?因为它带来了独立于浏览器在 HTTP 和 HTTPS 协议上执行对 web 服务器的资源请求的可能性。在这个请求中,可以用 GET 或 POST 变量的形式发送信息,就像用表单发送数据一样。
客户端和服务器之间的请求是异步的,这意味着不需要等待它完成来执行其他操作,这些操作以几种方式混合了网页的典型数据流。
事实上,该流程通常一次包含两个步骤——用户请求(链接、表单或刷新)和服务器响应,然后会产生新的用户请求。
2006 年,W3C 终于认识到 XMLHttpRequest 的重要性,并成立了一个工作组来标准化这个浏览器对象提供的 API。
XMLHttpRequest Level 2 是定义一个改进的XMLHttpRequest
对象规范的新成果。对象规范支持的新特性之一是上传文件的可能性,因为 XMLHttpRequest Level 2 对象的 send 函数可以将文件或 BLOB 对象作为参数。
blob(代表二进制大对象或基本大对象)是在数据库管理系统中作为单个实体存储的二进制数据的集合。斑点通常是图像、音频或其他多媒体对象。
它还创造了向与创建 XMLHttpRequest 的浏览器文档不在同一个域的 URL 发送或检索数据的可能性(CORS 方法,在本章开始时已经讨论过)。
涉及到什么
使用 XMLHttpRequest 级别 2 对象类似于同一对象的早期版本。
事实上,您所要做的就是调用XMLHttpRequest()
构造函数:
var req = new XMLHttpRequest();
此时,可以用对象的-Open()
方法打开请求,或者用send()
方法发送请求:
req.open('GET', 'http://www.comtaste.com/', true);
open()
方法的第三个参数指定异步处理请求(当设置为 true 时)。
为了检查事务是否成功,您创建了一个事件处理函数对象,并将其分配给请求的onreadystatechange
属性。如果一切顺利,请求不包含错误,HTTP 状态返回 200。如果出现错误,将显示一条错误消息:
req.onreadystatechange = function (e) { if (req.readyState == 4) { if(req.status == 200) alert(req.responseText); else alert ("Error loading page."); } }; req.send(null);
以下是XMLHttpRequest
对象的主要属性:
- readyState:验证对象的状态,值可以从 0 到 4:
- 0 =未初始化:对象存在,但尚未实例化。
- 1 =打开:对象是打开的。
- 2 =已发送:请求已发送。
- 3 =接收:数据正在到达目的地。
- 4 =已加载:操作完成。
- responseText:以文本格式返回 HTTP 请求的结果。
- responseXML:以 XML 格式返回 HTTP 请求的结果。
- Status:返回事务的状态;包含几个类似于 web 服务器的成功和错误消息编号,如 404(找不到文件)、500(内部服务器错误)等。
以下是主要方法:
- abort:预先终止 HTTP 请求。
- getResponseHeaders:返回请求的头。
- 打开:打开请求。
- 发送:发送请求。
- setRequestHeader:设置请求的头。
如何建造它
XMLHttpRequest Level 2 规范为send()
方法提供了接受文件对象参数的能力。这允许您将二进制数据异步传输到服务器。
使用 HTML5 表单数据,您可以选择要发送到服务器的文件。formData
对象创建了一组键/值对,可以与XMLHttpRequest
对象的send()
方法一起使用。
尽管FormData
旨在发送表单数据,但它可以独立于表单使用,以传输数据:
var formElement = document.getElementById("fileInput"); var formData = new FormData(formElement); var xhr = new XMLHttpRequest(); xhr.send(formData);
在上面的例子中,我们创建了一个formData
对象,它包含插入到表单元素中的值,id 等于fileInput
(在我们的例子中,是一个输入文件)。这个formData
对象然后被传递给XMLHttpRequest
对象的send()
方法。
如果表单的编码类型设置为"multipart/form-data
",则传输数据的格式与表单的submit()
方法发送数据的格式相同:
<form enctype="multipart/form-data">
对于这个解决方案,您将创建一个简单的表单,它使用post
方法发送只有一个输入文件元素的数据:
<form enctype="multipart/form-data" method="post"> <label>Select a file to upload:</label> <input type="file" name="myFile" id="myFile" required> </form>
用户选择并添加到表单中的文件将被传递到一个formData
对象上,因此插入一个脚本块并创建一个 JavaScript 函数,该函数将在单击一个按钮时执行:
`
function sendForm()
{
}`
在此函数中创建两个变量,一个包含对文件输入类型的引用,另一个指向输出元素(稍后将创建):
var output = document.getElementById("result"); var data = new FormData(document.getElementById("myFile"));
现在您可以调用XMLHttpRequest
构造函数,将用户在表单中选择的文件发送到一个 PHP 文件(您可以使用任何服务器端语言),该文件将保存到服务器的文件系统中:
var xhr = new XMLHttpRequest(); xhr.open("POST", "uploadImage.php", false) xhr.send(data);
最后,如果数据传输成功或存在问题,您可以向用户提供反馈:
if (xhr.status == 200) { output.innerHTML += "Uploaded!"; } else {
output.innerHTML += "The following error has occurred: " + xhr.status; } } </script>
通过添加<output>
元素和一个调用sendForm()
JavaScript 函数的链接对文件进行最后的润色,我们已经在表单一章中讨论过了。以下是网页的完整代码:
`
function sendForm() {
var output = document.getElementById(“result”);
var data = new FormData(document.getElementById(“fileinfo”))
var xhr = new XMLHttpRequest();
xhr.open(“POST”, “uploadImage.php”, false)
xhr.send(data);
if (xhr.status == 200) {
output.innerHTML += “Uploaded!”;
} else {
output.innerHTML += “Error " + xhr.status + " occurred uploading your file.
”;
}
}
Upload File
`专家提示
使用formData
对象,可以在运行时添加数据,也可以发送普通的、非文件的、多部分/表单数据值。事实上,通过使用append()
方法,您可以通过调用这个方法将字段注入到formData
对象中:
var myData = new FormData(); myData.append("firstName", "Marco"); myData.append("lastName", "Casario"); myData.append("file", myFile.files[0]); var xhr = new XMLHttpRequest(); xhr.open("POST", "submitForm.php"); xhr.send(myData);
我们在这段代码中创建了一个formData
对象,包含三个值:名为“firstName”和“lastName”的字段,以及一个从文件输入类型(myFile.files[0])获取的文件数据。
解决方案 8-6:检查 XMLHttpRequest 级跨源浏览器支持
XMLHttpRequest Level 2 引入的最重要的新发展之一是可以克服该对象以前版本的限制:只能与相同的源服务器通信。
另一方面,新的 XMLHttpRequest Level 2 对象支持 CORS,这一点我们已经在本章中讨论过,它允许您的 web 页面与不同来源的服务进行通信。因此,这个特性在许多应用环境中变得至关重要,但是您必须确保浏览器支持它。
一些浏览器已经支持 XMLHttpRequest Level 2,但它还远未被普遍采用。表 8-2 详细说明了浏览器对 XMLHttpRequest 级的支持。
表 8-2。 XMLHttpRequest 二级浏览器支持
| **浏览器** | **版本** | | :-- | :-- | | 微软公司出品的 web 浏览器 | 尚不支持(它支持`XDomainRequest`对象。) | | 火狐浏览器 | 03.05.00 | | 谷歌 Chrome | 02.00.00 | | 歌剧 | 尚不支持 | | 旅行队 | 04.00.00 |从这个表中可以看出,在使用 XMLHttpRequest Level 2 之前,必须确保加载网页的浏览器支持这个对象。这将允许您使应用更加健壮,并避免奇怪的浏览器行为。
涉及到什么
为了能够检查浏览器是否支持跨源通信,您将使用withCredentials
属性。此属性控制凭据标志,该标志包含一个布尔值。当跨来源请求中包含用户凭据时,此值返回 true。当它们被排除在跨来源请求之外并且 cookies 在其响应中被忽略时,它返回 false 。该属性的默认值为 false。
与解决方案 8-1 一样,在这种情况下,您还将创建一个条件来检查 XMLHttpRequest 的withCredentials
属性是否返回未定义的值:
if (typeof xhr.withCredentials != 'undefined') {}
如果这个条件返回 false,并且浏览器因此不支持withCredentials
属性,我们的应用将相应地运行。
如何建造它
为了创建这个解决方案,您将添加一个事件侦听器,它将在 web 页面的 load 事件发生时检查浏览器是否支持跨原点功能。控件的结果将在输出元素上写入文本。
因此,插入addEventListener
方法,该方法将在您接下来编写的init
函数中注册加载事件:
window.addEventListener("load", init, false);
init()
事件处理程序首先实例化 XMLHttpRequest 对象,并创建一个对 id 为 result 的输出元素的引用:
var output = document.getElementById("result"); var xhr = new XMLHttpRequest();
此时,它将检查 XMLHttpRequest 对象的withCredentials
属性,查看该属性返回的值是否未定义:
if (typeof xhr.withCredentials != 'undefined') { }
现在,您可以在输出元素中写入条件的结果。以下是该解决方案的完整代码:
`
if (typeof xhr.withCredentials != ‘undefined’)
{
output.value += " Your browser supports the XMLHttpRequest Level 2 and its cross-origin
feature!“;
} else {
output.value += " Cross-origin XMLHttpRequest not supported by your browser.”;
}
}
window.addEventListener(“load”, init, false);
Solution 8-6: Checking for the XMLHttpRequest Level 2 cross-origin browser support
This is the output:
`你可以在图 8-2 中看到结果。网页已经载入谷歌 Chrome 9.0。
图 8-2。 Chrome 9.0 支持跨原点 XMLHttpRequest 属性。
专家提示
为了确保所有浏览器都能够使用跨原点特性,您可以在解决方案的代码中添加一个 else if 条件,以便在另一个浏览器不支持时使用XdomainRequest
对象(受 Internet Explorer 支持)。
根据 MSDN 文档([
msdn.microsoft.com/en-us/library/cc288060(v=vs.85).aspx](http://msdn.microsoft.com/en-us/library/cc288060(v=vs.85).aspx)):
),XDomainRequest
对象是一个安全、可靠、轻量级的数据服务,它允许任何页面上的脚本匿名连接到任何服务器并交换数据。当跨站点安全性不成问题时,开发人员可以使用XDomainRequest
对象。
您可以按如下方式更改前面的语句:
if ("withCredentials" in xhr) { output.value += " Your browser supports the XMLHttpRequest Level 2 and its cross-origin![images]() feature !"; xhr.open(method, url, true); } else if (typeof XDomainRequest != 'undefined') { output.value += " Your browser supports the XDomainRequest and its cross-origin feature !"; xhr = new XdomainRequest(); xhr.open(method, url); } else {
output.value += " Cross-origin XMLHttpRequest not supported by your browser."; xhr = null; } }
总结
新的 HTML5 通信 API 是为希望创建协作和数据密集型应用的开发人员提供可靠方法的重要一步。
在本章中,您学习了一些技术和解决方案,通过创建从不同域进行通信的文档(跨文档消息传递)来避免浏览器沙箱安全限制。您已经使用 postMessage API 和跨来源资源共享(CORS)方法为来自同一来源策略下的不同域的沙盒脚本提供了接口。
您还了解了如何在客户机和服务器之间创建实时通信,以使用服务器发送的事件规范找到服务器开始与客户机交互的位置,该规范指示 API 打开 HTTP 连接以接收来自服务器的推送通知。您还阅读了 XMLHttpRequest Level 2 的概述。