kaplay icon indicating copy to clipboard operation
kaplay copied to clipboard

bug: Color animation happens instantly

Open nojaf opened this issue 6 months ago • 8 comments

Bug Description

I'm trying to create a hover effect by animating my colour to a different value:

let c = add([
    circle(50),
    pos(200, 300),
    color("#53eafd"),
    animate(),
    area(),
]);

c.onHover(() => {
    c.animate("color", [Color.fromHex("#f4a8ff")], {
        duration: 0.5,
        loops: 1,
        direction: "end"
    })
})

c.onHoverEnd(() => {
    c.color = Color.RED;
})

The problem is that the colour instantly changes to the new colour. Am I missing something on how this is supposed to work? Or is this a bug? Is animate okay to use here, or should I use tween or lerp?

Next, the assignment of c.color = Color.RED does not work when an animation is used. Is this also to be expected? How should this work?

Animate repro.kaplay.zip

Version

master

Playground Link

No response

Extra information

I was expecting this to be similar to how I would do it in the browser:

const square = document.createElement("div");
square.style = "background: yellow; width: 50px; height: 50px; position: fixed; right: 50px; top: 50px; z-index: 1000; transition: background 500ms;";
square.addEventListener("mouseenter", () => {
    square.style.background = "#FF0000";
})
square.addEventListener("mouseleave", () => {
    square.style.background = "yellow";
})
document.documentElement.lastChild.appendChild(square)

Summary

  • [ ] Fixed in v4000
  • [ ] Fixed in v3001

nojaf avatar Jun 07 '25 08:06 nojaf

Animate does not use the current color, position, angle or any other property. You need to pass several keyframes to animate and it will interpolate between them.

So correct would be c.animate("color", [c.color, Color.fromHex("#f4a8ff")], ...)

mflerackers avatar Jun 07 '25 09:06 mflerackers

Still doesn't seem to work for me: playground

And the onHoverEnd is not working either.

nojaf avatar Jun 07 '25 12:06 nojaf

https://play.kaplayjs.com/?code=eJyNkMFqwzAMhu95CuFdHDCuty5QWlooW6Hn7Vh6ELY8Qh07OOm6Mfbuc5x0hZ0mDJY%2BydIvz2bw2mPsO0DwdIE3bKg4Yevwk5eronDUg4Y1oDH8UEAyXUftiFeqFDluQ8cflBIwV1ekgwuRs7tqTmgNmyj6usGe%2BDWMhIN%2FHMZoGfw%2BvFPkvIT1Br7GPvLsp1db5wY9Ix1ZHbzsiE5c%2FUkQZ1kBE3DQMrsCnoZL2hiaPX0kbfYRF9ay8iimYYOZc8x9l6BkJX6xC6HtlnB%2FI6aOpMdKZkO8YDQsJ7%2FLIp3bPjtv%2FrVSVpn%2BeZT5sntepT4%2FZRlwmg%3D%3D&version=master

mflerackers avatar Jun 07 '25 12:06 mflerackers

Can you explain this a bit? Why do I need to call c.animation.seek(0);? Seems like that is fixing some internal state that was wrong. This is unintuitive after reading the guide

nojaf avatar Jun 07 '25 13:06 nojaf

The animate component has an internal clock which starts the moment it is added to an object. So by the time you animate a property for 0.5ms, the internal clock may already be many seconds ahead. So when you want to play an animation not at creation time, you need to seek to 0, which sets the internal clock to 0. The other mistake is to not clear existing animations. Animations don't disappear on their own, you need to clear them when finished with them.

mflerackers avatar Jun 07 '25 13:06 mflerackers

Thanks, this is super insightful! That explains

c.onAnimateChannelFinished(name => {
    if (name === "color") {
        console.log("color ended")
        c.unanimate("color");
    }
})

better as well.

Would you accept a PR with some more API documentation for this?

nojaf avatar Jun 07 '25 13:06 nojaf

Sure. Originally animate was meant to build or load (it can export and load animations too) a complex animation, and play it. That's why the internal clock starts immediately. However now it is often used as a more advanced replacement for the old tween, so maybe some API changes are in order.

mflerackers avatar Jun 07 '25 13:06 mflerackers

Thanks, will try and prepare something in the next days.

nojaf avatar Jun 07 '25 13:06 nojaf