Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Q: PWA Navigation Animation #26

Closed
ghasemdev opened this issue Jan 25, 2025 · 6 comments
Closed

Q: PWA Navigation Animation #26

ghasemdev opened this issue Jan 25, 2025 · 6 comments

Comments

@ghasemdev
Copy link
Contributor

ghasemdev commented Jan 25, 2025

I have another question about creating navigation animations.

One of Kilua’s modules is related to routing, which handles navigation between pages. Do you have any thoughts on how to implement fade or slide animations between pages in a PWA? Where should I start?

in jetpack compose navigation we do that

...
enterTransition = {
      slideInHorizontally(
        initialOffsetX = { -NAVIGATION_ANIM_OFFSET },
        animationSpec = tween(
          durationMillis = NAVIGATION_ANIM_DURATION_MS,
          easing = FastOutSlowInEasing,
        ),
      ) + fadeIn(animationSpec = tween(durationMillis = NAVIGATION_ANIM_DURATION_MS))
    },
    exitTransition = {
      slideOutHorizontally(
        targetOffsetX = { NAVIGATION_ANIM_OFFSET },
        animationSpec = tween(
          durationMillis = NAVIGATION_ANIM_DURATION_MS,
          easing = FastOutSlowInEasing,
        ),
      ) + fadeOut(animationSpec = tween(durationMillis = NAVIGATION_ANIM_DURATION_MS))
    },
    popEnterTransition = {
      slideInHorizontally(
        initialOffsetX = { NAVIGATION_ANIM_OFFSET },
        animationSpec = tween(
          durationMillis = NAVIGATION_ANIM_DURATION_MS,
          easing = FastOutSlowInEasing,
        ),
      ) + fadeIn(animationSpec = tween(durationMillis = NAVIGATION_ANIM_DURATION_MS))
    },
    popExitTransition = {
      slideOutHorizontally(
        targetOffsetX = { -NAVIGATION_ANIM_OFFSET },
        animationSpec = tween(
          durationMillis = NAVIGATION_ANIM_DURATION_MS,
          easing = FastOutSlowInEasing,
        ),
      ) + fadeOut(animationSpec = tween(durationMillis = NAVIGATION_ANIM_DURATION_MS))
    },
...
@rjaros
Copy link
Owner

rjaros commented Jan 26, 2025

Kilua's routing module doesn't know anything about pages or transitions. In simple cases it just chooses which composable to show. And when async SSR is implemented (it's when ssr process requires some asynchronous actions like calling some apis) the router doesn't even work with composables - it just emits actions to viewmodel or other state holder.

So in my opinion the real question is - how to animate recomposition?

You can use compose animations with Kilua (@shubhamsinghshubham777 used this in his spotify clone app). But there are some technical issues, because compose animations depend on skiko (see: https://youtrack.jetbrains.com/issue/CMP-4133).

You can also use CSS animations and transitions, directly with css styles or with some lib like "motion", and hide it under api similar to AnimatedVisibility. Unfortunately I have no idea how hard it would be.

@ghasemdev
Copy link
Contributor Author

You can use compose animations with Kilua (@shubhamsinghshubham777 used this in his spotify clone app). But there are some technical issues, because compose animations depend on skiko (see: https://youtrack.jetbrains.com/issue/CMP-4133).

I tried using AnimateVisibility and AnimateContent in Compose, but I think they might throw an error due to using Layout inside these functions.

Then, I tried achieving the effect using CSS animations, but I'm not sure if my approach is correct.

I created three div elements on the page as placeholders for my screens:

  • The current screen
  • The previous screen
  • The next screen

The current screen is always visible, so its opacity is set to 1.
To prevent interactions with the other two screens, I disabled touch input for them to avoid unwanted clicks.

For the transition between screens, I used keyframes and animations to change the opacity and apply x transformations. This way, I can achieve a slide + fade effect when switching screens.

@rjaros
Copy link
Owner

rjaros commented Feb 1, 2025

After some experiments I've come up with a simple animatedVisibility implementation based on CSS animations:

@Composable
fun IComponent.animatedVisibility(
    visible: Boolean,
    transitionTime: Duration = 1.seconds,
    timingFunction: String = "ease",
    content: @Composable IComponent.() -> Unit
) {

    var visibilityState by remember { mutableStateOf(visible) }
    var animation: String? by remember { mutableStateOf(null) }

    div {
        style("animation", animation)
        if (visibilityState) {
            content()
        }
    }

    LaunchedEffect(visible) {
        if (visible != visibilityState) {
            if (visible) {
                animation = "fadein ${transitionTime.toInt(DurationUnit.MILLISECONDS)}ms $timingFunction"
                visibilityState = true
                delay(transitionTime)
                animation = null
            } else {
                // Added 500ms to prevent flickering 
                animation = "fadeout ${transitionTime.toInt(DurationUnit.MILLISECONDS) + 500}ms $timingFunction"
                delay(transitionTime)
                visibilityState = false
                animation = null
            }
        }
    }
}

It requires named keyframes added to the external css file:

@keyframes fadein {
    from {opacity: 0;}
    to {opacity: 1;}
}

@keyframes fadeout {
    from {opacity: 1;}
    to {opacity: 0;}
}

It can be used like this:

var itemVisible by remember { mutableStateOf(true) }

hPanel(gap = 10.px) {
    button("Show 1") {
        onClick {
            itemVisible = true
        }
    }
    button("Hide 1") {
        onClick {
            itemVisible = false
        }
    }
}

animatedVisibility(itemVisible, transitionTime = 3.seconds) {
    div {
        width(300.px)
        height(300.px)
        border(1.px, BorderStyle.Solid, Color.Blue)
        +"Test 1"
    }
}

It's simple but works quite well. The fadein/fadeout keyframes are constant - can be just added to Kilua CSS. It should be easy to add different transitions keyframes (scale, slide) and select them by some enum parameter. timingFunction should be an enum as well (with other values supported by css - linear, ease-in, etc.). Or we could go with more jetpack-like API and create EnterTransition/ExitTransition classes created with fadeIn()/fadeOut() functions.

And with the animatedVisibility function it should be quite easy to implement transitions for navigation.

rjaros added a commit that referenced this issue Feb 2, 2025
@rjaros
Copy link
Owner

rjaros commented Feb 2, 2025

I've created the new kilua-animation module and implemented a bunch of different composables for animations powered by CSS and also the Motion library. There are two versions of animatedVisibility and also functions like animateDoubleAsState, animateIntAsState, animateCssSizeAsState, animateColorAsState. All these function can be easily used with advanced animation engine of Motion (which includes both tween and spring based animations).

@ghasemdev
Copy link
Contributor Author

I've created the new kilua-animation module and implemented a bunch of different composables for animations powered by CSS and also the Motion library. There are two versions of animatedVisibility and also functions like animateDoubleAsState, animateIntAsState, animateCssSizeAsState, animateColorAsState. All these function can be easily used with advanced animation engine of Motion (which includes both tween and spring based animations).

Building this module was a really interesting experience. While reviewing the code, I found it quite fascinating.

I think we could also add functions for enterTransition and exitTransition so that those familiar with Jetpack Compose can relate to it more easily. But overall, the concept remains the same.

@rjaros
Copy link
Owner

rjaros commented Feb 25, 2025

Released in 0.0.20

@rjaros rjaros closed this as completed Feb 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants