JavaScript操作SVG的一些知识

最近有个学生遇到了svg的问题,正好在这里整理一下关于svg的问题!

获取SVGDocument

当使用JavaScript在页面上对HTML进行操作的使用,一个非常重要的对象就是document了。无论是getElementByIdgetElementsByTagName,异或是createElement,它们都是document对象上的方法。而且所有其它任何DOM对象都被包含在该对象之内。
一般而言,一个HTML文件,或者说一个网页都对应一个document对象,所以如果SVG是直接嵌套在HTML的内容中的话,它们就会共用一个document对象,因此可以直接通过该对象来获取到SVG元素对象。
比如下边的代表,在浏览器上打开,就会看到一个蓝色的圈而非绿色的圈,因为JavaScript通过document获得了circle对象,并重新设置了其fill属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
     version="1.1" width="20" height="20">
    <circle id="c" cx="10" cy="10" r="7" fill="green"/>
</svg>
<script type="text/javascript">
    var c = document.getElementById('c');
    c.setAttribute('fill','blue');
</script>
</body>
</html>

不过大多时候,SVG并不会直接嵌套在HTML之中重现出来,更多的会选择将其作为图片通过img标签引进,或者当做背景图片在CSS中通过url引入。对于这种情况,SVG只是单纯的当做图片来使用而且一个XML类型的文档,所以也就无法使用JavaScript来操作它们。
还有一类方法则是通过object、embed或者iframe标签将SVG文件引入到HTML页面上呈现出来。对于该种情况, 这些标签实际上会把通过datasrc属性指定的内容相对独立的引入到页面上来,也就是其中的内容会有完全属于自己的document对象。所以使用原来的document对象就无法取得通过上述标签引入进来的SVG文档中元素,更不用说去修改上边的属性了。
好在当使用JavaScript获取到这些元素对象的时候,它们都一个方法可以获取所引用的SVG文档的document对象,那就是getSVGDocument()
 
getsvgdocument
当然这些都是需要浏览器支持才行的,对于目前主流最新浏览器来说这些都是标配的方法。如果使用object或iframe引入SVG文档,除了getSVGDocument(),还可以使用contentDocument属性来获取其引入文档对应的document对象。区别在于如果是引入的不是SVG文件,而是XML或者HTML等等,contentDocuement依然会返回对象,而getSVGDocument()则返回null
contentdocument
获取了SVG的document之后,就可以像往常那样获取内部元素属性、绑定事件等等。还可以定义一个document为参数的方法形成局部变量,要对某个引入SVG文档进行操作时就获取该文档的document对象传入,想获取主文档的对象时就使用window.document即可。

function setup (document) {
// do something with svg docuemnt
}
 
setup(document.getElementById('svg-embed').getSVGDocument());

当然了使用上边一系列属性和方法都有一个大前提:要满足同源策略(Same-origin policy)。若引入的SVG文档是来自于其它站点的,那么浏览器就会禁止获取document对象。
blocked_accessing

操作SVG的元素

SVG作为XML的方言,不同于HTML松散的标签结构和格式,它严格遵循XML的语法格式,所以开始标签都要有对应的结束标签,所有标签都要被svg标签包含在内。另外在HTML经常被忽略的一个知识就是:XML是有命名空间(namespace)的。命名空间在通过JavaScript创建SVG元素对象的时候就引起了一些麻烦。
一般的在HTML中若想通过JavaScript创建一个元素对象的话,代码类似如下:

var inp = document.createElement('input');
inp.type = 'button';
inp.value = 'button';
inp.name = 'button';
 
var con = document.getElementById('container');
con.appendChild(inp);

但是使用相同的方法,创建SVG元素并添加到SVG文档中的话, 该元素并不会呈现出来。

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     version="1.1" width="20" height="20">
    <script type="text/javascript">
        var c = document.createElement('circle');
        c.cx = 10;
        c.cy = 10;
        c.r = 7;
        c.fill = 'green';
        document.rootElement.appendChild(c);
    </script>
</svg>

这是因为创建SVG元素需要指定命名空间,就像需要在svg标签上设定xmlns为http://www.w3.org/2000/svg。正确的构造方式是调用createElentNS()方法,并将”http://www.w3.org/2000/svg”作为第一参数传入。
此外,不同于HTML元素对象可以直接对一些属性赋值,SVG元素对象都需要通过调用setAttribute()方法来设定属性值。因为大部分属性都是SVGAnimatedLength类型,即使要通过属性赋值也要写成类似c.r.baseVal.value = 7,多层访问其下属性。不过像fillstroke等默认都是undefined,所以使用setAttribute()是更好的选择。
下述代码就可以在页面上呈现是一个半径为7px的绿色的圆点。

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     version="1.1" width="20" height="20">
    <script type="text/javascript">
        var c = document.createElementNS('http://www.w3.org/2000/svg','circle');
        c.setAttribute('cx', 10);
        c.setAttribute('cy', 10);
        c.r.baseVal.value = 7;
        c.setAttribute('fill', 'green');
        document.rootElement.appendChild(c);
    </script>
</svg>

除了元素有命名空间,有些属性也有其特定的命名空间。比如在HTML极为常用的a标签的,在SVG中又有存在,但是对于其href属性,在SVG之中就必须加上xmlns:前缀来指定其命名空间了。对于设置这些属性也需要用setAttributeNS()方法并将”http://www.w3.org/1999/xlink“作为第一参数传入。

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
     version="1.1" width="20" height="20">
    <script type="text/javascript">
        var a = document.createElementNS('http://www.w3.org/2000/svg','a');
        a.setAttributeNS('http://www.w3.org/1999/xlink',
                'xlink:href',
                'http://blog.iderzheng.com/');
        var c = document.createElementNS('http://www.w3.org/2000/svg','circle');
        c.setAttribute('cx', 10);
        c.setAttribute('cy', 10);
        c.r.baseVal.value = 7;
        c.setAttribute('fill', 'green');
        a.appendChild(c);
        document.rootElement.appendChild(a);
    </script>
</svg>

现在可以通过点击绿点进入到博客主页了。
不仅是a标签,对于其它标签,例如:use、image,若要设置xlink:href属性时都应使用命名空间的方式,否则就不会起作用。对于其它该命名空间下的属性也是如此,例如xlink:titlexlink:show。这里就不一一列举,具体关于xlink的可参看此处
如果你碰到了其它在JavaScript中因XML命名空间引起的问题,也欢迎留言补充。

SVG与窗口坐标系的转换

SVG比HTML的一大优势在于前者支持平移、缩放、切变等变换(Transform)。虽然现在CSS3也支持这些变换,但是毕竟SVG是向量图,它在缩放的时候不会丢失像素。而且SVG可以通过use标签设置tranform属性来进行快速复用。而HTML的标签就没有这样的“复用性”,每个在页面上呈现出来的内容都严格对应一个标签元素。
不仅是transform属性可以变化坐标,一些元素例如svg,也可以通过设定viewBox来改变自身的坐标系以影响呈现的内容。这就引发了一些问题坐标系转换的问题:比如鼠标点击在页面上的时候获取到的是基于窗口坐标系以像素为单位的位置,如何转变到SVG的坐标系的点以确定被点击的对象或者进行其它操作呢?
一种方法可以是自己记录下窗口和SVG图的比例,然后根据比例进行转换。这可能可以一定程度地解决平移和缩放带来的变换问题,但是对于旋转就无能为力了。而且当窗口进行了滚动或者拖拽,都需要重新计算这些比例。
其实在每个SVG元素对象上,都有一个getScreenCTM()的方法,它会返回一个SVGMatrix来表示元素的坐标系所做过的变换。此外SVG还有一种SVGPoint类型,它有x和y两个属性可以表示任一一个点,同时它还有一个matrixTransform()方法可以将点跟某个SVGMatrix相乘得到相应矩阵变换后的点。通过这些再加上一些线性代数的知识,就可以轻松的进行坐标系的变换了。
要注意的是SVGPoint只能通过svg元素对象的createSVGPoint()来创建,不能用new SVGPoint()这样的方式。

function click(e) {
// rootElement is specific to SVG document
// documentElemnt is to any XML document include HTML
// they both can retrieve the root element of a document
var r = document.rootElement || document.documentElement,
pt = r.createSVGPoint(),
im = r.getScreenCTM().inverse(); // inverse of tranforma matrix
 
// set point with window coordination
pt.x = e.clientX;
pt.y = e.clientY;
 
// convert point to SVG coordination
var p = pt.matrixTransform(im);{
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注