Thijs Thijs - 6 months ago 16
CSS Question

Reverse animation of SVG elements

I'm working on an animation which will be triggered when a class is added with jQuery after a click event.

It has the be something like a toggle:


  1. After first click on button, animation 1 will be run https://codepen.io/thijs-webber/pen/dMBKRp

  2. After second click on same button reversed animation needs to be run



Is there a way to reverse the chain of animations easily?



.animation-container {
width: 250px;
position: relative;
margin: 0 auto;
margin-top: 50px;
}
.dot-pink-light {
fill: #CE97AE;
}
.dot-pink {
fill: #D82566;
}
.dot-green-light {
fill: #AFBF99;
}
.dot-green {
fill: #77BC1F;
}
/* ANIMATIONS */
@keyframes rotate_clockwise {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
}
@keyframes rotate_anticlockwise {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
}
}
@keyframes scale_down {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
100% {
-webkit-transform: scale(0.5);
transform: scale(0.5);
}
}
@keyframes pop_out_dots_top {
100% {
transform: translate(0px, -80px);
}
}
@keyframes pop_out_dots_bottom {
100% {
transform: translate(0px, 80px);
}
}
/* 1 */
.container.opened {
animation: scale_down 250ms ease;
transform-origin: 50% 50% 0;
}
/* 2 */
.container.opened .groups .extra-dot-top {
animation: pop_out_dots_top 250ms ease;
-webkit-animation-delay: 250ms;
animation-delay: 250ms;
transform-origin: 50% 50% 0;
}
.container.opened .groups .extra-dot-bottom {
animation: pop_out_dots_bottom 250ms ease;
-webkit-animation-delay: 250ms;
animation-delay: 250ms;
transform-origin: 50% 50% 0;
}
.container.opened .groups .group-2 {
animation: rotate_anticlockwise 250ms ease;
transform-origin: 50% 50% 0;
-webkit-animation-delay: 250ms;
animation-delay: 250ms;
}
/* 4 */
.container.opened .groups {
animation: rotate_clockwise 250ms ease;
transform-origin: 50% 50% 0;
-webkit-animation-delay: 500ms;
animation-delay: 500ms;
}

<div class="animation-container">
<div class="col-xs-12">
<?xml version="1.0" ?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" enable-background="new 0 0 200 200">

<!-- Group 1 -->
<g class="container opened">
<g class="groups">
<g class="group-1 pink">
<circle class="dot-pink-light extra-dot-top" cx="100" cy="21.4" r="21.4" />
<circle class="dot-pink-light extra-dot-bottom" cx="100" cy="178.6" r="21.4" />
<circle class="dot-pink" cx="100" cy="21.4" r="21.4" />
<circle class="dot-pink" cx="100" cy="100" r="21.4" />
<circle class="dot-pink" cx="100" cy="178.6" r="21.4" />
</g>

<!-- Group 2 -->
<g class="group-2 green">
<circle class="dot-green-light extra-dot-top" cx="100" cy="21.4" r="21.4" />
<circle class="dot-green-light extra-dot-bottom" cx="100" cy="178.6" r="21.4" />
<circle class="dot-green" cx="100" cy="21.4" r="21.4" />
<circle class="dot-green" cx="100" cy="100" r="21.4" />
<circle class="dot-green" cx="100" cy="178.6" r="21.4" />
</g>
</g>
</g>
</svg>
</div>
</div>




Answer

Since you are looking for a toggle effect with only two distinct states, it is much better for you to use transitions instead of animations. Transitions can automatically produce the reverse effect when the class is removed.

Demo with transitions:

window.onload = function() {
  var btn = document.querySelector('button');
  btn.addEventListener('click', function() {
    document.querySelector('.container').classList.toggle('opened');
  });
}
.animation-container {
  width: 250px;
  position: relative;
  margin: 0 auto;
  margin-top: 50px;
}
.dot-pink-light {
  fill: #CE97AE;
}
.dot-pink {
  fill: #D82566;
}
.dot-green-light {
  fill: #AFBF99;
}
.dot-green {
  fill: #77BC1F;
}
/* 1 */
.container {
  transform: scale(1);
  transition: transform 2.5s ease;
  transform-origin: 50% 50% 0;
  transition-delay: 5s;
}
.container.opened {
  transform: scale(0.5);
  transition-delay: 0s;
}

.container .groups .extra-dot-top {
  transform: translate(0px, 0px);
  transition: transform 2.5s ease;
  transition-delay: 2.5s;
  transform-origin: 50% 50% 0;
} 
.container.opened .groups .extra-dot-top {
  transform: translate(0px, -80px);
}
.container .groups .extra-dot-bottom {
  transform: translate(0px, 0px);
  transition: transform 2.5s ease;
  transition-delay: 2.5s;
  transform-origin: 50% 50% 0;
}
.container.opened .groups .extra-dot-bottom {
  transform: translate(0px, 80px);
}
.container .groups .group-2 {
  transform: rotate(0deg);
  transition: transform 2.5s ease;
  transform-origin: 50% 50% 0;
  transition-delay: 2.5s;
}
.container.opened .groups .group-2 {
  transform: rotate(-90deg);
}
/* 4 */
.container .groups {
  transform: rotate(0deg);
  transition: transform 2.5s ease;
  transform-origin: 50% 50% 0;
}
.container.opened .groups {
  transform: rotate(45deg);
  transition-delay: 5s;
}
<div class="animation-container">
  <div class="col-xs-12">
    <?xml version="1.0" ?>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" enable-background="new 0 0 200 200">

      <!-- Group 1 -->
      <g class="container">
        <g class="groups">
          <g class="group-1 pink">
            <circle class="dot-pink-light extra-dot-top" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-pink-light extra-dot-bottom" cx="100" cy="178.6" r="21.4" />
            <circle class="dot-pink" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-pink" cx="100" cy="100" r="21.4" />
            <circle class="dot-pink" cx="100" cy="178.6" r="21.4" />
          </g>

          <!-- Group 2 -->
          <g class="group-2 green">
            <circle class="dot-green-light extra-dot-top" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-green-light extra-dot-bottom" cx="100" cy="178.6" r="21.4" />
            <circle class="dot-green" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-green" cx="100" cy="100" r="21.4" />
            <circle class="dot-green" cx="100" cy="178.6" r="21.4" />
          </g>
        </g>
      </g>
    </svg>
  </div>
</div>
<button>Click</button>


Animations on the other hand cannot automatically produce the reverse effect and code needs to be written. Even when the reverse animation's code is written, getting it work is complex (and cannot be done with pure CSS) because you will have to remove the "open" animation before adding the "close" animation and when the happens the element(s) will immediately snap to their original position before executing the reverse effect. The output wouldn't be as graceful as with transitions unless you put in a lot of messy code.

Demo with animations:

window.onload = function() {
  var btn = document.querySelector('button');
  var container = document.querySelector('.container');
  var groups = document.querySelector('.groups');
  var group2 = document.querySelector('.group-2');
  var extradtG = document.querySelector('.green .extra-dot-top');
  var extradtP = document.querySelector('.pink .extra-dot-top');
  var extradbG = document.querySelector('.green .extra-dot-bottom');
  var extradbP = document.querySelector('.pink .extra-dot-bottom');
  var opened = false;
  btn.addEventListener('click', function() {
    if (opened) {
      container.style.transform = 'scale(1)';
      extradtG.style.transform = 'translate( 0px, 0px)';
      extradtP.style.transform = 'translate( 0px, 0px)';
      extradbG.style.transform = 'translate( 0px, 0px)';
      extradbP.style.transform = 'translate( 0px, 0px)';
      group2.style.transform = 'rotate(0deg);'
      groups.style.transform = 'rotate(0deg)';
      container.classList.remove('opened');
      container.clientHeight; // dummy call
      container.classList.add('closed');
    } else {
      container.style.transform = 'scale(0.5)';
      extradtG.style.transform = 'translate( 0px, -80px)';
      extradtP.style.transform = 'translate( 0px, -80px)';
      extradbG.style.transform = 'translate( 0px, 80px)';
      extradbP.style.transform = 'translate( 0px, 80px)';
      group2.style.transform = 'rotate(-90deg);'
      groups.style.transform = 'rotate(45deg)';
      container.classList.remove('closed');
      container.clientHeight; // dummy call
      container.classList.add('opened');
    }
    opened = !opened;
  });
}
.animation-container {
  width: 250px;
  position: relative;
  margin: 0 auto;
  margin-top: 50px;
}
.dot-pink-light {
  fill: #CE97AE;
}
.dot-pink {
  fill: #D82566;
}
.dot-green-light {
  fill: #AFBF99;
}
.dot-green {
  fill: #77BC1F;
}
/* ANIMATIONS */

@keyframes rotate_clockwise {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(45deg);
  }
}
@keyframes rotate_anticlockwise {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(-90deg);
  }
}
@keyframes scale_down {
  0% {
    transform: scale(1);
  }
  100% {
    transform: scale(0.5);
  }
}
@keyframes pop_out_dots_top {
  0% {
    transform: translate(0px, 0px);
  }
  100% {
    transform: translate(0px, -80px);
  }
}
@keyframes pop_out_dots_bottom {
  0% {
    transform: translate(0px, 0px);
  }
  100% {
    transform: translate(0px, 80px);
  }
}
/* 1 */

.container.opened {
  animation: scale_down 2.5s ease;
  transform-origin: 50% 50% 0;
}
.container.closed {
  animation: scale_down 2.5s ease reverse backwards;
  transform-origin: 50% 50% 0;
  animation-delay: 5s;
}
/* 2 */

.container.opened .groups .extra-dot-top {
  animation: pop_out_dots_top 2.5s ease backwards;
  animation-delay: 2.5s;
  transform-origin: 50% 50% 0;
}
.container.closed .groups .extra-dot-top {
  animation: pop_out_dots_top 2.5s ease reverse backwards;
  animation-delay: 2.5s;
  transform-origin: 50% 50% 0;
}
.container.opened .groups .extra-dot-bottom {
  animation: pop_out_dots_bottom 2.5s ease backwards;
  animation-delay: 2.5s;
  transform-origin: 50% 50% 0;
}
.container.closed .groups .extra-dot-bottom {
  animation: pop_out_dots_bottom 2.5s ease reverse backwards;
  animation-delay: 2.5s;
  transform-origin: 50% 50% 0;
}
.container.opened .groups .group-2 {
  animation: rotate_anticlockwise 2.5s ease backwards;
  transform-origin: 50% 50% 0;
  animation-delay: 2.5s;
}
.container.closed .groups .group-2 {
  animation: rotate_anticlockwise 2.5s ease reverse backwards;
  transform-origin: 50% 50% 0;
  animation-delay: 2.5s;
}
/* 4 */

.container.opened .groups {
  animation: rotate_clockwise 2.5s ease backwards;
  transform-origin: 50% 50% 0;
  animation-delay: 5s;
}
.container.closed .groups {
  animation: rotate_clockwise 2.5s ease reverse backwards;
  transform-origin: 50% 50% 0;
}
<div class="animation-container">
  <div class="col-xs-12">
    <?xml version="1.0" ?>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" enable-background="new 0 0 200 200">

      <!-- Group 1 -->
      <g class="container">
        <g class="groups">
          <g class="group-1 pink">
            <circle class="dot-pink-light extra-dot-top" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-pink-light extra-dot-bottom" cx="100" cy="178.6" r="21.4" />
            <circle class="dot-pink" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-pink" cx="100" cy="100" r="21.4" />
            <circle class="dot-pink" cx="100" cy="178.6" r="21.4" />
          </g>

          <!-- Group 2 -->
          <g class="group-2 green">
            <circle class="dot-green-light extra-dot-top" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-green-light extra-dot-bottom" cx="100" cy="178.6" r="21.4" />
            <circle class="dot-green" cx="100" cy="21.4" r="21.4" />
            <circle class="dot-green" cx="100" cy="100" r="21.4" />
            <circle class="dot-green" cx="100" cy="178.6" r="21.4" />
          </g>
        </g>
      </g>
    </svg>
  </div>
</div>
<button>Click</button>

(The animation version has a dummy call container.clientHeight which is done to make sure that there is a repaint between the removal of one animation and addition of another. Else, it would look as though nothing happened. You can find more details in my answer here.)