Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I am a total newbie trying to make an interactive SVG - preferably without any external scripting. The effect that I am aiming for is to have one SVG element act as an interactive toggle to make another element appear and disappear.

Please find below a simple version where the text "Toggle" acts as the toggle. On click, this will animate the opacity attribute of the rectangle from 0 to 1 making it appear.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns="http://www.w3.org/2000/svg"
   width="47.652294mm"
   height="10.096307mm"
   viewBox="0 0 47.652294 10.096307"
   version="1.1"
   id="svg8">
  <style
     id="style861"></style>
     id="defs2" />
     id="layer1"
     transform="translate(-29.085516,-61.315985)">
       fill="#ff0000"
       opacity="0"
       id="rect"
       width="9.8317242"
       height="9.8317242"
       x="66.773796"
       y="61.448277">
       <animate attributeName="opacity" fill="freeze" from="0" to="1" dur="2s" begin="toggletext.click" />
     </rect>
       xml:space="preserve"
       style="font-size:8.46667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;stroke-width:0.264583"
       x="29.085516"
       y="68.002762"
       id="toggletext"><tspan
         id="tspan857"
         x="29.085516"
         y="68.002762"
         style="stroke-width:0.264583">Toggle</tspan></text>

Any subsequent click will now just simply repeat that same animation. But I want any second (fourth, sixth, etc.) click to reverse the animation (i.e. make the rectangle disappear). In other words to truly act as a toggle.

Any advice on how to achieve this effect with as little code and/or invisible elements as possible would be greatly appreciated. Thanks!

Thanks for your comment @Robert. From my google search I indeed could only find solutions with either invisible elements or extra code but I wasn't sure if I had missed some new and/or easy possibility. – RvV Feb 14, 2021 at 11:04

This is how I would do it: I'm using 2 overlapped text elements and I'm setting the pointer events to all or none on click: This way you'll click once on one text and next on the other.

The rect has 2 animate elements: one animation will start when you click on the first text, the second animation will start when clicking on the second text.

text{font-size:8.46667px;line-height:1.25;font-family:sans-serif;stroke-width:0.264583;}
<svg width="300" viewBox="0 0 47.652294 10.096307" id="svg8">
  <g id="layer1" transform="translate(-29.085516,-61.315985)">
    <rect fill="#ff0000" opacity="0" id="rect" width="9.8317242" height="9.8317242" x="66.773796" y="61.448277">
      <animate attributeName="opacity" fill="freeze" from="0" to="1" dur="2s" begin="toggletext1.click" />
      <animate attributeName="opacity" fill="freeze" from="1" to="0" dur="2s" begin="toggletext2.click" />
    </rect>
    <text x="29.085516" y="68.002762" id="toggletext2">
      <tspan x="29.085516" y="68.002762">Toggle</tspan>
      <set attributeName="pointer-events" from="none" to="all" begin="toggletext1.click" />
      <set attributeName="pointer-events" from="all" to="none" begin=".click" />
    </text>
    <text x="29.085516" y="68.002762" id="toggletext1">
      <tspan x="29.085516" y="68.002762">Toggle</tspan>
      <set attributeName="pointer-events" from="none" to="all" begin="toggletext2.click" />
      <set attributeName="pointer-events" from="all" to="none" begin=".click" />
    </text>
                Thanks @enxaneta this is indeed very helpful. I now understand it is impossible to achieve the desired result without some workaround & in my opinion your solution with toggling pointer-events of two elements seems the most straightforward.
– RvV
                Feb 14, 2021 at 11:34

Add a second rectangle fade animation

<animate  id="hide" attributeName="opacity" fill="freeze" 
  from="1" to="0" dur="2s" begin="indefinite" /> 

And add a JS trigger that toggles the animation of the appearance and disappearance of the rectangle

var svg_1 = document.getElementById("svg8"),
  hide = document.getElementById("hide"),
  visable = document.getElementById("visable");
let flag = true;
svg_1.addEventListener('click', function() {
  if (flag == true) {
    visable.beginElement();
    flag = false;
  } else {
    hide.beginElement();
    flag = true;
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns="http://www.w3.org/2000/svg"
   width="47.652294mm"
   height="10.096307mm"
   viewBox="0 0 47.652294 10.096307"
   version="1.1"
   id="svg8">
  <style
     id="style861"></style>
     id="defs2" />
     id="layer1"
     transform="translate(-29.085516,-61.315985)">
       fill="#ff0000"
       opacity="0"
       id="rect"
       width="9.8317242"
       height="9.8317242"
       x="66.773796"
       y="61.448277">
       <animate id="visable" attributeName="opacity" fill="freeze" from="0" to="1" dur="2s" begin="indefinite" /> 
         <animate  id="hide" attributeName="opacity" fill="freeze" from="1" to="0" dur="2s" begin="indefinite" />
     </rect>
       xml:space="preserve"
       style="font-size:8.46667px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;stroke-width:0.264583"
       x="29.085516"
       y="68.002762"
       id="toggletext"><tspan
         id="tspan857"
         x="29.085516"
         y="68.002762"
         style="stroke-width:0.264583">Toggle</tspan></text>

Although I like the No-JavaScript pointer-events method; this is how I would do it:

When OP says: preferably without any external scripting.
I presume he means no 3rd party libraries.

So I would use a native JavaScript Web Component (JSWC) <svg-toggle> (supported in all modern browsers)
that creates the SVG for any number of toggles you want

To toggle animation:

  • switch the from and to parameters on every click
  • restart the animation
  • <style>
      svg {
        display: inline-block; width: 30%; vertical-align: top;
        cursor: pointer; background: teal; color: white;
    </style>
    <svg-toggle></svg-toggle>
    <svg-toggle color="yellow"></svg-toggle>
    <svg-toggle color="blue" label="Blue" duration=".5"></svg-toggle>
    <script>
    customElements.define('svg-toggle', class extends HTMLElement {
      connectedCallback() {
        this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 18">
        <text x="2" y="12" font-size="10px" fill="currentColor">
              ${this.getAttribute("label")||'Toggle'}</text>  
          <rect fill="${this.getAttribute("color")||'red'}" x='33' y="3" width="12" height="12">
            <animate attributeName="opacity" dur="${this.getAttribute("duration")||2}s" from="0" to="1" fill="freeze" begin="indefinite"/> 
          </rect></svg>`;
        this.animate = this.querySelector("animate");
        this.onclick = (evt) => this.toggle();
      toggle( // method, so can be called from Javascript
        from = this.animate.getAttribute("from"), // optional from/to parameters
        to   = this.animate.getAttribute("to"),
        this.animate.setAttribute( "from", to   );
        this.animate.setAttribute( "to"  , from );
        this.animate.beginElement();
    </script>

    I don't want modern W3C standard Web Components mumbo jumbo...

    Then stick the JavaScript on every SVG:

    onclick="{ let a = this.querySelector('animate'); let from = a.getAttribute('from'); a.setAttribute('from',a.getAttribute('to')); a.setAttribute('to',from); a.beginElement(); xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 18"> <text x="2" y="12" font-size="10px" fill="currentColor">Gold</text> <rect fill="gold" x="33" y="3" width="12" height="12"> <animate attributeName="opacity" dur=".3s" from="0" to="1" fill="freeze" begin="indefinite"></animate> </rect>

    I don't want JavaScript

    See Enxaneta his pointer-events answer

    Thanks @Danny for this nice solution. It is not exactly what I am looking for (I ultimately want multiple toggles within the same SVG) so I will go for Enxaneta's anwer. But I am sure this will help others. – RvV Feb 14, 2021 at 11:30

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.