JavaScript权威指南--多媒体和图形编程
知识要点
21.1节介绍如何用传统的JavaScript技术实现诸如图片翻转(鼠标指针移动到一张静态图片上切换成另外一张图片)这样的视觉效果。
21.2节介绍HTML5的<audio>和<video>元素以及它们的JavaScript API。
21.3和21.4介绍两项非常强大的用于客户端绘图的技术。能够在浏览器中动态生成复杂图形是非常重要的,因为:
- 用于在客户端生成图形的代码大小要比图片本身小很多,这样可以减少部分带宽。
- 通过一些实时数据来动态生成图形,需要消耗大量的CPU周期。而如果把这个任务放到客户端做,就可以有效地减轻服务器的负担,某种程度上也是节约了硬件开销。
- 在客户端生成图形也是符合现代Web应用的架构:服务器提供数据,然后客户端负责展现这些数据
21.3节介绍可伸缩的矢量图形(Scalable Vector Graphics,SVG)。SVG是一种基于XML的并且用于描述图形的语言,SVG图形可以通过JavaScript和DOM来创建和操控。
21.4节会介绍HTML5的<canvas>元素及其用于客户端画图的、功能齐全的JavaScript API。<canvas>元素是一项革命性的技术。
1.脚本化图片
如下的HTML代码段是一个非常简单的例子:它创建一张图片,并在鼠标指针经过的时候改变该图片:
<img src="images/help.gif"
onmouseover="this.src='images/help_rollover.gif'"
onmouseout="this.src='images/help.gif'">
为了有用起见,像图片翻转这样的效果需要较高响应度。这也意味着需要想办法来确保一些必要的图片要预提取,让浏览器缓存起来。客户端JavaScript定义了一个专用的API来达到这一目的:为了强制让图片缓存起来,首先利用Image()构造函数来创建一个屏幕外图片对象,之后,将该对象的src属性设置成期望的URL。由于图片元素并没有添加到文档中,因此,它是不可见的,但是浏览器还是会加载图片并将其缓存起来。这样一来,之后当设置成同样的URL来显示该屏幕内图片的时候,它就能很快从浏览器缓存中加载,而不需要再通过网络加载。
前面展示的图片翻转的代码片段并没有预提取它使用的翻转图片,这样,当用户第一次将鼠标指针移到图片上的时候会明显感到翻转效果有延时。要解决这个问题,将代码修改成如下形式:
<script>(new Image()).src="images/help_rollover.gif";</script> <img src="images/help.gif" onmouseover="this.src='images/help_rollover.gif'" onmouseout="this.src='images/help.gif'">
优雅的图片翻转实现方式
上述代码用了一个<script>元素和两个JavaScript事件处理程序的属性来实现一个简单的图片翻转效果。这个例子的代码非常不优雅:大量的JavaScript和HTML代码混在一起。
例子中展示了一种更为优雅的实现方式,这种方式允许在任意的<img>元素上,只要简单地指定了data-rollover属性(参见15.4.3节),就会创建一个图片翻转效果。要注意的是,该例使用了例13-5中介绍的onLoad()函数。同时它还用到了document.images[]数组(参见15.2.3节)从文档中查找所有的<img>元素。
例21-1:优雅的图片翻转实现方式。
2.脚本化音频和视频
<audio src="background_music.mp3"/> <video src="news.mov"width=320 height=240/>
由于各家浏览器制造商未能在对标准音频和视频编解码器支持上达成一致,因此,通常都需要使用<source>元素来为指定不同格式的媒体源:
<audio id="music"> <source src="music.mp3"type="audio/mpeg"> <source src="music.ogg"type='audio/ogg;codec="vorbis"'> </audio>
支持<audio>和<video>元素的浏览器不会渲染这些元素的内容。而不支持它们的浏览器则会将它们的内容都渲染出来,因此,可以在这些元素中放置后备内容(比如,一个用于调用Flash插件的<object>元素)
<video id="news"width=640 height=480 controls preload> <!--Firefox和Chrome支持的WebM格式--> <source src="news.webm"type='video/webm;codecs="vp8,vorbis"'> <!--IE和Safari支持的H.264格式--> <source src="news.mp4"type='video/mp4;codecs="avc1.42E01E,mp4a.40.2"'> <!--Flash插件作为后备方案--> <object width=640 height=480 type="application/x-shockwave-flash" data="flash_movie_player.swf"> <!--这里的参数元素用于配置Flash视频播放器--> <!--文本是最终的后备内容--> <div>video element not supported and Flash plugin not installed.</div> </object> </video>
Audio()构造函数
在不设置controls属性的情况下,<audio>元素没有任何视觉外观。正如可以使用Image()构造函数来创建一张屏幕外图片那样,HTML5中的媒体API同样也允许使用Audio()构造函数,并将媒体源URL作为参数,来创建一个屏幕外音频元素:new Audio("chime.wav").play();//载入并播放声音效果。Audio()构造函数的返回值和通过从文档中查询<audio>元素或者使用document.createElement("audio")来创建一个新的元素获得的都是同一类对象。这里要注意的是,Audio()是音频元素特有的API,换句话说,视频元素是没有类似Video()这样的构造函数的。
尽管对于多种不同格式的文件要分别定义媒体比较繁琐,但是,能够不借助插件在浏览器中原生播放音频和视频是HTML5中非常强大的新特性。要注意的是,对于媒体编解码器的问题以及浏览器对其兼容性的问题并不在本书讨论的范畴。接下来会集中讨论如何利用JavaScript API来操控音频和视频流。
2.1.类型选择和加载
想要测试一个媒体元素能否播放指定类型的媒体文件,可以调用canPlayType()方法并将媒体的MIME类型(有时需要包含codec参数)传递进去。如果它不能播放该类型的媒体文件,该方法会返回一个空的字符串(一个假值);反之,它会返回一个字符串:"maybe"或者"probably"。之所以返回"probably"这样不确定的结果,是因为音频和视频编解码器本身就非常复杂,在没有真正下载并尝试播放指定类型的媒体前很难确定是否真的可以支持播放此类型文件:
var a=new Audio();
if(a.canPlayType("audio/wav")){
a.src="soundeffect.wav";
a.play();
}
当设置媒体元素的src属性的时候,加载媒体的过程就开始了(除非将preload设置成"auto",否则,只会加载少量内容,因此该过程不会持续很长时间)。当设置src属性的时候,如果有其他的媒体文件正在加载或者播放,则会中止它们的加载或者播放过程。如果通过在媒体元素中添加<source>元素而不是设置src属性的方式指定媒体源,媒体元素无法知道是否已经将一系列<source>元素都添加完毕了,因此它也不会开始选择并加载<source>元素指定的媒体源文件,除非显式地调用load()方法。
2.2.控制媒体播放
//文档载入完成后,开始播放背景音乐 window.addEventListener("load",function(){document.getElementById("music").play(); },false);
2.3.查询媒体状态
例如,假设媒体文件从开始缓存起中间没有定点播放发生(跳过一段播放),可以使用如下代码来确定当前缓存内容的百分比:
var percent_loaded=Math.floor(song.buffered.end(0)/song.duration*100);
readyState属性指定当前已经加载了多少媒体内容,因此同时也暗示着是否已经准备好可以播放了。如下表格展示了该属性的取值以及对应的意义:
NetworkState属性指定媒体元素是否使用网络或者为什么媒体文件不使用网络:
当在加载媒体或者播放媒体过程中发生错误时,浏览器就会设置<audio>或者<video>元素的error属性。在没有错误发生的情况下,error属性值为null。反之,error的属性值是一个对象,包含了描述错误的数值code属性。同时,error对象也定义了一些描述可能的错误代码的常量:
可以以如下方式使用error属性:
if(song.error.code==song.error.MEDIA_ERR_DECODE) alert("Can't play song:corrupt audio data.");
下表根据它们触发的先后顺序,总结了22个媒体相关事件。这些事件不能通过属性来注册事件,只能通过<audio>和<video>元素的addEventListener()方法来注册处理程序函数。
3 SVG:可伸缩的矢量图形
一个简单的SVG文件如下所示:
<!--SVG图形一开始声明命名空间--> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><!--图形的坐标系--> <defs><!--设置后面要用到的一些定义--> <linearGradient id="fade"><!--将一种渐变色命名为"fade"--> <stop offset="0%"stop-color="#008"/><!--深蓝--> <stop offset="100%"stop-color="#ccf"/><!--渐变到浅蓝--> </linearGradient> </defs> <!--画一个具有宽的黑色边框并且渐变色为填充色的矩形--> <rect x="100"y="200"width="800"height="600" stroke="black"stroke-width="25"fill="url(#fade)"/> </svg>
学习资料:
http://www.w3school.com.cn/svg/index.asp
https://www.w3.org/TR/
当使用<img>或者<object>元素展示SVG图形的时候,SVG就变成了另外一种图片格式了,这种方式对于JavaScript程序员来说是不友好的。更好的方式是直接将SVG图片嵌入到HTML文档中,这样这些图片就可以通过脚本的方式来控制。由于SVG就是一种XML语法,因此可以将它以如下的方式嵌入到XHTML文档中:
//保存为XHTML文件 <?xml version="1.0"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg"> <!--声明HTML作为默认的命名空间,以"svg:"前缀的为SVG的命名空间--> <body> This is a red square:<svg:svg width="10" height="10"> <svg:rect x="0" y="0" width="10" height="10" fill="red"/> </svg:svg> This is a blue circle:<svg:svg width="10" height="10"> <svg:circle cx="5" cy="5" r="5" fill="blue"/> </svg:svg> </body> </html>
HTML5将XML和HTML的区别进一步缩小,允许SVG(和MathML)标记直接在HTML文件中使用,不需要命名空间的声明或者标签前缀:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> This is a red square:<svg width="10"height="10"> <rect x="0"y="0"width="10"height="10"fill="red"/> </svg> This is a blue circle:<svg width="10"height="10"> <circle cx="5"cy="5"r="5"fill="blue"/> </svg> </body> </html>
例21-2是一个pieChart()函数。
例21-3是另外一个用脚本绘制SVG图形的例子:它使用SVG来绘制一个模拟时钟
4.<canvas>中的图形
大部分的画布绘制API都不是在<canvas>元素自身上定义的,而是定义在一个“绘制上下文”对象上,获取该对象可以通过调用画布的getContext()方法。调用getContext()方法时,传递一个"2d"参数,会获得一个CanvasRenderingContext2D对象,使用该对象可以在画布上绘制二维图形。这里很重要的一点是要搞清楚,画布元素和它的上下文对象是两个完全不同的对象。由于CanvasRenderingContext2D名字太长了,因此这里做个约定,统一简称为“上下文对象”。同样地,“画布API”指的也就是CanvasRenderingContext2D对象的方法。
画布中的3D图形
浏览器提供商实现<canvas>元素用于绘制3D图形的API。这些API称为:"WebGL",它是绑定到OpenGL标准API的一个JavaScript。将"webgl"字符串作为参数传递给画布的getContext()方法可以获得用于绘制3D图形的上下文对象。由于WebGL很庞大,而且也非常复杂,本书将不会介绍它的一些底层API:其实Web开发者也更倾向于使用封装了WebGL底层API的工具类库而不喜欢直接使用WebGL API。
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> This is a red square:<canvas id="square"width=10 height=10></canvas>. This is a blue circle:<canvas id="circle"width=10 height=10></canvas>. <script> var canvas=document.getElementById("square");//获取第一个画布元素 var context=canvas.getContext("2d");//获取2D绘制上下文 context.fillStyle="#f00";//设置填充色为红色 context.fillRect(0,0,10,10);//填充一个正方形 canvas=document.getElementById("circle");//第二个画布元素 context=canvas.getContext("2d");//获取它的绘制上下文 context.beginPath();//开始一条新的路径 context.arc(5,5,5,0,2*Math.PI,true);//将圆形添加到该路径中 context.fillStyle="#00f";//设置填充色为蓝色 context.fill();//填充路径 </script> </body> </html>
之前我们看到SVG使用可以绘制或填充的线段和曲线这种路径来描述复杂的图形。画布API也采用“路径”的思想。然而不同的是,相比SVG使用一个包含了字母和数字的字符串来描述路径,画布API是通过一系列方法调用来定义路径的,如上述代码中的beginPath()和arc()方法调用。一旦定义了路径,其他的诸如fill()这样的方法就可以在该路径上操作了。而像fillStyle这样的上下文对象的属性则是指定了如何进行这些操作。接下来的内容将解释:
- 如何定义路径、如何绘制或者说勾勒路径的外边框以及如何填充路径的内部。
- 如何设置和获取画布上下文对象的属性以及如何保存和恢复这些属性的当前状态。
- 画布的大小、默认画布坐标系以及如何进行坐标变换。
- 画布API定义的大量的绘制曲线的方法。
- 一些用于绘制长方形的专用工具方法。
- 如何指定颜色、使用透明度以及如何绘制渐变色和重复的图案。
- 控制线条宽度以及顶点和端点外观的属性。
- 如何在<canvas>元素中绘制文本。
- 如何“裁剪”图形以保证图形不超过指定区域。
- 如何给图形添加下拉阴影效果。
- 如何在画布中绘制(和选择性地伸缩)图形以及如何作为图片从画布中提取内容。
- 如何控制画布中新画(半透明)像素和原有像素的融合过程。
- 在画布中,如何设置和查询像素的红、绿、蓝色值以及alpha值(透明度)。
- 当在画布中绘制图形的时候,如何判定是否触发了鼠标事件。