带有脚本的SVG <symbol>会更改所有<use>克隆

时间:2018-12-08 16:49:08

标签: svg scripting

我对此完全没有经验,所以要谦虚;-)。

我正在尝试制作类似Inkscape中的动画图标集的东西。 为了向矩形“符号”添加行为,我向其中添加了一些Javascript。到目前为止,一切都很好。如果我在'use'标签的帮助下克隆'symbol'并将其悬停在矩形上,它会像应该的那样改变颜色。

这是问题所在:如果我用'use'标签创建了第二个克隆,那么当我将鼠标悬停在另一个上时,两个副本的颜色都会改变。

那不是我想要的。我希望'use1'独立于'use2'来改变颜色。同时,我希望脚本成为'symbol'标签的一部分,而不是'use'标签的一部分。

示例代码(未成功):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="210mm"
   height="297mm"
   viewBox="0 0 210 297"
   version="1.1"
   id="svg8"
   inkscape:version="0.92.1 r15371"
   sodipodi:docname="rectangle.svg">
  <script
     type="text/javascript"
     href="svg.js"
     id="script5609" />
  <defs
     id="defs2">
    <symbol
       id="symbol7630"
       onmouseover="console.log(evt.target)"
       onmouseout="evt.target.style.fill='blue'">
      <rect
         style="fill:#ff0000;stroke:#00fb00;stroke-width:3.16499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="BigRect"
         width="57.452377"
         height="36.285713"
         x="61.988094"
         y="47.535706" />
      <rect
         style="fill:#ff0000;stroke:#00fb00;stroke-width:3.16499996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         id="SmallRect"
         width="21.166666"
         height="35.529762"
         x="143.63095"
         y="45.267857" />
      <script
         type="text/javascript"
         id="script5613"><![CDATA[
         var element = SVG.get('SmallRect')
         element.style('fill', 'yellow')
         element.click(function() {
           this.style('fill', 'green')
         })
         element.mouseover(function() {
           this.style('fill', 'red')
         })
         element.mouseout(function() {
           this.style('fill', 'blue')
         })
         //element.attr('fill', '#c06')
         //element.fill('#c06')
         //element.stroke(
         ]]></script>
    </symbol>
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.75722503"
     inkscape:cx="104.33497"
     inkscape:cy="561.25984"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="1920"
     inkscape:window-height="1017"
     inkscape:window-x="-8"
     inkscape:window-y="-8"
     inkscape:window-maximized="1" />
  <metadata
     id="metadata5">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <!-- Specify the place where the animation library svg.js can be found -->
    <use
       xlink:href="#symbol7630"
       id="use16221"
       transform="translate(-15.72353,1.3976471)"
       x="0"
       y="0"
       width="100%"
       height="100%" />
    <use
       id="use3984"
       xlink:href="#symbol7630"
       x="0"
       y="0"
       width="100%"
       height="100%"
       transform="translate(-20.449326,79.41301)" />
    <use
       id="use4008"
       xlink:href="#symbol7630"
       x="0"
       y="0"
       width="100%"
       height="100%"
       transform="translate(-37.570503,138.11419)" />
  </g>
</svg>

1 个答案:

答案 0 :(得分:1)

您的示例代码并不理想,因为您尝试做的某些事情或某些事情只能用不带Javascript的CSS来实现,而其他一些事情可以用SMIL动画更优雅地完成(再次避免使用Javascript,但目前付出了一些代价浏览器兼容性问题)。但是由于您的问题始于尝试编写脚本,所以我将从那里开始。

不管做什么,保持快速的一件事是,将与该符号的所有实例同步执行与<symbol>关联的脚本。同样困难的规则是,为符号成员元素设置的样式将应用于该元素的所有实例。

但是第二条规则在边缘有一些缝隙:您不需要在style属性中设置style属性,但是CSS级联提供了以下机会:a)一次性为所有适合选择器的元素设置属性; b)从其父级继承属性。这就是窍门:如果您通过一个<symbol>元素引用一个<use>,该实例将从单个<use>元素继承样式属性。

您必须做的第一件事是从fill属性中删除style属性。这样,其值可以从父级<use>继承。然后,选择样式表中的所有<use>元素,并在其中定义一个fill。我正在将这种图案用于大矩形。

请注意:如果定义<style>元素,Inkscape会将其内容分发到目标元素并将其添加到本地style属性中。这违背了CSS级联的目的,并且会破坏我在这里描述的内容。 Inkscape是一个不错的设计器工具,但是在为网络编程时不要依赖它!

这个小矩形具有仅CSS的解决方案,用于在悬停时更改颜色:如果将鼠标悬停在<use>元素上,则其自身的属性会更改,并且属性值会向下继承。您可以设置一个use:hover {fill: red}规则,但这会使所有没有更具体规则的元素变为红色。相反,我设置了property variable --small-rect-fill: red,并用fill:var(--small-rect-fill)的小矩形进行引用。您可以根据需要定义任意多个变量。

对于脚本编写,您必须遵循相同的基本路径:更改<use>元素上的属性以使其能够被继承。无法直接定位内部的符号实例(此“影子树”的成员是只读的)。这里的问题是,您需要一个脚本,该脚本由每个<use>元素上的事件触发,并且可以区分它们。有两种可能的解决方案。优雅的事件委托,我只去hint at,然后讲第二个简单的事:首先定义您的侦听器函数,然后将其附加到每个目标元素。

作为一个抽象问题(为了避免出现兼容性问题,事实证明),我不是直接在<use>元素上设置样式,而是添加/删除一个类,该类会更改属性变量。

我希望能涵盖您所想到的用例。

var elements = document.querySelectorAll('use');

var onclick = function (event) {
    event.target.classList.add('clicked');
};

var onmouseout = function (event) {
    event.target.classList.remove('clicked');
};

elements.forEach(function (el) {
    el.addEventListener('click', onclick);
    el.addEventListener('mouseout', onmouseout);
});
rect {
    stroke: #00fb00;
    stroke-width: 3.165;
}
use {
    fill: red;
    --small-rect-fill: yellow;
}
use:hover {
    --small-rect-fill: red;
}
use.clicked {
    --small-rect-fill: green;
}
<svg
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   viewBox="0 0 210 297">
  <defs>
    <symbol id="symbol7630">
      <rect
         id="BigRect"
         width="57.452377"
         height="36.285713"
         x="61.988094"
         y="47.535706" />
      <rect
         style="fill:var(--small-rect-fill)"
         id="SmallRect"
         width="21.166666"
         height="35.529762"
         x="143.63095"
         y="45.267857" />
    </symbol>
  </defs>
  <g>
    <use
       xlink:href="#symbol7630"
       transform="translate(-15.72353,1.3976471)" />
    <use
       xlink:href="#symbol7630"
       transform="translate(-20.449326,79.41301)" />
    <use
       xlink:href="#symbol7630"
       transform="translate(-37.570503,138.11419)" />
  </g>
</svg>