RvV
RvV

Reputation: 95

SVG animate reverse on second click

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"?>
<svg
  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>
  <defs
     id="defs2" />
  <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="toggletext.click" />
     </rect>
    <text
       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>
  </g>
</svg>

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!

Upvotes: 2

Views: 651

Answers (3)

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:

<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>
</svg>

I don't want JavaScript

See Enxaneta his pointer-events answer

Upvotes: 2

enxaneta
enxaneta

Reputation: 33054

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>
  </g>
</svg>

Upvotes: 2

Alexandr_TT
Alexandr_TT

Reputation: 14565

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"?>
<svg
  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>
  <defs
     id="defs2" />
  <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 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>
    <text
       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>
  </g>
</svg>

Upvotes: 2

Related Questions