使用脚本动态操作 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. <svg3. 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環境下是可以正確運行,這意味著通過對代碼進行細致的處理,我們能夠編寫在兩種平臺下都能運行的腳本。
|
4. 由瀏覽工具提供的腳本支持
在 W3C 的 SVG 標準之外,各種品牌的 SVG 瀏覽器還提供了一些在 SVG 編程中支持的特殊函數和對象,用于實現一些特殊功能或提高開發效率。其中有些函數是各個產品都實現了的,這就大大降低了我們在移植過程中的難度。
4.1 數據通訊函數
函數名稱:getURL(uri, GetURLHandler)
支持環境:Adobe SVG Viewer3.0;Batik1.5.1
用途:該函數是 window 對象的提供的方法,可以允許你從指定的 URL 路徑實時加載數據。GetURLHandler 參數用于指定一個用于處理加載數據的函數指針。
表6: 使用 getURL
| function loadFile () {getURL("menuitem_en-us.xml", fileLoaded);}function fileLoaded (data) {if(data.success) {alert(data.contentType);alert(data.content);}} |
示例中的 fileLoaded 函數用于處理實時加載的文件,其中的 data 參數是一個關于指定 URL 文件信息的對象,該對象的 success 屬性用于標識是否成功加載指定文件;content 屬性用于記錄加載文件的文本內容;contentType 屬性標識文件類型(該屬性在 Batik 中未被支持)。
由 Adobo 實現的 getURL 方法在加載文件時可以智能的判斷加載文件的文件類型和編碼方式,你可以加載gzip 壓縮的 xml 文件,比如壓縮存儲格式的.svgz 文件在加載后會自動進行必要的解壓操作。在加載文本文件的時候還可以根據加載文件的編碼格式(ASCII ,UTF-8,UTF-16)進行自動識別。
警告:在Adobe SVG Viewer 的早期版本(3.0以前)中可以為 getURL 的 url 參數設定任意路徑的文件,遠程攻擊者可以利用這個漏洞讀取系統本地或遠程文件,泄露敏感信息。不過 IE6 SP1 對從 Internet 域讀取本地文件內容做了限制,因此 IE6 SP1 不存在此問題,也可以通過下載 Adobe SVG Viewer3.01 版本來彌補這個漏洞。彌補漏洞后只可以為 getURL()設定 SVG 文件所在 URI 域的文件路徑。
4.2 XML 轉換函數
函數名稱:String printNode(Node)
支持環境:Adobe SVG Viewer3.0
用途:參數中的node節點解析為字符串。
函數名稱:Node parseXML(String ,document)
支持環境:Adobe SVG Viewer3.0;Batik1.5.1
用途:將字符串解析成一個節點對象。
這一對函數用于進行字符串和DOM節點之間的轉換。我們可以使用printNode()序列化指定節點用于將當前SVG文檔中的Node元素生成字符串用于保存為文本文件或提交給遠程服務器。相反的我們可以通過parseXML()將一個字符串用指定的Document解析為一個Node對象,為parseXML()配置的document參數用于指定解析Node對象的Document;在Adobo SVG Viewer環境下可以不指定document對象,系統會默認用當前SVG文檔的Document對象解析字符串。
表7: 將字符串編譯成SVG節點并添加到當前SVG文檔
| function parseAndAddData (string) {var node = parseXML(string, document);document.documentElement.appendChild(node);} |
4.3 在Adobo SVG Viewer中重構菜單
Adobe SVG Viewer3.0為瀏覽用戶提供了單擊鼠標右鍵彈出的菜單用于實現常用的瀏覽操作功能,在實際應用中我們有時會需要定義自己的鼠標右鍵菜單的語言或裁減相應的菜單功能。Adobe SVG Viewer3.0的腳本環境中提供了一個環境變量"contextMenu"。 變量contextMenu是一個document對象,我們可以通過重新定義contextMenu文檔對象的節點內容來重構菜單的內容和樣式,并在設定菜單條目顯示的文字時通過"&*"來定義相應條目的快捷鍵。
表8: 根據系統默認語言動態加載不同的菜單文檔
| function setMenuLanguage(){if(top.navigator.userLanguage=="zh-cn"){}else if(top.navigator.userLanguage=="zh-tw"){getURL("menuitem_zh-tw.xml", fileLoaded);}else{getURL("menuitem_en-us.xml", fileLoaded);}}function fileLoaded (data) {var msg = '';if(data.success) {var newMenuRoot=parseXML(data.content,contextMenu);contextMenu.replaceChild(newMenuRoot,contextMenu.getDocumentElement());}} |
下面是針對英文操作系統配置的菜單定義文件,讀著可以根據模板定義其他語言的菜單。
表9: 使用英文的菜單文件"menuitem_en-us.xml"
| <?xml version="1.0" encoding="UTF-8"?><menu id="myCustomMenu"><header>Adobe SVG Viewer</header><item action="Open" id="Open">Open</item><item action="OpenNew" id="OpenNew">Open in New Window</item><separator/><item action="ZoomIn" id="ZoomIn">Zoom In^_^&E</item><item action="ZoomOut" id="ZoomOut">Zoom Out</item><item action="OriginalView" id="OriginalView">Original View</item><separator/><item action="Quality" id="Quality">Higher Quality</item><item action="Pause" id="Pause">Pause</item><item action="Mute" id="Mute">Mute</item><separator/><item action="Find" id="Find">Find...</item><item action="FindAgain" id="FindAgain">Find Again</item><separator/><item action="Copy" id="Copy">Copy Selected Text</item><item action="CopySVG" id="CopySVG">Copy SVG</item><item action="ViewSVG" id="ViewSVG">View SVG</item><item action="ViewSource" id="ViewSource">View Source</item><item action="SaveAs" id="SaveAs">Save SVG As...</item><separator/><item action="Help" id="Help">Help</item><item action="About" id="About">About Adobe SVG Viewer...</item></menu> |
圖 2. 重構后的菜單
|
5. 在Batik 下通過 java DOM 實現 SVG 文檔操作
在 Batik 環境下還可以通過 Java 環境下的 DOM 接口直接操作當前視圖使用的 SVG 文檔。我們可以通過Batik 提供的 org.apache.batik.swing.JSVGCanvas 對象獲取當前顯示 SVG 文件的 DOM 文檔對象引用,通過對該 DOM 的操作改變當前 SVG 圖像的內容:
| JSVGCanvas svgCanvas = new JSVGCanvas();svgCanvas.setURI("dom.svg");SVGDocument svgDocument=svgCanvas.getSVGDocument();SVGSVGElement svgRoot=svgDocument.getRootElement() ;Element g5=svgDocument.getElementById("g5");g5.setAttribute("transform","translate(225, 250) ");Element shape=svgDocument.createElementNS("http://www.w3.org/2000/svg","circle");shape.setAttribute("cx", "100");shape.setAttribute("cy", "100");shape.setAttribute("r", "20");shape.setAttribute("style", "fill: green");g5.appendChild(shape); |
需要重點提出來的是,在 Batik 中添加新的節點的時候,一定要指明添加節點的命名空間。另外需要特別注意的是,在 Batik 的 java 編程環境中,不支持對樣式如"shape.getStyle(). setProperty("stroke","red");"這樣的屬性設置,必須通過對 style 屬性的一次性賦值來設定元素的樣式。
你可以從參考資料中獲取 Batik 的詳細定義文檔。
參考資料
- 有關 SVG 的背景知識,請閱讀 developerWorks 上的教程,"可伸縮向量圖形介紹"
- 可以參考教程交互式動態可伸縮向量圖形
- 通過 http://www.w3.org/TR/SVG11/ 獲取當前最新 SVG 標準文檔
- Batik 項目介紹 http://xml.apache.org/batik/
- 可以從http://www.adobe.com/svg/indepth/pdfs/CurrentSupport.pdf獲取 Adobo SVG Viewer3.0的技術細節
- 可以通過http://www.w3.org/TR/DOM-Level-2-Core了解 DOM 對象的細節
- 通過Sacré SVG你可以找到最近關于 SVG 的文章和新聞。
關于作者
| ? | 陳珂 chenke@snmobile.com ,從業以來一直從事政府信息化建設和GIS的相關工作,自從多年前接觸SVG以來就對其產生極大的興趣。現在南京安元科技擔任技術總監。您可以通過這里訪問我創建的基于SVG的GIS開源項目,我把它叫做AntGIS(小而強大)。 | |
原文 :http://www-128.ibm.com/developerworks/cn/xml/x-svgscript/
轉載于:https://www.cnblogs.com/cuihongyu3503319/archive/2007/09/30/911458.html
總結
以上是生活随笔為你收集整理的使用脚本动态操作 SVG 文档的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 11月16日站立会议
- 下一篇: 给cad文件加密的软件,CAD文件加密软