级别: 初级
陈珂 (mailto:chenke@snmobile.com?subject=使用脚本动态操作 SVG 文档), 技术总监, 南京安元科技
2005 年 5 月 01 日
本教程适用于那些希望使用可伸缩向量图形(SVG)创建交互式 SVG 图形的开发人员。它讨论了使用ECMAScript(JavaScript)对现有的 SVG 图像进行实时操作得技术。
本文主要介绍在 SVG 中通过编程实现动态操作 SVG 图像的知识。
SVG 图像的结构是用 XML 文档表示的,因此可以使用 XML 编程技术如"文档对象模型(Document Object Model,DOM)"来操纵它。本文描述了如何使用 ECMAScript/JavaScript 来支持与 SVG 图像的交互。理论上说我们可以用这些知识实现类似射击游戏这样复杂的图形交互程序。
有两种方法可以对SVG文档的DOM对象进行操作:通过JavaScript在SVG文档内部进行处理;在Batik环境下通过相关接口获取当前显 示SVG视图的DOM对象引用使用Java语言对SVG文档进行处理。本文重点描述使用JavaScript对SVG进行操作的相关技术,并在文章最后用 一个简单的例子实现Batik下通过Java实现操作DOM对象。另外还用相当的篇幅讨论了常用SVG浏览工具中支持的特殊 ECMAScript/JavaScript 用法,这些方法可以显著提高我们的开发速度。
1. 理解 SVG 对象结构
在 SVG 浏览器上下文环境("上下文环境"一词来自"context"的直译)中,除了 SVG 本身作为 XML 文档所包含的 DOM 对象外,还包含一些其他对象,这些支持对象随着浏览工具的不同而在细节上有所区别。
图 1. SVG 对象结构
Window 是一个全局变量,该变量表示 SVG 运行时的浏览器窗口对象。因为脚本的运行就是在 window 对象内部进行的,所以调用该对象方法和属性时可以省去对 window 变量的指定,例如 window.document 可以直接通过 document 实现引用。完全介绍 window 对象的属性和方法内容已经超出了本文的范围,有兴趣的读者可以通过参考资料查阅详细说明。
Document 是 window 对象中的静态全局变量,通过该变量我们可以立即获取当前浏览 SVG 图形的 SVG 文档对象(SVGDocument)。通过获取 SVG 文档对象我们就可以在 DOM 框架下对当前 SVG 文档的内容进行动态操作。
contextMenu 变量只在 Adobo SVG Viewer3.0中有效,该变量同 document 变量一样,也是 window 对象的静态全局变量。它引用了在Adobo SVG Viewer3.0浏览环境下单击鼠标右键时所展示菜单的 XML 文档对象(Document),通过重新构建该变量引用的对象内容,我们可以重新构建浏览时鼠标右键菜单的字体和条目。
回页首
2. 将 JavaScript 脚本放在哪里
使用 JavaScript 首先我们要解决一个简单的问题:我们把脚本代码放在哪里?SVG 标准允许将JavaScript 脚本代码以两种方式来实现:使用 script 元素将 JavaScript 脚本内嵌在 SVG 文件中;或使用script 的 xlink:href 属性从 SVG 文件之外连接 JavaScript 脚本文件。从脚本实现的功能上来说,这两种代码加载方式没有区别,我们可以将共享的脚本代码放在外边连接文件中,把该 SVG 文件个性化的代码嵌在自身的文件中。
下面是一个 SVG 文件和一份 JavaScript 脚本文件,将这两个文件放在同一个文件夹下打开即可运行。
表1: jslocation.svg
1. <?xml version="1.0" encoding="UTF-8"?>
2. <svg
3. xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
4. <script language="JavaScript" xlink:href="lib.js" />
5. <script language="JavaScript"><![CDATA[
6. function Body_function(str){
7. alert("Body_function->"+str)
8. }
9. ]]></script>
10. <g id="funciton_load" transform="translate(10 10)">
11. <g οnclick="Body_function('onclike')" transform="translate(0 0)">
12. <text x="6px" y="30px" style="font-size:20">Body_function</text>
13. </g>
14. <g οnclick="Lib_function('onclike')" transform="translate(160 0)">
15. <text x="6px" y="30px" style="font-size:20">Lib_function</text>
16. </g>
17. </g>
18. </svg>
因为在 SVG 引用外部脚本文件时是以 utf 编码方式引入的,所以我们不能在待引用的脚本文件中使用中文,甚至在注释中使用中文也会使代码运行出现不确定的异常。所以在实际运行时,需要删除本文后续例子中为代码所做的中文注释。
表2: lib.js
function Lib_function(str){
alert("Lib_function->"+str)
}
jslocation.svg 文件的第5-9行之间内嵌了一段 JavaScript 脚本,第4行引用了一个外部 js 文件。第 11 行和第 14 行分别通过 onclick 事件调用了这两种代码加载方式所包含的两个不同的函数。
特别需要留意的是第 3 行的对名称空间的声明。名称空间的声明指定了这些 SVG 元素位于 SVG 名称空间内,编写这些元素时无需带任何名称空间的前缀。在使用Adobo SVG Viewer3.0浏览 SVG 图像时可以缺省定义这些命名空间,Adobo SVG Viewer3.0浏览环境会默认的将文档内的 XML 元素识别为SVG 元素。但当我们使用Batik 浏览 SVG 图像时,这些命名空间是必须要指定的,否则,脚本的链接和其他一些复杂的功能将不能起作用。
回页首
3. 通过 JavaScript 动态操作 SVG 文档
3.1 通过 DOM 对象操作节点
在 JavaScript 环境下,通过 DOM 定义的接口,我们可以在 SVG 的 XML 树中漫游,可以对找到的节点属性重新赋值,还可以在当前文档中删除节点或添加新创建的节点。
下面的例子展示了通过 DOM 接口如何删除现有节点,添加新的节点并为新节点设置了超级链接地址:
表3: 对 SVG 文档进行动态操作
<g οnclick="operate_Dom()" id="g5" transform="translate(0 80)">
<text id="txt1" x="6px" y="30px" style="font-size:15">operate DOM</text>
<text id="txt2" x="6px" y="50px" style="font-size:15">operate DOM</text>
</g>
<!-内嵌脚本代码'
<script language="JavaScript"><![CDATA[
function operate_Dom(){
//通过document环境变量获取id值为"g5"的节点
var g5=document.getElementById("g5");
//将g5节点下所有text节点删除
var txts=g5.getElementsByTagName("text");
for(var i=txts.length-1;i>=0;i--){
g5.removeChild(txts.item(i));
}
//将g5节点的onclick事件删除
g5.removeAttribute("onclick");
//创造一个文本节点对象
var text = document.createElement ("text");
text.setAttribute("x", 100);
text.setAttribute("y", 100);
//将文本内容添加到text节点对象中
text.appendChild(document.createTextNode("new text"));
//创造一个链接节点,注意在这里给设置节点和属性时必须指定命名空间
var a=document.createElementNS("http://www.w3.org/2000/svg","a");
a.setAttributeNS(
"http://www.w3.org/2000/xlink/namespace/",
"xlink:href",
"http://www.sina.com");
//将text节点添加到链接节点中
a.appendChild(text);
//将链接节点添加到g5节点中
g5.appendChild(a);
//获取视图范围
var bBox=(document.getDocumentElement().getBBox());
//创建一个矩形节点
var shape = document.createElement("rect ");
//配置属性
shape.setAttribute("x", bBox.x);
shape.setAttribute("y", bBox.y);
shape.setAttribute("width", bBox.width);
shape.setAttribute("height", bBox.height);
shape.setAttribute("style", "fill: #eeeeee");
shape.getStyle().setProperty("stroke","red");
shape.getStyle().setProperty("stroke-width","1");
//将矩形节点添加到SVG根节点子节点队列的最前边
document.getDocumentElement().insertBefore(shape,document.getDocumentElement().firstChild);
}
]]></script>
关于 DOM 接口的详细定义,我们可以通过参考资料提供的相关链接获取。除了在 DOM 接口中定义的相关方法和属性之外,SVG 在具体实现 JavaScript 时根据图形处理的需要对 DOM 接口也进行了相应的扩展。比较重要的扩展是关于 SVG 视图比例池相关的方法,这些方法可以让我们通过 document.getDocumentElement()获取 SVG 文档的坐标系统的附加信息。
表4: 文档根节点扩展的坐标系统附加信息
除了这里提到的比较重要的接口之外,我们还可以通过使用的 SVG 浏览器产品的开发文档获取详细信息。但遗憾的是这些文档往往写的非常简略,很多操作接口只是给出了 IDL 定义并没有对接口的内容进行详细阐述,这就需要我们发挥想像力去猜测并进行具体的测试验证了。比较常用的方法是分析 Batik 的源代码跟踪相关函数的具体实现来获取函数的执行过程。
需要特别指出的是用于创建超级链接节点的相关代码,当需要创建超级链接节点的时候需要明确指出节点的命名空间"http: //www.w3.org/2000/svg",为节点设置链接属性的时候也必须指定属性的命名空间"http: //www.w3.org/2000/xlink/namespace/"。关于命名空间在 SVG 中的意义我们可以通过在 Batik 中的脚本改写来深刻体会其中的内涵。
3.2 针对 Batik 的脚本改写:
在 Batik 环境下运行刚才的例子时,你会发现根本得不到我们在 Adobo SVG Viewer3.0 下看到的效果。这是因为在 Adobo 的环境下对 DOM 编程的要求不是很严格,所以我们可以用比较模糊的代码来实现这些功能,IE浏览器的 JavaScript 引擎会对我们调用的函数进行自动匹配(这类似于 C 语法中的默认参数值)。但在Batik 环境下使用的 JavaScript 的引擎是引自 Apache 的脚本引擎库,该脚本引擎对 JavaScript 的代码要求非常严格。为了能在 Batik 环境下运行,我们必须对 JavaScript 脚本进行严谨的修改,使其能经过 Batik脚本引擎的考验。我们将红色部分的代码注释掉换上新的代码就可以看到在Batik环境下JavaScript 对 DOM对象的操作起作用了。
在 Batik 环境下需要创建新节点时,必须指定新节点引用的命名空间"http://www.w3.org/2000/svg"才能有效。
表5: 针对 Batik 的改写
创建新节点的时候必须明确指定该节点的命名空间
//var shape = document.createElement("rect");
var shape = document.createElementNS("http://www.w3.org/2000/svg"," rect ");
//var text = document.createElement ("text");
var text = document.createElementNS("http://www.w3.org/2000/svg","text");
定义样式属性时必须指定该属性的优先级,一般设置一个空白字符串就可以了。
//shape.getStyle().setProperty("stroke","red");
//shape.getStyle().setProperty("stroke-width","10");
shape.getStyle().setProperty("stroke","red","");
shape.getStyle().setProperty("stroke-width","10","");
为 Batik 改写的脚本代码在Adobo SVG Viewer3.0环境下是可以正确运行,这意味着通过对代码进行细致的处理,我们能够编写在两种平台下都能运行的脚本。