I have an animation and it will jump into the target height, and then fall to the ground.
If I click "jump" twice when it is still in the air and does not hit the target height, it will recalculate the target and continue the jump into the new target. This event works.
But if I click "jump" when the object is going down, it should cancel the "fall" animation, and retrigger the "jump" animation again into the new target. However, this does not work. When I click, the animation stops.
And so I try to debug. I put a console.log on each function to see what's happening.
When the object jumps and falls, it shows this:
But if I click jump again when it's falling, it shows this:
It seems that when I click the jump again, it's not canceling the "fall" animation frame, so the jump and fall functions repeat simultaneously infinitely. So I assume the window.cancelAnimationFrame()
is not working.
How to fix/properly cancel an animation?
Svelte version example for simplicity: https://svelte.dev/repl/dcf5913d7b75422aaa59123286591fd9?version=4.1.1
let up = false
let height = 0
let speed = 1
let target = height + 150
let rect = document.getElementById("rect")
let stateText = document.getElementById("state")
let upText = document.getElementById("up")
let heightText = document.getElementById("height")
let targetText = document.getElementById("target")
stateText.innerText = "State: idle"
upText.innerText = "Up: "
heightText.innerText = "Height: 0"
targetText.innerText = "Target: " + target
function jump() {
up = true
upText.innerText = "Up: " + up
stateText.innerText = "State: jump"
if (height < target) {
// before hit the target height
console.log("debug jump")
height += speed
heightText.innerText = "Height: " + height
rect.setAttribute("y", 280 - height)
window.requestAnimationFrame(jump)
} else {
// falls down after hitting the target height
window.cancelAnimationFrame(jump)
up = false
window.requestAnimationFrame(fall)
}
}
function fall() {
up = false
stateText.innerText = "State: fall"
upText.innerText = "Up: " + up
if (height > -1) {
// falls down
console.log("debug fall")
height -= speed
heightText.innerText = "Height: " + height
rect.setAttribute("y", 280 - height)
window.requestAnimationFrame(fall)
} else {
// stays on the ground when the falls finish
window.cancelAnimationFrame(fall)
height = 0
stateText.innerText = "State : idle"
heightText.innerText = "Height: " + height
up = false
}
}
function handleClick() {
target = height + 150
targetText.innerText = "Target: " + target
if (!up) {
// if the object falls, cancel fall animation
// and trigger the jump animation
console.log("cancel fall")
window.cancelAnimationFrame(fall)
window.requestAnimationFrame(jump)
}
}
svg {
border: solid;
width: 300px;
height: 300px;
}
h3 {
margin: 0;
}
<h3 id="state"></h3>
<h3 id="up"></h3>
<h3 id="height"></h3>
<h3 id="target"></h3>
<button id="button" onClick="handleClick()">Jump</button>
<br><br>
<svg>
<rect id="rect" x="140" y="280" width="20" height="20"></rect>
</svg>
You seem to be using cancelAnimationFrame()
incorrectly. As per the documentation, the parameter you pass should be "the ID value returned by the call to window.requestAnimationFrame()
that requested the callback."
Thus, you should have a reference to the request ID returned from your requestAnimationFrame()
calls and then pass this request ID to cancelAnimationFrame()
when needed:
let up = false
let height = 0
let speed = 1
let target = height + 150
let rect = document.getElementById("rect")
let stateText = document.getElementById("state")
let upText = document.getElementById("up")
let heightText = document.getElementById("height")
let targetText = document.getElementById("target")
stateText.innerText = "State: idle"
upText.innerText = "Up: "
heightText.innerText = "Height: 0"
targetText.innerText = "Target: " + target
let animationId;
function jump() {
up = true
upText.innerText = "Up: " + up
stateText.innerText = "State: jump"
if (height < target) {
// before hit the target height
console.log("debug jump")
height += speed
heightText.innerText = "Height: " + height
rect.setAttribute("y", 280 - height)
animationId = window.requestAnimationFrame(jump)
} else {
// falls down after hitting the target height
window.cancelAnimationFrame(animationId)
up = false
animationId = window.requestAnimationFrame(fall)
}
}
function fall() {
up = false
stateText.innerText = "State: fall"
upText.innerText = "Up: " + up
if (height > -1) {
// falls down
console.log("debug fall")
height -= speed
heightText.innerText = "Height: " + height
rect.setAttribute("y", 280 - height)
animationId = window.requestAnimationFrame(fall)
} else {
// stays on the ground when the falls finish
window.cancelAnimationFrame(animationId)
height = 0
stateText.innerText = "State : idle"
heightText.innerText = "Height: " + height
up = false
}
}
function handleClick() {
target = height + 150
targetText.innerText = "Target: " + target
if (!up) {
// if the object falls, cancel fall animation
// and trigger the jump animation
console.log("cancel fall")
window.cancelAnimationFrame(animationId)
animationId = window.requestAnimationFrame(jump)
}
}
svg {
border: solid;
width: 300px;
height: 300px;
}
h3 {
margin: 0;
}
<h3 id="state"></h3>
<h3 id="up"></h3>
<h3 id="height"></h3>
<h3 id="target"></h3>
<button id="button" onClick="handleClick()">Jump</button>
<br><br>
<svg>
<rect id="rect" x="140" y="280" width="20" height="20"></rect>
</svg>