NativeScript / NativeScript

⚡ Empowering JavaScript with native platform APIs. ✨ Best of all worlds (TypeScript, Swift, Objective C, Kotlin, Java, Dart). Use what you love ❤️ Angular, Capacitor, Ionic, React, Solid, Svelte, Vue with: iOS (UIKit, SwiftUI), Android (View, Jetpack Compose), Dart (Flutter) and you name it compatible.
https://nativescript.org
MIT License
24.19k stars 1.64k forks source link

Animating an element prevents future CSS animations from working #3608

Closed 3rror404 closed 7 years ago

3rror404 commented 7 years ago

Hi guys,

I seem to have found a strange bug. Hopefully somebody can think of a workaround, or fix if possible.

I have an element that I wish to animate on tap.

<GridLayout id="message-manual-journey" class='message message-danger' columns="auto, *" tap="tapSyncUnsynced"  visibility="{{ unsyncedJourneys ? 'visible' : 'collapse' }}">
    <Image col="0" src="~/images/exclamation-circle-white@2x.png" class="icon" />
    <Label col="1" id="message-manual-journey-label" Text="Journey data found. Tap to upload." />
</GridLayout>  

The element has the following CSS:

.message-danger {
    background-color: #FB3640;
    border-color: #E13039;
    border-width: 1 0 1 0;
    color: #FFF;
    font-size: 14;
}

When this element is tapped I add a CSS class in order to trigger a CSS keyframe animation:

exports.tapSyncUnsynced = function(args) {
    var message = page.getViewById('message-manual-journey');
    message.className = message.className + ' pulse';
}
@keyframes pulse {
    from { background-color: #bedb39; }
    50% { background-color: #98af2d; }
    100% { background-color: #bedb39; }
}

#message-manual-journey.pulse {
    border-color: #859927;
    animation-name: pulse;
    animation-duration: 3s;
    animation-iteration-count: infinite;
    animation-delay: .2s;
}

This works but results in an immediate change from background-color: #FB3640 (initial colour) to background-color: #bedb39; (first stage of keyframe animation). The keyframe animation then runs as expected

To work around this I am trying to use the animate module to animate the background colour from #FB3640 to #bedb39 before then applying the CSS class.

message.animate({ 
        backgroundColor: '#bedb39',
        duration: 326,
        curve: enums.AnimationCurve.easeOut
    })
    .then(() => {
        message.className = message.className + ' pulse';
    })
    .catch((e) => {
        console.log(e.message);
    });

I would expect this code to animate the background colour and then apply the CSS class which would in turn trigger the keyframe animation. However the CSS keyframe animation does not run.

Tested on iOS and Android.

┌──────────────────┬─────────────────┬────────────────┬──────────────────┐
│ Component        │ Current version │ Latest version │ Information      │
│ nativescript     │ 2.4.2           │ 2.5.0          │ Update available │
│ tns-core-modules │ 2.4.3           │ 2.5.0          │ Update available │
│ tns-android      │ 2.5.0           │ 2.5.0          │ Up to date       │
│ tns-ios          │ 2.4.0           │ 2.5.0          │ Update available │
└──────────────────┴─────────────────┴────────────────┴──────────────────┘
NickIliev commented 7 years ago

Hey @3rror404

I notice that in your code snippet you are using the following syntax for your CSS animation

@keyframes pulse {
    from { background-color: #bedb39; } /* from-to or 0-50-100 */
    50% { background-color: #98af2d; }
    100% { background-color: #bedb39; }
}

Starting with from and then switching to percents. Try to animate either with from-to or with percents for example:

@keyframes pulse {
    0% { background-color: #bedb39; }
    50% { background-color: #98af2d; }
    100% { background-color: #bedb39; }
}
3rror404 commented 7 years ago

Hi @NickLiev,

Unfortunately it doesn't make any difference which keyframe syntax I use. It will never run if the class is applied in the .animate() callback.

The class is definitely applied as other properties work, just the animation doesn't.

#message-manual-journey.pulse {
    /* These work... */
    border-color: #859927;
    color: red;

    /* These do not work... */
    animation-name: pulse;
    animation-duration: 3s;
    animation-iteration-count: infinite;
    animation-delay: .2s; 
}
NickIliev commented 7 years ago

@3rror404 I've recreated your first scenario in this sample but using a bit more contrast colors, and your first code snippet (with 0--50-100 percents) and everything works as expected on both iOS and Android. I've used a transition from BLUE to LIME to RED to demonstrate the start and the end of the transition called pulse. Note that it is to be expected that your initial background color will be changed immediately to the first colour set as 0%. If you want to apply a smooth transition from the real background you can adjust the background color (#FB3640) as the initial color in your CSS transition (0%) and start to change this color smoothly (for example to 30 or to 50%).

In your second case where you are using a mix of declarative and imperative animations, there is indeed non-executing declarative animation. Will investigate more, but from y point of view it is overkill to use to different kind of animations to achieve this effect. As mentioned above it is better to create your keyframe animation with initial color equal to your background color. Or if you need to control and play your keyframes animations via code behind you can also achieve this as follows:

   import { KeyframeAnimation } from "ui/animation/keyframe-animation";

   let animationInfo = page.getKeyframeAnimationWithName("pulse"); // pulse is @keyframe from your css file
   animationInfo.duration = 3000;
   animationInfo.iterations = 9999;
   animationInfo.delay = 0.2;
   let animation = KeyframeAnimation.keyframeAnimationFromInfo(animationInfo, 0);
   animation.play(message).then(() => {
       console.log("Played with code!");
   });
3rror404 commented 7 years ago

Hi @NickIliev,

yeah I know the first example is working as expected. And that is exactly how I would expect it to behave. It was just an explanation of the problem I'm trying to solve.

Your suggestion of setting the first stage to the keyframe animation to the red colour won't work unfortunately. The animation needs to loop so it would switch back to red every iteration.

In a browser this would be simple as I would just set a transition on the initial state and the smooth colour change would take care of itself. The only way I can think to do this in Nativescript is how I'm attempting it above with the .animate() then add class.

I've got it working using your code above by calling animation.play() in the .animate() callback so thanks a lot for the suggestion 👍

3rror404 commented 7 years ago

Though this solved my specific problem, there is still a bug here somewhere.

lock[bot] commented 5 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.