Play new css animation seamlessly

Viewed 76

I created a simple HTML game, which disappears under the screen when I click on a moving box. However, the animation that disappears starts at the original location, not where it was clicked.

I think 0% of the remove @keyframes should have the location of the click, but I couldn't find a way

How shall I do it?

  (function () {
    const charactersGroup = document.querySelectorAll('.character');
    const stage = document.querySelector('.stage')
    const clickHandler = (e) => {
      const target = e.target;
      if (target.classList.contains('character')) {
        target.classList.remove(`f${target.dataset.id}`);
        target.classList.add('f0');
        target.classList.add('remove');

        setTimeout(() => { stage.removeChild(target) }, 2000);
      }
    }
    stage.addEventListener('click', clickHandler);
  }());
.stage {
      overflow: hidden;
      position: relative;
      background: #eeeeaa;
      width: 40vw;
      height: 20vw;
    }

    @keyframes moving {
      0% {
        transform: translateX(0);
      }
      100% {
        transform: translateX(30vw);
      }
    }
    @keyframes remove {
      0% {
        transform: translate(0);
      }
      100% {
        transform: translateY(60vw);
      }
    }
    .character {
      position: absolute;
      width: 50px;
      height: 50px;
      background-repeat: no-repeat;
      background-position: 50% 50%;
      background-size: contain;
      animation: moving infinite alternate;
    }
    .remove {
      animation: remove 0.2s cubic-bezier(.68,-0.55,.27,1.55) forwards;
    }
    .f0 {
      background-color: black;
      animation-duration: 2s;
    }
    .f1 {
      left: 5%;
      bottom: 5%;
      animation-duration: 2s;
      background-color: red;
    }
<div class="stage">
  <div class="character f1" data-id="1"></div>
</div>

2 Answers

If your use case is to deal with a lot of such boxes and complexity, it's better to go with handling everything with pure JS but I tried to make this work with minimal changes in JS and CSS.

I have added comments to the new JS lines.

Also taken the liberty to have a separate class with name moving for animation moving so that we can remove it on click.

(function () {
    const charactersGroup = document.querySelectorAll('.character');
    const stage = document.querySelector('.stage')
    const clickHandler = (e) => {
      const target = e.target;
      if (target.classList.contains('character')) {
        target.classList.remove(`f${target.dataset.id}`);
        target.classList.add('f0');
        // remove the moving animation
        target.classList.remove('moving');
        // Get offsetWidth which is the half of width to substract later while calculating left for the target i.e our box.
        const offsetWidth  = parseInt(getComputedStyle(target).width)/2;
        // e.clientX gives us the x coordinate of the mouse pointer
        // target.getBoundingClientRect().left gives us left position of the bounding rectangle and acts as a good offset to get the accurate left for our box.
        target.style.left = `${e.clientX -target.getBoundingClientRect().left - offsetWidth}px`;
       target.classList.add('remove');
     setTimeout(() => { stage.removeChild(target) }, 2000);
      } 
    }
    stage.addEventListener('click', clickHandler);
  }());
.stage {
      overflow: hidden;
      position: relative;
      background: #eeeeaa;
      width: 40vw;
      height: 20vw;
    }

    @keyframes moving {
      0% {
        transform: translateX(0);
      }
      100% {
        transform: translateX(30vw);
      }
    }
    @keyframes remove {
      0% {
        transform: translate(0vh);
      }
      100% {
        transform: translateY(60vw);
      }
    }
    .character {
      position: absolute;
      width: 50px;
      height: 50px;
      background-repeat: no-repeat;
      background-position: 50% 50%;
      background-size: contain;
    }
    
    .moving{
      animation: moving infinite alternate;
    }
    .remove {
      animation: remove 0.2s cubic-bezier(.68,-0.55,.27,1.55) forwards;
    }
    .f0 {
      background-color: black;
      animation-duration: 2s;
    }
    .f1 {
      left: 5%;
      bottom: 5%;
      animation-duration: 2s;
      background-color: red;
    }
<div class="stage">
  <div class="character moving f1" data-id="1"></div>
</div>

Change the first animation to consider left instead of translate then append both of them to the element initially and you simply toggle the animation-play-state when adding the remove class

(function() {
  const charactersGroup = document.querySelectorAll('.character');
  const stage = document.querySelector('.stage')
  const clickHandler = (e) => {
    const target = e.target;
    if (target.classList.contains('character')) {
      target.classList.add('remove');

      setTimeout(() => {
        stage.removeChild(target)
      }, 2000);
    }
  }
  stage.addEventListener('click', clickHandler);
}());
.stage {
  overflow: hidden;
  position: relative;
  background: #eeeeaa;
  width: 40vw;
  height: 20vw;
}

@keyframes moving {
  100% {
    left:calc(95% - 50px);
  }
}

@keyframes remove {
  50% {
    transform: translateY(-30vh);
  }
  100% {
    transform: translateY(60vw);
  }
}

.character {
  position: absolute;
  width: 50px;
  height: 50px;
  background:red;
  left: 5%;
  bottom: 5%;
  animation: 
     moving 2s infinite alternate,
     remove 1s cubic-bezier(.68, -0.55, .27, 1.55) forwards paused;
}

.remove {
  animation-play-state:paused,running;
  background: black;
}
<div class="stage">
  <div class="character f1" data-id="1"></div>
</div>

Related