SVG开发实践(部分章节)

发布时间:2011-01-05 17:40:25

12.1 SVG中使用JavaScript脚本

JavaScript脚本在SVG中分为内部脚本和外部脚本两种,内部和外部只是引用方式的区别,对程序的编写不造成影响。首先介绍一下内部脚本,它是以如下形式嵌入到SVG文档中的:

SVG采用

该例展示了如何在SVG文档被载入后,调用初始化程序,以获得SVGDOM结构,为后续的编程做好准备。

例程12-1中,u 处表示在SVG文档载入时激活的“onload”事件中执行“init”函数;“init”函数先是得到SVG Document对象,然后获得该对象的根元素(也就是“SVG”元素),最后的效果是弹出一个消息框,上面显示“SVG”

v 处的代码可以替换为“svgDoc = evt.getTarget().getOwnerDocument;”,得到的效果是一样的。

12.2.2 DOM对象操作相关

前面我们已经介绍过,DOM对象是一个树型的结构,并且经过载入后就放在内存中供我们读写。如何对这棵树进行操作,也就成为发挥SVG交互性很关键的一步。下面所示的方法中,有些是文档对象(Document)的方法,有些是文档元素(Element)的方法,需要区别开来。DOM可以分为三大部分:文档基本元素、文档对象和各种类型的从文档基本元素派生出的文档元素。文档对象是文档对象模型的顶级对象,它包含了整个文档的内容。各种类型的文档元素派生自文档基本元素类型,用于描述文档中各种实际存在的元素。其中可以定义一种文档元素,它们可以容纳其他的文档元素,这些元素就是容器元素,实际上文档对象就是最大的容器元素。由于文档对象模型中存在容器元素,因此所有的对象都组成一个树状结构,称为文档对象树或者DOM树,其中根节点就是文档对象。

— getElementById(ID_Name)方法

通过元素的ID名获得该元素。

使用举例:object = svgdoc.getElementById("map")

— getElementsByTagName(Tag_Name)方法

通过元素名获得一个或者一组元素,注意方法名中的“Elements”是复数,说明返回的元素可能有多个,是一个“NodeList”

使用举例:object = svgdoc.getElementsByTagName ("rect")

例程12-2 获得SVG文档中的元素

打开该文档后,弹出的消息框上显示“[object SVGRectElement],[object NodeList]”

例程12-2中,u处使用“rect1”ID名得到了“svgRoot”下属的一个矩形元素(SVGRectElement)。v是为了获得所有“svgRoot”下属的元素,返回的是一个“NodeList”,本例中一共有三个符合条件的元素。

— getAttribute(ID_Name)方法

根据所提供的ID名来获得元素的属性值。

使用举例:color = node.getAttribute ("fill")

— setAttribute(Attribute_Name,Value)方法

设置该元素属性名为“Attribute_Name”,属性的值为“Value”

使用举例:color = node.getAttribute ("fill")

— setAttributeNS(NameSpace, Attribute_Name ,Value)方法

功能效果同setAttribute方法,区别就是增加了为属性名加上命名空间(NameSpace)。在ASV3.0中,属性名都是默认SVG的命名空间,所以不需要再特别注明,但是如果你要使用“xlink”中的属性,就要加入相应的命名空间“http://www.w3.org/2000 /xlink/namespace/”

使用举例:object = svgdoc.setAttributeNS ("http://www.w3.org/2000/xlink/namespace/", xlink:href, "index.html")

注意 绝对不要在同一个程序中混合使用DOM1非名称空间APIDOM2名称空间感知的API(例如,createElementcreateElementNS)。如果使用名称空间,请尽量在根元素位置声明所有名称空间,并且不要覆盖名称空间前缀,否则情况会非常混乱。一般来说,只要按照惯例,就不会触发使你陷入麻烦的临界情况。

例程12-3 设置SVG元素的属性

这里例子中我们接触到了SVG中的事件,这跟HTML中的事件很相似,关于SVG的事件我们会在后面的章节中做详细介绍。这里用到了两个事件:一个是鼠标单击事件“onclick”,一个是鼠标移动到时触发的“onmousemove”事件,注意它们的大小写,全部是小写,否则事件无法激活,浏览器会报告脚本错误。

我们想要实现的效果是,单击ID“rect1”的矩形时,能得到它的填充颜色值和矩形的高度值,并且鼠标移动到该矩形的时候,矩形的填充颜色从红色变成绿色;另外一个矩形,我们在单击它的时候,它的填充颜色从红色变成绿色。

例程12-3中,u处设置矩形“rect1”“fill”属性为“green”

v通过命名空间来设置属性值。不过命名空间参数的值是“null”,因为ASV3.0已经内置了命名空间,所以你再给这些SVG的属性添加命名空间的话就会出错,所以填入“null”值。

w是为了弹出消息框,显示我们需要知道的那两个属性值。

— createElement(Element_Type)方法

DOM对象内创建一个新的元素,可以指定创建哪一种类型的元素,并且返回对这个新元素的引用。

使用举例:newnode = svgdoc.createElement"rect"

— appendChild(Element)方法

在该元素的最后追加一个孩子节点。

使用举例:someElement.appendChildnode

例程12-4 动态创建SVG的元素

style="text-anchor:middle;font-size:15;fill:red">Click the rectangle

在上面这个SVG文档中,没有看到对“rect”元素的定义,但是实际显示的时候还是显示了一个红色的矩形,原因就在于例程12-4u处,我们使用“createElement”方法动态生成了一个矩形元素,并且逐个设置了它的“x”“y”“width”“height”“fill”属性,并且在v处为该元素添加了“mousemove”事件及事件相应的函数名。但是这样生成的矩形元素依旧还是流离失所,无法显示出来,需要使用appendChildinsertBeforereplaceChild等方法把生成的节点元素添加到其它其他元素下才能显示。所以,执行w处的语句后,生成的新元素被加入到名为“group”的组中去,从而显示出来。最终的DOM结构为:如图12-1所示。



12-1 动态生成SVG元素后的DOM结构

从图12-1中可以看出,新加入的“rect”元素与之前就存在的“text”元素位置并列。

— replaceChild(newElement, oldElement)方法

在某元素的子节点中,使用新元素替代旧元素。

使用举例:someElement.replaceChildnewNode, oldNode

例程12-5 replaceChild方法使用举例

w

这个例子想要实现的效果是:单击一个黑色的矩形后,使它移动到新的位置,并且填充颜色变成蓝色。可以使用动画的办法来实现,但现在我们要用编写动态脚本的方法来实现。例程12-5中,w处已经存在一个黑色矩形了,单击后触发事件执行“change”函数。u“obj”就是产生事件的“rect”元素,也就是此后要被替代掉的那个元素。然后创建一个新的“rect”元素,设置新的位置属性和填充颜色值。在v处进行元素的替代,w处的矩形元素就被替换成新的矩形元素,旧的矩形元素不复存在,从而也在显示区域内消失。

— removeChild(Element)方法

删除某元素下的指定元素。

使用举例:someElement.replaceChildNode

— insertBefore(newElement,refElement)方法

newElement是一个包含新子元素地址的对象,refElement是参照元素的地址,新子元素被插到参照元素之前。如果refElement参数没有包含在内,或者refElement不是此集合的成员,新的子元素会被插到该元素子元素列表的末尾。

使用举例:objDocumentElement someElement.insertBeforenewNode, refNode

— cloneNode(true/false)方法

复制一个新的元素,并且返回对这个元素的引用。

使用举例:someElement.cloneNodetrue

例程12-6 删除、插入、复制一个新元素

该例中有三个矩形元素,分别进行删除、插入和复制操作。例程12-6中,函数u进行删除元素的操作,直接调用“removeChild”方法,要删除的是在root元素下的“rect”元素,执行后,“rect”元素被删除,矩形也就消失了;函数v依旧是先在内存中生成一个新的“rect”元素,然后使用“insertBefore”方法把它插入到产生事件的那个“rect”元素的前面,两者是并列的位置;函数w先是克隆了一个与产生事件的“rect”元素一模一样的元素,并且返回给局部变量“newNode”,我们再对这个局部变量设置了属性“y”和填充颜色,然后把这个元素追加到“root”元素内的最后的位置,使它显示出来。

— firstChild属性、getFirstChlid()方法

获得某个元素的第一个子元素。

使用举例:node = someElement.firstChild

node = someElement.getFirstChild()

— childNodes属性、getChildNodes()方法

获得某个元素下面所有的子元素。

使用举例:nodeList = someElement.childNodes

nodeList = someElement.getChildNodes()

— item(n)方法

当获得了一个元素集合的时候,需要使用该方法进行引用。

使用举例:node = someElement.childNodes.items(1)

— NodeType属性

节点类型,是一个枚举量。

使用举例:i = someElement.NodeType

详见下面的列12-1

12-1 节点类型

返回的整数

节点类型常数

1

ELEMENT_NODE

2

ATTRIBUTE_NODE

3

TEXT_NODE

4

CDATA_SECTION_NODE

5

ENTITY_REFERENCE_NODE

6

ENTITY_NODE

7

PROCESSING_INSTRUCTION_NODE

8

COMMENT_NODE

9

DOCUMENT_NODE

10

DOCUMENT_TYPE_NODE

11

DOCUMENT_FRAGMENT_NODE

12

NOTATION_NODE

— NodeName属性

节点名。

使用举例:name = someElement.NodeName

详见表12-2

12-2 节点名称

返回的字符串

comment

这是一个注释节点

#document

这是一个文档节点

Element.tagName

元素的标记名,同时也说明这是一个元素

Attri.name

属性的名字,同时也说明这是一个元素

#text

这是一个文本节点

例程12-7 SVG节点操作

fill="white" stroke="black" stroke-width="2" />

W

orld!

这个例子帮助大家更加深刻地理解SVGDOM树型结构。

例程12-7中,u 是为了取到“svg”元素下的第一个子元素,也就是“script”元素,所以执行后消息框显示“First node of rootscript”

v 是使用“getChildNodes”方法取到“svg”下所有子元素,然后再引用第2个元素(索引值同数组相同,从0开始),执行后消息框显示“First node of rootg”,也可以使用“root.firstChild. nextSibling.nodeName”语句获得同样的效果,这里的“nextSibling”属性指的是紧挨着某个元素的下一个元素

w 是取得“text”元素下的所有子元素,然后取得这些子元素的第一个元素也就是“tspan”元素,执行后显示“Third child node of text element:tspan”。在使用这些取节点元素的方法或者属性时,一定要小心地数好元素的排列顺序,稍有不慎就会引起错误,所以应该尽可能减少引用层次。

— attributes

获得某元素的属性集合。

使用举例:attributes = someElement.attributes

— length属性、getLength()方法

获得集合元素所含有元素的个数,如attributeschildNodes属性有此属性或方法。

使用举例:len = attributes.length

len = attributes.getLength()

例程12-8 SVG遍历元素值操作

这个例子很好理解,就是遍历矩形元素的所有属性值,在u处,通过attributes获得一个属性值的集合,然后同“childNodes”属性类似,可以使用“item()”方法进行引用,从而获得相应的属性值,如图12-2所示。

12-1 2 遍历属性值的显示结果

12.2.3 事件对象evt相关

— clientX属性、getClientX()方法

鼠标指针相对于浏览器窗口的客户区的X坐标。

使用举例:cx = evt.clientX cx = evt.getClientX()

— clientY属性、getClientY()方法

鼠标指针相对于浏览器窗口的客户区的Y坐标。

使用举例:cx = evt.clientY cx = evt.getClientY()

注意 这两个坐标并没有计算文档的滚动高度或者宽度,如果事件发生在窗口的最上边,不管这个文档已经向下滚动了多远,clientXclientY都是0

— screenX属性、getScreenX()方法

鼠标指针相对于用户显示器左上角的X坐标。

使用举例:sx = evt.screenX sx = evt.getScreenX()

— screenY属性、getScreenY()方法

鼠标指针相对于用户显示器左上角的Y坐标。

使用举例:sx = evt.screenY sx = evt.getScreenY()

例程12-9 SVG动态获得和设置样式操作

style="stroke-width:1; stroke:black;fill:white"/>

这个例子不复杂,就是获得鼠标当前的客户区坐标和屏幕坐标,这两个坐标其实有着相对坐标绝对坐标的味道。但是很奇怪的是,在这个例子中,不论鼠标在什么地方,浏览器窗口在什么地方,两个坐标值总是相等的。

— getCharCode()方法

获得键盘输入的字符的ASCII码。

使用举例:key = evt.getCharCode()

例程12-10 SVG动态获得键盘输入字符

style="text-anchor:middle;font-size: 25; font-family:Arial;fill:red"> Display here

例程12-10很有趣,用来实时地显示用户在键盘上输入的字符。首先使用u处的“getCharCode”方法获得字符的ASCII码,如果是控制键(如CtrlAlt、方向键等),则该方法自动过滤掉这些字符。v处的“String.fromCharCode”方法可以把获得的ASCII码转换成相应的字符。

12.2.4 字符串及文本相关

— createTextNode(TextContent)方法

动态生成文本节点的文本内容。

使用举例:text = svgdoc.createTextNode("SVG")

例程12-11 SVG动态生成节点

例程12-11向我们展示了如何动态地生成一个文本节点,并添加这个节点文本内容。首先需要生成一个“text”元素,u处开始的代码就做了这样一件事情,相信大家已经很熟悉这种动态生成元素的方法了,这里不再赘述。v处的代码调用了“createTextNode”方法,参数就是“text”元素的文本内容,也就是要显示出来的内容。然后使用“appendChild”方法添加到新创建的“text”元素中去。最后再把整个新创建好的元素加入到根元素中。例子中没有任何实现定义好的“text”元素,完全通过脚本程序动态生成。

— getNumberOfChars()方法

获得元素所包含的文本字符的个数(包括空格)。

使用举例:number = someElement.getNumberOfChars()

— getComputedTextLength()方法

获得元素所包含的文本字符的显示长度。

使用举例:len = someElement.getComputedTextLength()

— selectSubString(i,j) 方法

高亮显示元素所包含的文本字符串中第i个字符(不包括i)后的j个字符。

使用举例:letter = someElement.selectSubString (2,3)

例程12-12 元素文本字符相关方法

style="text-anchor:middle;font-size:24;font-family:Arial;fill:red"> Welcome to SVG world!

上述三个方法是针对“text”元素的,只有“text”元素才能使用。

例程12-12中,u处代码计算了该文本元素所含的文本字符的数字,此例是计算“Welcome to SVG world!”这个字符串的字符个数,一共有21

v处代码计算字符串的显示长度,单位是像素

w处代码执行后,“lco”这几个字母呈现蓝色背景高亮显示,即第2个字母“l”3个字母高亮显示。

12.2.5 样式相关

— style属性、getStyle()方法

获得某元素的样式值,单独使用没有太大的意义,需要配合后续两个方法一起使用。

使用举例:style = someElement.style style = someElement.getStyle()

— setProperty(Style_Name, Value)方法

设置某元素的某样式项的值。

使用举例:someElement.style.setProperty("fill","red")

someElement.getStyle().setProperty("fill","red")

— getProperty(Style_Name)方法

获得某元素的某样式项的值。

使用举例:someElement.style.getProperty("fill")

someElement.getStyle().getProperty("fill")

例程12-13 SVG动态获得和设置样式操作

style="fill:blue;opacity:0.7" onclick="information(evt)"/>

style="fill:blue;opacity:0.7" onclick="changeStyle(evt)"/>

12-13中有两个矩形,单击上方的那个矩形,会弹出一个消息框显示该矩形的填充颜色值“blue”;单击另外一个矩形,矩形的填充颜色和透明度分别变成红色和不透明。在u处获得了产生事件的这个矩形的样式信息,通过v处的“getPropertyValue”方法可以获得样式信息中你想要的某项的值,这里我们获取了“fill”属性的值。我们要对另外一个矩形产生改变样式的效果,就需要使用w处的“setProperty”方法,传入的参数是样式项目名称和要修改的目标值。无论是获得还是设置样式值的方法,都必须跟在“style”属性或“getStyle”方法的后面才有效。

12.2.6 图形变换相关

— getBBox()方法

返回一个SVGRect矩形对象,这个矩形表示该SVG元素的外包矩形。

使用举例:someElement.getBBox()

例程12-14 在可视区域内移动一段文字

SVG

例程12-14比较复杂,希望大家耐心一些。例子想要实现的效果是拖动“SVG”这段文字,在一个矩形区域内移动,充分体现了SVG的交互性。

图形部分主要是一个“text”元素和“path”元素,“path”元素帮助绘制一个背景矩形区域,使得用户在操作时无需无须非常小心地单击到文字才能触发事件,只要鼠标在这个矩形区域内进行操作即可,可以称之为热区。为了让大家能够清楚地看到这个区域又不影响使用,设置透明度为不完全透明。例子中的事件主要有三个:鼠标键按下时(onmousedown)、鼠标键弹起时(onmouseup)和鼠标移动时(onmousemove),注意它们被放在了不同的地方,被激活的范围有所不同。

初始化程序初始化了上述的热区,也就是包裹着“SVG”这三个文字的最小矩形,使用“getBBox”方法获得这个外包矩形框,然后使用路径绘制这个封闭区域。

当鼠标键按下时,y处的代码被执行。首先判断“cible”变量是不是“rectBox”,也就是判断事件的来源是不是在“SVG”这几个字的矩形区域内产生的,如果在这个区域外产生,则后续的效果都不会产生,也就无法拖动文字移动了。“isMove”这个变量就是用来控制是否能够移动文字。此外,还记住了当前鼠标的位置。

如果“isMove”变量是“true”的话,当鼠标移动时,w处以下的代码都会被执行。要使文字能够移动,就要改变它们相对位置偏移属性“x”“y”。一旦文字的位置改变了,它的外包矩形区域也就改变了,我们要使用“getBBox”方法获得这个外包矩形框,并且重绘一遍。所以,最后的效果就是这个外包矩形框和文字一起移动了。

鼠标键弹起时,执行x处的代码,“isMove”变量又被置回“false”,本次操作完成。

— getCTM()方法

返回一个初始变换矩阵。

使用举例:someElement.getBBox()

— currentScale属性、getCurrentScale()方法

获得当前视图的缩放比例。

使用举例:someElement.currentScale

someElement.getCurrentScale()

— currentTranslate()属性、getCurrentTranslate()方法

获得当前视图的平移距离的SVGPoint对象。

使用举例:someElement.currentTranslate

someElement.getCurrentTranslate()

例程12-15 SVG动态获得当前坐标系统信息

style="stroke-width: 1;stroke:black;fill:#999999" />

ViewBox:

ViewBox: -200 -200 1750 1750

Centre 700 180

CurrentTranslate:

CurrentScale :

这几个函数很有用,通过它们可以获得当前SVG视图的平移以及变形情况,这样视图内的其它其他元素就有了参照值。

12.2.7 XML序列化相关

— printNode(Element)方法

将参数Element节点解析为字符串。

使用举例:printNode(node)

— parseXML(string)方法

将参数字符串序列化成一个节点对象。

使用举例:parseXML(string , svgdoc)

例程12-16 XMLDOM对象之间的序列化和逆序列化

style="stroke-width:1; stroke:red;fill:red" onclick="string2node()" />

12-23是单击了黑色矩形后的显示结果。图12-34是单击了红色矩形后的显示结果,在左上角出现了一个小圆

12-23 printNode方法执行结果 12-3 4 parseXML方法执行结果

这两个方法刚好是相互对应的一组函数,这一组函数用于进行字符串和DOM节点之间的转换。我们可以使用printNode()逆序列化指定节点元素,用于将当前SVG文档中的Node元素生成字符串保存为文本文件或提交给远程服务器。相反地,我们也可以通过parseXML()将一个字符串用指定的文档对象解析为一个节点(Node)对象;在Adobo SVG Viewer环境下可以不指定document对象,系统会默认使用当前SVG文档的Document对象解析字符串,所以在v处的“svgdoc”可以替换成“document”关键字。

例程12-16中,u 处代码就是把这个节点转换成一个字符串,字符串内容就是这个节点的内容,如图12-23所示。

v 处的代码把事先定义后的字符串内容(字符串内容是合法的符合SVG语法的语句)通过解析后成为一个DOM对象的节点,并且添加到DOM对象中,使之能够显示出来。

12.3 SVG鼠标事件响应的四种写法

SVG有四种常用的鼠标响应的脚本写法,根据不同的需要让大家有更多的选择。以下四种方式的例子都是单击一个红色的矩形后,它的填充颜色变成蓝色。

1SMIL方式

例程12-17 鼠标事件之SMIL方式

>

在前面有关动画效果的章节中我们使用过类似的方法,也就是在单击后触发一个动画效果,此例中被改变的是“fill”属性,由红变蓝,中间没有渐变的过程,一次到位。

2Attributes方式

例程12-18 鼠标事件之Attributes方式

xmlns:xlink=http://www.w3.org/1999/xlink

xmlns:a3="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"

a3:scriptImplementation="Adobe">

fill="red"

onclick= "changeColor(evt)"/> u

这种事件触发方式想必大家已经很熟悉了,在上一节中,满眼尽是这种事件触发方式,即把事件触发作为元素的属性与其它其他属性写在一起。事件属性在u处,“onclick”事件调用的是“changeColor”函数,参数是“evt”,这样使得函数内部脚本可以从“evt”获得事件相关信息。这种方式比较常用。

3JavaScript+SMIL方式

例程12-19 鼠标事件之JavaScript+SMIL方式

这个例子没有图形元素的事先定义,所有定义都是通过脚本完成的,包括事件的定义也都是动态脚本完成的,最后在内存中的SVG DOM机构与例程12-17是类似的。例程12-19中,v处的“createElementNS”方法,有了一个后缀“NS”是需要添加命名空间参数的,这里的命名空间定义在u处。

4EventListener方式

例程12-20 鼠标事件之EventListener方式

这种方法也是经常用到的,W3CDOM元素绑定事件处理函数唯一真正的标准方式。原理就是使用“addEventListener”方法来注册事件方法,而且一次性可以很方便地注册很多事件,“EventListener”被称为事件监听器 “addEventListener”这个方法的参数依次是:事件的名称(如:click)、处理该事件的函数名和是否启用事件捕获的布尔量(一般是false)。

例程12-20中,u处调用了“addEventListener”方法来注册“onclick”事件,事件处理函数是“changeColor”。值得注意的是写在参数里的事件名称,要去掉“on”,所以填写的是“click”,而不是“onclick”

“addEventListener”的方法中有一个参数表示是否启用事件捕捉,要理解这个变量的作用,我们需要了解一下在DOM2事件模型中,事件传播的三个阶段。

首先,在捕获阶段(capturing phase),事件是从文档对象(Document object)开始,沿着文档树向下一直到目标对象传播.。如果任何目标对象的祖辈(不包括目标对象本身)也有一些指定注册的捕获事件的处理程序,在事件传播的这个阶段(捕获阶段)将运行它们,是否启用事件捕捉的变量的意义也就在于此。

事件传播的下一个阶段发生在目标对象本身:所有注册到目标对象的对应事件处理程序都被运行。

事件传播的第三阶段是冒泡阶段,或者说按文档层次倒序进行,从目标元素到文档对象(Document object)。尽管所有的事件都受事件传播的捕获阶段(capturing phase)的影响,但是并不是所有类型的事件都冒泡,像“mousedown”这样的一般事件对文档中的其它其他元素是有意义的,所以这些事件才沿着文档层次向上冒泡,并且触发目标元素的祖元素的相应事件的处理程序。通常情况下,原始的输入事件冒泡,而高级的语义事件不会。

12.4 SVG事件类型总结

下面是SVG中可用的一些事件类型,按照事件的类别进行分类。

1UIEvents(用户界面事件)

Ø focusinonfocusin):一个元素获得焦点(例如,一段文本被选中)

Ø focusoutonfocusout):一个元素失去焦点(例如,一段文本放弃被选中)

Ø activateonactivate):一个元素通过被单击而激活,或者单击了“enter”键或“shiftenter”键。

2MouseEvents(鼠标事件)

Ø clickonclick):一个指针设备(例如,鼠标)在一个元素上进行单击,“mousedown”“mouseup”事件会伴随发生在同样的位置。如果多次在同一个位置双击,那么evt.detail 属性的值会变成2;单击的话是1,所以可以据此判断是双击还是单击。

Ø mousedownonmousedown):鼠标在一个元素上按下。

Ø mouseuponmouseup):鼠标在一个元素上被释放,也就是按键弹起。

Ø mouseoveronmouseover):鼠标在一个元素上悬停。

Ø mousemoveonmousemove):鼠标移动经过一个元素。

Ø mouseoutonmouseout):鼠标从一个元素移开。

注意 当鼠标在一个元素上单击的时候,三个事件的发生顺序为onmousedownonmouseuponclick

3KeyEvents(键盘事件,至今仍不是SVG标准的一部分,但是很多SVG解析器都支持)

Ø keydownonkeydown):按下一个键。

Ø keyuponkeyup):释放一个键。

Ø keypressonkeypress):按下并释放一个键。

4SVGEventsSVG文档事件)

Ø SVGLoadonload):当客户端完全解析了SVG文档或者其中的元素,或者所需的外部资源已经准备好的时候会触发此事件,此时已经准备开始渲染SVG图像。

Ø SVGUnloadonunload):当SVG文档从浏览器窗口或者某个框架中被删除后会触发此事件,此事件只能应用在元素上。

Ø SVGAbortonabort):当正在载入的文档或者元素被中止时会触发此事件。

Ø SVGErroronerror):当载入一个元素或者当执行脚本发生错误时,都会触发此事件。

Ø SVGResizeonresize):当嵌入有SVG文档的浏览器窗口或者框架的尺寸发生改变时会触发此事件,此事件也只能应用在元素上。

Ø SVGScrollonscroll):当用户拖动窗口的滚动条或者浏览文档视图时,或当通过脚本改变了“currentTranslate”属性值时,都会触发此事件,此事件也只能应用在元素上。

Ø SVGZoomonzoom ):当用户缩放SVG文档视图时,或当通过脚本改变了“currentScale”属性值时,都会触发此事件,此事件也只能应用在元素上。

5SMILEventsSMIL动画事件)

Ø beginEventonbegin):当动画开始时触发此事件,此事件对动画元素有效。

Ø endEventonend):当动画结束时触发此事件,此事件对动画元素有效。

Ø repeatEventonrepeat):当动画重复播放时触发此事件,此事件对动画元素有效。

6MutationEventsDOM 树变化事件)

Ø DOMSubtreeModifiednone):常规事件,通告一个元素或一个文档所有的变化。

Ø DOMNodeInsertednone):当一个新节点被插入到给定的父节点时的通知事件。

Ø DOMNodeRemovednone):当一个节点从父节点中删除时的通知事件。

Ø DOMNodeRemovedFromDocumentnone):当一个节点从文档中删除时,无论这种删除是节点直接被删除,还是它的祖先节点被删除导致它跟着被删除,都会生成该通知事件。

Ø DOMNodeInsertedIntoDocumentnone):当一个节点被插入到文档中,无论是直接插入还是随着祖先节点的插入而插入,都会生成该通知事件。

Ø DOMAttrModifiednone):当一个给定节点的属性被修改时生成该通知事件。

Ø DOMCharacterDataModifiednone):当一个节点(例如文本元素)内的字符数据被改变时,生成该通知事件。

12.5 SVGDeveloper的使用(6

SVGDeveloper中,对于事件的编写有着很好的支持,不仅可以在用户想要注册事件时列出当前所有函数;还可以为每个SVG的可视化元素像设置属性一样注册事件。

12-45是事件面板,使用过VBC#等拥有可视化界面的编程集成环境的读者应该对这个面板不陌生,不同的事件只要选择不同的函数名,就可以把这些函数注册到这些事件上,使用起来十分方便,特别是当你不记得某个元素拥有哪些事件时。

12-45 事件面板

当你不使用这个事件面板,而选择直接在元素中编辑时,(如图12-56所示)在一个元素中,输入事件“onclick=”后,SVGDeveloper会提示已经编写好的一个函数“init”,你就可以选择这个函数,省去了重新输入的麻烦。

12-5 6 编写代码时显示已存在的函数

12.6 本章小结

本章介绍了如何在SVG中使用JavaScript脚本以及相关的属性和方法,为后面编写较大型的例子奠定了基础。在SVG编写脚本,本质就是进行DOM操作的脚本的编写,通过动态地改变DOM的结构,来获得新的显示效果。所以本章会成为读者们经常返回翻阅的内容。

这一章,将介绍SVG如何同数据库进行交互,程序自动从数据库获得相关数据,并且使用SVG作为数据的展现手段。这样SVG更加符合表现层的要求,数据由专门的数据库保存和处理,尽量减少SVG处理商业逻辑运算,甚至一些SVG的配置项也可以保存在数据库中,供载入SVG文档的时候使用,可以提高SVG的灵活性和可配置性。

15.1 什么是Ajax

Ajax (Asynchronous JavaScript and XML))并不是一项新的技术,而是多种技术的综合,或者说是一种设计方式,这些技术包括JavaScriptXHTMLCSSDOMXMLXSTLXMLHttpRequest等技术。其中:

使用XHTMLCSS实现标准化的呈现界面。

使用DOM实现动态的显示和交互。

使用XMLHttpRequest实现与服务器的异步通信(ASV3中由“getURL”函数提供该功能))。

使用JavaScriptXHTMLDOMXMLXMLHttpRequest绑定。

各种技术在Ajax引擎中的作用如图15-1所示。

15-1 各种技术在Ajax引擎中的作用

整个交互通信过程是异步进行的,异步这个词是指Ajax应用软件与主机服务器进行联系的方式。如果使用传统模式,每当用户执行某种操作、向服务器请求获得新数据,Web浏览器就会更新当前窗口。图15-2展示了Ajax所采用的异步模式,浏览器不必等待用户请求操作,也不必更新整个窗口就可以显示新获取的数据。只要来回传送采用XML格式的数据,在浏览器里面运行的JavaScript代码就可以与服务器进行联系。执行结果到达时,才会通知浏览器客户端,使之能够在合适的时间显示执行结果。JavaScript代码还可以把样式表加载到检索到的数据上,然后在现有网页的某个部分加以显示。

15-2 Ajax的异步传输过程

Ajax的主要功能在于,改变浏览器客户端和服务器端传统的同步的交互通信方式为异步通信交互方式,从而丰富浏览器客户端功能,解决浏览器频繁刷新页面等待数据传输的问题,改善Web应用程序的用户体验。使用Ajax,就算不重载刷新Web页面,用户也可以顺利的快速的得到Web服务器的数据。

典型的,看看微软的Vitual Earth的功能(http://local.live.com)。当用户拖动地图左上角的放大标尺的时候,Web页上的地图立即被放大,页面却没有刷新。当按住鼠标左键移动地图的时候,地图跟着移动。这个过程是快速的,而期间用户并没有向服务器提交表单或者单击一个超链接。如果用传统的Web应用程序交互思维来理解,这个过程是难以理解的。这正是Ajax的魅力。

作为Web应用程序一部分的Ajax的生命周期更像桌面系统的GUI,而DOM在扮演了类似GUI控件的角色。JavaScript脚本向DOM注册事件监听器,操作DOM响应事件。在响应事件的过程中,Web服务器可能被调用。这个调用是异步进行的,所以事件监听阶段和事件响应阶段是分开的。

下面是一个典型的浏览器中Ajax应用的生命周期。

用户访问:用户访问一个网站,比如单击链接或者在浏览器中输入网站的URL地址。

页面初始化:页面初始化加载,准备处理用户输入或者刷新页面内容。

触发浏览器事件:浏览器触发一个事件,比如鼠标单击或者按下键盘。

向服务器发起请求:浏览器向服务器发出一个请求。

服务器处理请求:服务器收到浏览器发出的请求,调用业务逻辑接口处理请求。

服务器响应请求:服务器响应浏览器发出的请求,将处理结果返回。这个返回结果传递给在发出请求时指定的请求调用函数。

浏览器更新页面:请求调用函数根据响应结果更新DOM内容,比如DOM变量或者任何的JavaScript变量,更新页面内容。

触发浏览器事件à向服务器发起请求à服务器处理请求à服务器响应请求à浏览器更新页面这个过程是可以多次循环的,如图15-3所示。在循环过程中通常会有很多的变量产生,很多事件也可能在客户端解决而不提交到服务器。有些Ajax应用的生命周期可能是短暂的,随着用户提交表单或者重新刷新页面而结束。其他则持续呈现在浏览器上(用户不重新刷新页面,也不提交表单),一直响应用户的行为。

而在SVG中,Ajax使用变得更加简单,它将采取如下的工作方式:

— JavaScript事件触发:用户操作Web页面上的某个控件或者文字链接,触发一个JavaScript事件。比如单击一个按钮,或者自动请求一个页面。

— JavaScript事件处理函数执行,调用getURL函数:JavaScript事件被触发,相应的事件处理函数即被调用。在getURL函数中,XMLHttpRequest对象被初始化,并根据情况向服务器发出异步通信请求,将用户的请求提交到服务器。只不过这个过程被封装了,对开发者不可见了。

15-3 Ajax的生命周期

服务器接收用户请求:这个步骤与传统的Web应用程序交互模式一致。服务器接收到用户请求后,根据URL地址判断用户行为,响应用户行为,并将响应结果以HTML/XHTML/XML的形式打印(Response)出来。

— XMLHttpRequest接收服务器的响应数据:XMLHttpRequest检测到服务器已经将响应结果打印出来,即将响应结果以文本或者XML文档的形式返回,赋予一个JavaScrip变量,在SVG中这个变量为“data”

— JavaScript调用DOM处理“data”,更新页面内容:JavaScript调用DOM相应的方法,解析“data”,然后更新相应的页面内容。

页面被更新,一次Ajax处理过程结束。

这个工作方式和过程需要Ajax的各个组成技术相互配合,Ajax的主要角色在于JavaScript事件触发和响应、发起异步通信请求、接收返回的数据并更新Web页面内容。而生成HTML/XHTML/XML格式的数据的任务,由服务器完成。生成HTML/XHTML/XML格式的数据的方法,取决于Web应用程序所选择的解决方案,会有所不同。本书将使用微软的.NET技术作为服务器端技术。

15.2 模拟实时数据显示

15.2.1 原理分析

Web上是很难实现真正的实时显示的,只有通过不断地刷新页面来模拟实现。客户端把请求提交到服务器,服务器返回数据,然后客户端把数据显示在页面上,如果这个刷新的间隔时间很短,比如只有一两秒,那么客户端的显示结果可能会不断变化,给用户一种实时的感觉。如果不采用Ajax技术的刷新,会给用户很不友好的感觉,因为整个页面内容都会重新刷新再载入进来,遇到网络情况不佳的情况,页面可能出现长时间的空白。Ajax的异步技术很好地解决了这个问题,只有当服务器端的数据顺利取回后才能显示在客户端,并且因为是对DOM树的直接改动,所以使得页面没有刷新的感觉,提供了非常友好的界面交互效果。在SVG中,同样可以通过脚本编程来实现这样的效果,SVG可以同数据库进行交互,取回需要的数据,然后通过解析器渲染出来。实时与不实时只是取决于客户端刷新的间隔时间,间隔短就可以认为是接近实时状态;如果只有事件触发时才取回数据,那么就认为是非实时的普通数据访问,这种应用在SVG中也非常广泛。本节介绍的就是在SVG中,如何实现模拟实时数据访问效果。先看一下整个过程是什么样子的,如图15-4所示。



15-4 SVG模拟实时效果系统原理图

整个过程主要由四部分组成,负责数据显示的是SVG,负责数据存储的是数据库。中间起桥梁作用的是Ajax技术和服务器端程序。首先由客户端发起请求,调用了getURL函数,函数的参数就是需要请求的服务器端页面,可以在请求的地址后以?参数=值的形式传递参数。被请求的页面被执行,这个过程就是跟数据库直接交互的过程,可以通过执行SQL语句或者存储过程或者其它其他数据库访问技术来获得我们所需要的数据。获得数据后,服务器端程序会把数据按照我们想要的格式进行封装,比如可以是直接拼凑成SVG的语句,也可以是XML格式,还可以是简单的数据,它们之间只是用一些符号隔开而已。这个过程是异步的,所以数据全部准备好后发回客户端,客户端才会调用相应的回调函数进行数据解析和处理。在数据解析处理完毕后,就可以开始改变DOM树,使得这些新的数据能够成为SVG文档的一部分,从而改变SVG页面上的显示。

15.2.2 实例分析

本例要实现的效果就是在SVG1为间隔,不断取回数据库的数据,然后显示出来。在数据库中存放的是点数据,最终SVG显示出来的是一条折线。

例程15-1 SVG文件Data.svg

style="text-anchor:middle;font-size:24;font-family:Simhei;fill:red">

实时数据显示

例程15-1中,u处使用了setInterval函数,这个函数的作用就是能够定时(单位是毫秒)执行某个函数。在这里是每秒钟调用一次getData函数,与之对应的还有一个clearInterval函数,用来取消这个定时器。在调用getData函数时,写下函数的名字以及参数,并且使用引号把它们括起来,否则会引起一些错误。getData函数中调用了get URL函数,第13.3节中介绍过这个函数,有遗忘的读者可以返回阅读相关内容。vgetURL函数访问的服务器端程序页面是“Default.aspx”,这个页面不是用来展示的,只是用来读取数据并返回数据,带有一个“type”函数,用来传入当前的模拟秒数。数据传回时的回调函数是“displayCallback”,所谓回调函数就是异步请求完成后会执行的函数。

w处的代码是取回数据后很关键的一部分,回调函数一定有一个参数“data”,这个参数负责接收传回的数据,名字未必是“data”,可以起其它其他名字。可以通过“data.success”来判断数据状态是否正常;使用“data.content”来得到传回的所有数据内容,该例中传回的数据是形如

type="image/svg+xml">

这是嵌有SVG文件的HTML页面,代码很简单,就是使用元素嵌入SVG文件。执行这个例子的时候必须从浏览器中输入这个页面的网络地址开始(如“http://localhost/svg/test.html”),不能从直接单击SVG文件或者HTML文件开始。否则,浏览器会提示因为跨域而产生的错误。实际应用中也是这种使用模式,即把SVG嵌入到HTML静态页面或者.aspx.jsp等类型的动态页面。

例程15-3 服务器端的Asp.net文件default.aspx

protected void Page_Load(object sender, EventArgs e)

{

SqlConnection connect = new

SqlConnection(getADONETConnectString("welfreda", "sa", "Abcd1234!", "testSVG"));

try

{

connect.Open();

}

catch

{

connect.Close();

}

string strSec = Request.QueryString["type"];

if (strSec = = null) strSec = "1";

SqlCommand cmd = new SqlCommand("select x,y from dbo.rtData where second <= " + strSec, connect); u

SqlDataReader dr = cmd.ExecuteReader();

string data = "

d=\"M100,100 ";v

while (dr.Read())

{

data += "L"+dr[0] + "," + dr[1] +" ";

}

data = data + "\"/>";

dr.Close();

connect.Close();

Response.ContentType = "text/xml";w

Response.ContentEncoding = System.Text.Encoding.UTF8;

Response.Write(data);

Response.End();

}

private string getADONETConnectString(string strDataSource, string strUID, string strPWD, string strInitialCatalog)

{

return string.Format("Data Source={0};User ID={1};pwd={2};Initial

Catalog={3};",strDataSource, strUID, strPWD, strInitialCatalog);

}

这个例程是服务器端与数据库进行交互的例子,经历了数据库连接、执行SQL语句、返回数据记录等若干个常规的数据库访问的过程,如何使这个过程更加具有灵活性、安全性、稳定性,不是本书讨论的重点,如有需要请参考相关的书目,本书只以实现基本数据库访问功能为目标。

例程15-3中,u处的SQL语句的含义是,每次把当前秒数之前的数据读取出来。因为数据库的数据是事先准备好的,不是断断续续地根据实际情况插入数据库的,但为了让大家有一点实时的感觉,所以按照不同的秒数排列数据。数据库的表结构很简单,只有三列:第几秒(second)、X轴坐标(x)、Y轴坐标(y),每一行数据代表了第N秒钟的点的坐标位置。例如,当前是第3秒钟,那么会取出含第123秒钟的数据返回给SVG端显示。v处的代码进行SVG语句的拼接,每次都是返回一条完整的路径语句,只不过它的“d”属性的值不同罢了。w开始把封装好的数据进行输出,使用的是“UTF-8”格式的编码。如果直接访问这个页面并且赋予正确的参数,也可以看到最终输出的结果,这样便于我们进行调试。

15-5就是最终的效果图,可以看到图形是由一组线段构成的不规则路径。这个图形不是一次性生成的,而是一秒一秒从数据库中读取数据,不断向X轴方向生长而成。所以大家执行例程后看到的是动态变化的图形,就好像股票的成交价格波动图,随着时间的推移,不断地绘制折线图。只不过本例数据是事先准备好的,不是实时生成的,所以就使用了模拟读秒的方式来读取每秒的数据。在你修改了数据库的数据后,很快就会在SVG的图形上体现出来。

15.3 非实时数据显示

非实时显示与实时显示的区别在上面的章节已经说明过,本质是一样的,只是数据刷新的条件不一样而已,所以在代码上也不会有太多的区别,依旧先来看看SVG文件中的代码内容。

例程15-4 SVG文件Data.svg

style="text-anchor:middle;font-size:24;font-family:Simhei; fill:red">

非实时数据显示

与实时显示数据的区别在u处就开始显现出来,这里是直接调用getData函数,不用再启动定时器来调用这个函数,所以在u处也就没什么特别的地方。v处也没有悬念,跟上一节中的例子基本相同,唯一不同的是调用ASPX页面时不带任何参数。

w开始,跟上一节中的例子有些不太一样了,主要是解析数据的过程稍微复杂了一些。因为这次从服务器端传回的数据不是SVG语句,而是最原始的数据,与保存在数据库中的数据基本一样,除了增加了分隔符,所以必须按照分隔符进行数据拆分。在此处,传回的数据按照号进行数据的分割,放入到“points”数组中。x进行这个数组的遍历,再一次对数据按照号进行分割,就获得了每一个字段的数据。然后把这些数据作为函数drawPoint的参数。

y声明了一个新的字符串变量,并且立即对这个变量进行赋值。我们需要一个超级链接,这个链接是由一个小的实心圆和文本组成(效果如图15-6所示),,也就是说单击实心圆或者文本都算单击了链接,并且都会弹出一个新的窗口进入新的地址。“parseInt(x)+4”语句的含义是把“x”参数值转化为整数,因为转化前是字符串,进行运算后是进行字符串连接,而不是加法运算,这个语句的目的在于使得文字在X轴方向上进行些许偏移,不要跟实心圆重叠在一起。z是把这个新生成的链接节点进行序列化,并且添加到这个SVG文档的根节点下。

例程15-5 服务器端的Asp.net文件default.aspx

protected void Page_Load(object sender, EventArgs e)

{

SqlConnection connect =

new SqlConnection(getADONETConnectString("welfreda", "sa", "Abcd1234!", "testSVG"));

try

{

connect.Open();

}

catch

{

connect.Close();

}

SqlCommand cmd = new SqlCommand("select * from dbo.nonRtData",

connect);

SqlDataReader dr = cmd.ExecuteReader();

string data = "";

while (dr.Read())

{

data += dr["ID"].ToString().Trim() + "," + dr["name"].ToString().Trim()

+ "," + dr["url"].ToString().Trim() + "," + dr["x"] + "," + dr["y"] + ";";u

}

dr.Close();

connect.Close();

Response.ContentType = "text/html";v

Response.ContentEncoding = System.Text.Encoding.UTF8;

Response.Write(data);

Response.End();

}

private string getADONETConnectString(string strDataSource, string strUID,

string strPWD, string strInitialCatalog)

{

return string.Format("Data Source={0};User ID={1};pwd={2};Initial

Catalog={3};",strDataSource, strUID, strPWD, strInitialCatalog);

}

服务器端的代码没有太大的变动,主要是数据的封装变得简单了,只是简单的带分隔符的字符串连接。返回的数据内容类型,改成“text/html”,依旧是“UTF-8”编码格式。表15-1是数据库中实际的表格式,一共有5个字段,列出了不同的6个银行。每个银行的信息使用号进行连接,银行内部的5个字段的数据使用号进行连接。这些数据连接符可以根据你自己的喜好进行设定,没有强制,只需要在客户端解析数据的时候使用相同的分隔符即可。

15-1 数据库中的数据表结构及数据

ID

Name

URL

x

y

1

中国银行

bankinfo1.aspx

100

100

2

工商银行

bankinfo1.aspx

330

200

3

招商银行

bankinfo1.aspx

260

110

4

农业银行

bankinfo2.aspx

222

222

5

建设银行

bankinfo2.aspx

80

130

6

民生银行

bankinfo2.aspx

90

180

15-6 非实时数据显示最终效果

15.4 不使用getURL函数的Ajax框架

因为不是所有的SVG解析器都有“getURL”这个函数,所以有时为了让程序更加通用,可以选择更加本质的方法,也就是直接使用“XMLHttpRequest”对象进行异步交互。例程15-6就是一个比较典型的Ajax框架,但是还有很多地方需要改进,但是对付普通应用足够了。使用的时候只要把这个文件链接进SVG文件即可。

例程15-6 Ajax框架Ajax.js文件

var req;

function ajax(url){ u

req = false;

//本地XMLHttpRequest对象

if(window.XMLHttpRequest) {

try {

req = new XMLHttpRequest();

} catch(e) {

req = false;

}

//IE/Windows ActiveX版本

} else if(window.ActiveXObject) {

try {

req = new ActiveXObject("Msxml2.XMLHTTP");

}

catch(e) {

try {

req = new ActiveXObject("Microsoft.XMLHTTP");

}

catch(e) {

req = false;

}

}

}

if(req) {

//一旦状态改变就会执行processReqChange函数

req.onreadystatechange = processReqChange;

req.open("GET", url, true);

req.send();

}

}

parent.ajax = ajax;

/*等待响应*/

function processReqChange()

{

//请求完成

if (req.readyState == 4) {

//请求成功

if (req.status == 200) {

procData();

} else {

alert("获得数据遇到问题!\n请保证您的网络连接畅通;否则,请及时与管理员联系,谢谢!:\n" + req.statusText);

}

}

}

/*

读取XML格式内容,解析并获得数据

*/

function procData(){v

var xmlResult = req.responseXML;

var root = xmlResult.documentElement;

var data = root.firstChild.text;

alert(data);

}

调用该框架时,只需要调用u处的函数,参数是请求页面的地址,可以是相对地址也可以是绝对地址,但是必须与调用这个函数的页面在同一个地址域中。具体的技术细节,读者可以参看程序中的注释,这里不作详细介绍。

其它其他代码都可以不用改变,v是唯一需要改变的一段代码,这段代码对返回的XML格式数据进行剥皮处理,让我们可以得到真正我们想要的数据。然后可以把数据传回给SVG或者网页页面使用。

15.5 本章小结

现在很多应用程序都与数据库有着沾亲带故的关系,数据库帮助应用程序更好地完成数据处理的任务,很难想象没有数据库的世界是什么样的世界。同样,数据库应用,也会让SVG如虎添翼。没有必要把大量的数据都放在SVG文件中,这样只会使它变得臃肿,性能低下,把更多的数据存放在数据库中,按照我们的要求读取到SVG中显示,这就是本章所完成的任务。

SVG开发实践(部分章节)

相关推荐