0

CSS:

.dragon {
  background-image: url(images/dragon.png);
  background-position: center;
  background-size: contain;
  background-repeat: no-repeat;
  width: 180px;
  height: 120px;
  position: absolute;
  bottom: 0;
  left: 2000px;
  animation: ani-dragon 6s linear infinite;
}
@keyframes ani-dragon {
  0% {
    left: 2000px;
  }
  100% {
    left: -500px;
  }
}
.ani-dino {
  animation: ani-dino 1s linear;
}
@keyframes ani-dino {
  0% {
    bottom: 0;
  }
  50% {
    bottom: 300px;
  }
  100% {
    bottom: 0;
  }
}

Javascript

let dino = document.querySelector(".dino");
let dragonAnimationTime = parseInt(window.getComputedStyle(dragon).getPropertyValue("animation-duration"));
let dragonPosition = window.getComputedStyle(dragon);
let newAni = 6;

document.addEventListener("keydown", function (e) {
  if (e.key == "ArrowUp") {
    dino.classList.add("ani-dino");

    setInterval(() => {
      if (newAni > 2 && Math.round(parseInt(dragonPosition.left)) < -490) {
        newAni = dragonAnimationTime - 0.1;
        dragon.style.animationDuration = newAni + "s";
        dragon.style.left = "2000px";
        dragonAnimationTime = newAni;
      }
    }, 1);

    setTimeout(() => {
      dino.classList.remove("ani-dino");
    }, "1000");
  }
});

The problem is with the animation the dragon which is suppose to animate from right to left, is not working fine. As i am decreasing the animation time everytime the 'ArrowUp' is triggered and dragon successfully pass it and goes to extreme left. Animation duration is decreasing as expected and dragon animation is getting fast as it should be, but the problem comes that the dragon animation sometimes start randomly from anywhere as it should start everytime from extreme right as per my code, but in between it startes sometimes randomly while playing, from anywhere of its path, i really cant understand why it is so.

I tried whatever i can , I tried to change the durations, i tried to change length of the horizontal path of dragon, i tried both setInterval and setTimeout with different codes and logics.

But really cant understand why the animation sometimes begins from in between the path, although i am everytime putting it to the extreme right once it met conditions of decreasing the animation-duration and also i am decreasing the animation-duration at the extreme right of it(-490px), so that the current running path is not effected, still not working.

Please help if someone got this !!!

  • Good to have any demo to check out. – brynzovskii Nov 26 '22 at 20:42
  • It seems to have something to do with updating the duration while the animation is running, and produces similar artifacts in both Firefox and Edge (i.e web-kit). I'll check out possible workarounds tonight if I can. – traktor Nov 27 '22 at 04:44
  • I have several worries here. For example, you are looking at the computed style just once and you are creating setIntervals on each keypress, without removing any past ones. I can't completely follow what will happen, but I imagine it might be a bit random. – A Haworth Nov 27 '22 at 09:49
  • @AHaworth `getComputedStyle` returns a live but read-only CSSStyleDeclarations object. There is no need in this case to call it again. Fully concur multiple setIntervals running is a mess. – traktor Nov 27 '22 at 14:18
  • @traktor yes, quite right, my slip. – A Haworth Nov 27 '22 at 17:31

1 Answers1

0

It seems the bug is intermittently triggered by updating animation duration while an animation is running. Unfortunately defenders of browser code (copied between multiple open sourced browsers if I am not mistaken) might possibly respond that, because CSS does not state that animation duration values can be dynamically changed, it's not a bug - it just doesn't work.

My first thoughts were that adding setInterval timers every time ArrowUp key events were detected was probably causing the problem, but persistence of the issue with no timers at all proved this incorrect.

The workaround presented below takes a different approach to avoid changing animation durations while they are running:

  • animations have an iteration count of 1, not infinite.
  • animationend events are used to detect when an animation finishes.
  • the event handler for animationend calculates a new animation duration and restarts the dragon animation using the method presented in this answer to a different post asking about how to restart animations.
  • the event handler sets the duration on the dragon's style object after setting animation to null to restart the animation. Setting it earlier was not effective.

Minor modifications to posted code mean that

  • .dragon CSS contains an animation-duration property for program access.
  • .ani-dragon CSS contains actual animation rules for the dragon.
  • the dragon is not visible until the up arrow key is pressed and the animation moves the element inside the body element. Change the left rule for .dragon rules to modify.
  • dragon related variables start with dragon to disambiguate them from dino rules.
  • calculations of updated dragonDuration values may not be what was intended - please modify as required.

The workaround has been tested in Firefox and Edge, a web-kit based browser:

"use strict";
let dino = document.querySelector(".dino");
let dragon = document.querySelector(".dragon");
const dragonDeclarations = window.getComputedStyle(dragon); // read only

let dragonAnimationTime = parseInt(dragonDeclarations.getPropertyValue("animation-duration"));

let dragonUps  = 0;
let dragonStarted = false;
let dragonDuration = dragonAnimationTime;

dragon.addEventListener("animationend", event=> {
   // console.log("previous iteration, ups = %s, dragonDuration =  %s, style.animationDuration = %s", dragonUps, dragonDuration, dragon.style.animationDuration)
   if( dragonDuration > 2 && dragonUps) {
       // this code decreases dragon duration by 0.1s
       // for each "ArrowUp" downkey event during the animation,
       // with a minimum of 2 seconds duration.
       // modify as needed
       dragonDuration = Math.max(2, dragonDuration - 0.1 * dragonUps)
       dragonUps = 0;

    }

    // restart animation as per https://stackoverflow.com/a/45036752/5217142
    dragon.style.animation = 'none';
    dragon.offsetHeight; /* trigger reflow */
    dragon.style.animation = null;
    dragon.style.animationDuration = dragonDuration + 's'; 
});

document.documentElement.addEventListener("keydown", function (e) {
  if (e.key == "ArrowUp") {
    if( !dragonStarted) {
        dragon.classList.add("ani-dragon");
        dragonStarted = true;
    }
    ++dragonUps;

    // dino code incomplete
    setTimeout(() => {
      dino.classList.remove("ani-dino");
    }, 1000);
  }
});
.dragon {
  /* background-image: url(images/dragon.png); */
  background-position: center;
  background-size: contain;
  background-repeat: no-repeat;
  width: 180px;
  height: 120px;
  position: absolute;
  bottom: 0;
  left: 2000px;
  animation-duration:  6s;
 /* animation: ani-dragon 6s linear infinite; */
}
.ani-dragon { 
  animation: ani-dragon 6s linear /* deefault to 1 iteration */;
}
@keyframes ani-dragon {
  0% {
    left: 2000px;
  }
  100% {
    left: -500px;
  }
}
.ani-dino {
  animation: ani-dino 1s linear;
}
@keyframes ani-dino {
  0% {
    bottom: 0;
  }
  50% {
    bottom: 300px;
  }
  100% {
    bottom: 0;
  }
}
/* testing without images: */
.dragon::before { content: ""; font-size: 80px; } 
.dino::before   { content: ""; font-size: 80px; }
/* testing under body element - remove scroll bars */
body { overflow: hidden}
<div style="float:right">
   Click this pane to focus.<br>
   Press "Up Arrow" and wait a few seconds to begin.</div>
<div class="dragon"></div> 
<div class="dino"></div>
traktor
  • 17,588
  • 4
  • 32
  • 53