SVG with script changes all the clo

2019-08-16 17:05发布

I'm fairly unexperienced with this, so be gentle ;-).

I'm trying to make something like an animated iconset in Inkscape. To add behaviour to a rectangle 'symbol' I added some Javascript to it. So far so good. If I clone the 'symbol' with help of the 'use' tag and hover over the rectange it changes color just like it should.

Here's the problem: If I create a second clone with the 'use' tag, both copies change color if I hover over one or the other.

That is not what I want. I want 'use1' to change color independent of the 'use2'. At the same time I want the script to be part of the 'symbol' tag, not of the 'use' tag.

Sample code (no success):

<?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>

标签: svg scripting
1条回答
成全新的幸福
2楼-- · 2019-08-16 18:00

Your example code isn't ideal because some or the things you are attempting to do can be achieved with only CSS without Javascript, others could be done more elegantly with SMIL animations (avoiding Javascript again, but currently at the price of some browser compatibility issues). But since your question started with trying to script things, I'll go from there.

One thing that holds fast, whatever you do, is that a script that is associated with a <symbol>, will be executed for all instances of that symbol synchrouously. An equally hard rule is that a style set for a symbol member element will apply to all instances of that element.

But the second rule has some cracks around the edges: You do not need to set a style property in a style attribute, but the CSS cascades offer oportunities to a) set properties for all elements fitting a selector at once and b) inherit the property from its parent. And here is the trick: if you reference a <symbol> wiht a <use> element, the instance inherits the style properties from the individual <use> element.

The first thing you must do is remove the fill property from the style attribute. This way, its value can be inherited from the parent <use>. Then, you select all <use> elements in a style sheet and define a fill there. I'm using this pattern for the big rectangle.

A word of caution: If you define <style> elements, Inkscape will distribute their content to the targeted elements and add them in local style attributes. That goes against the very purpose of the CSS cascade and will break what I am describing here. Inkscape is a nice designer tool, but do not depend on it it when programming for the web!

The small rectangle features a CSS-only solution for changing color on hover: if you hover over the <use> element, its own property changes and the property value inherits down. You could set a use:hover {fill: red} rule, but that would make all elements without a more specific rule turn red. Instead, I am setting a property variable --small-rect-fill: red and reference this for the small rectangle fill with fill:var(--small-rect-fill). You can define as many variables as you need.

For scripting, you have to follow the same basic path: change properties on the <use> element to let them be inherited. A direct targeting of the symbol instances inside is impossible (members of this "shadow tree" are read-only). The problem here is that you need a script that is triggered by events on each <use> element and that can distinguish between them. Therer are two possilbe patterns to solve that. The elegant one, event delegation, I'm only going to hint at and go with the second, easier one: define your listener function once, and then attach it to every target element.

As a matter of abstraction (and to avoid some compatibility issues, as it turns out), I am not setting the style directly on the <use> element, but add/remove a class that changes the used value for the property variable.

I hope that covers the use cases you have in mind.

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>

查看更多
登录 后发表回答