0

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>
Bigman74066
  • 408
  • 4
  • 12
  • 1
    That is only to be expected. If you add a behaviour to the ``, it remains a part of the template and will be applied equally to all `` clones. To get individual behavior of each clone, the behavior needs to be attached to the individual clone. To help you with your problem, we'll need a much more detailed description of what you are trying to achieve and what you have tried so far, ideally in a [mcve]. – ccprog Dec 08 '18 at 19:37
  • Hello ccprog, thank you very much for your answer. "To get individual behavior of each clone, the behavior needs to be attached to the individual clone." --> that is what I'm trying to achieve, but the scipt must reside in the "symbol". I do not have a sample because I don't know how to do it. The idea is to have a library of SVG's with script that acts on the individual instances of the symbol instead of the symbol itself. – Bigman74066 Dec 09 '18 at 14:39
  • I added the code I made so far... It acts on the , not on the . This is an SVG made with Inkscape. I added the – Bigman74066 Dec 09 '18 at 14:58
  • I could have a script in the checking for in the DOM and then copy the script to all the but that would be kinda ugly... and I don;t know if I could pull it off... – Bigman74066 Dec 09 '18 at 15:10

1 Answers1

1

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>
ccprog
  • 20,308
  • 4
  • 27
  • 44
  • Hi ccprog, Thank you very much for your elaborate answer. I will have to chew on it for a while because it's all pretty new to me, but I see some possiblitities. Petty that it's not as streight forward as I had in mind. I'm planning on using Inkscape to build a Human Machine Interface, so that is why I need the scripting (the SVG's should reflect the status of a machine) – Bigman74066 Dec 10 '18 at 11:42
  • My effords to get this working have not lead to anything usefull yet... What I'm trying to achieve is a self-contained unit of code and SVG all stuffed into a . It could represent a small walking puppet or something similar. Then, if I wanted 100 walking puppets I would just them to create 100 instances of walking puppets, animated icons or whatever... – Bigman74066 Jan 31 '19 at 20:08
  • I tried build a script to replace the tags with the original SVG and JS contained in the tag but I find myself struggeling to keep all id's unique. It would be nice to reference an SVG element by using something like "self.element1" or "self.element2", but I cannot find an equivalent for "self" in HTML5. – Bigman74066 Jan 31 '19 at 20:23