cssta icon indicating copy to clipboard operation
cssta copied to clipboard

React Native: implementing CSS animations and CSS transitions in regular CSS

Open kristerkari opened this issue 5 years ago • 12 comments

Hi @jacobp100!

First of all, thank you for this awesome library. I'm really surprised that it does not have more stars on Github.

I have been already for a long time thinking if it is possible to add support for CSS animations for my React Native CSS modules project, but I haven't done anything yet because mapping CSS animations to the Animated API is a bit complicated.

I noticed that cssta supports both CSS transitions and CSS animations in React Native, which got me thinking that maybe I could just borrow the implementations from this library and modify them to fit the different way to define/apply styles.

I was wondering if you could give me some tips of how to approach the problem in a CSS modules context as you have already implemented the support for those features in cssta?

React Native CSS modules already supports CSS media queries, so I think that a similar approach needs to be used for animations and transitions.

Here's roughly how it works:

  1. CSS with media queries gets parsed at bundle time. The parsed media queries are inside the style object under a special key.
  2. A Babel plugin changes the className property to function call that calls a runtime library with the bundled styles.
  3. The parsed media queries get matched at runtime against the current window dimensions.

For CSS animations and CSS transitions there would need to be a wrapping component instead of just the function call that takes in styles.

Anyway, it would be nice to get your ideas of what would be the best way to implement those features.

kristerkari avatar May 01 '19 07:05 kristerkari

Thanks for your interest! I’ve got a small refactor on the native side that’ll make use of React hooks, which will make some of the stuff easier to follow.

Okay, so we first start by processing the CSS passed into the component. This is only done once per component, and never re-parsed.

So on native, we start here and extract the key frames, animation properties, and transition properties from the css. We use the postCSS AST for this.

Note that we don’t fully parse the animation and transition properties here — they could contain css variables. You’ll end up with something like { 'transition-delay': '3s' }, or maybe { 'transition-delay': 'var(--some-prop)' }. If you see a _, that just means the shorthand animation or transition property.

Here's an example of transition properties and keyframes.

Next, we would mount the component. You'll see VariablesStyleSheetManager, which I won't get into too much detail here, but it'll take the props before and resolve the css variables. So the values for the objects before should now have valid CSS values.

For the transitioned properties from above, do the smallest amount of parsing, and form it into an object we can pass to Animated.timing here.

For transitions, we also keep a record of the current applied styles and the previous applied styles. in We use these in the render method, we and run animation.interpolate between the previous and the new value here. If a transitioned style changes, we fire off an animation from 0 to 1 using the transition properties we got above to make the interpolation happen [here](in the render method, we animation.interpolate between the previous and the new value here.

The animations works in similar concept. The parsing step is here. However, it gets simpler here, as we just create an animation that corresponds to the key frames and then play it. That's here.

Let me know if anything doesn't make sense or you need one part in more detail!

jacobp100 avatar May 01 '19 08:05 jacobp100

Thanks for the explanation, it's very helpful.

Note that we don’t fully parse the animation and transition properties here — they could contain css variables.

In React Native CSS modules I decided to use postcss-css-variables to add support for CSS vars. Is there some added benefit of supporting matching CSS variables during runtime? I'm thinking that not handling CSS vars on runtime would probably make it easier to implement things.

kristerkari avatar May 01 '19 08:05 kristerkari

Is there some added benefit of supporting matching CSS variables during runtime?

Do you support inheriting variables? Like a parent sets a variable, and the child uses it?

jacobp100 avatar May 01 '19 08:05 jacobp100

Do you support inheriting variables?

Nope. I don't think that it would make much sense in the context of CSS modules.

kristerkari avatar May 01 '19 08:05 kristerkari

It can be useful for theming when you have multiple themes. But if you assume they don't inherit, it'll make stuff a lot easier for you, as you'll be able to do more parsing ahead of time

jacobp100 avatar May 01 '19 08:05 jacobp100

I'm not sure yet if I should support the CSS vars inheriting or not.

I think that theming could be a very useful feature, but it makes the runtime parsing a bit more complex. I need to think about it.

kristerkari avatar May 01 '19 09:05 kristerkari

But if I would use what cssta already implements, then it would be quite easy to add the same features.

I need to do a proof of concept.

kristerkari avatar May 01 '19 09:05 kristerkari

I think it depends on how you want to expose your API. I don't think you can do it just exporting styles alone, I think the component needs to have logic to do some of this.

FWIW, I want to refactor Cssta from a HOC to be a transform of CSS to JS component (+ runtime) - example below. Then the runtime could be used to make a HOC, or it could be a babel transform, or something else

Example

in.css

background: red;
opacity: 0.5;
transition: opacity 0.5s;

[@active] {
  opacity: 1;
  background: blue;
}

out.js

import React from 'react'
import { Animated, StyleSheet } from 'react-native'
import { useTransitions } from 'cssta-runtime'

const styles = StyleSheet.create({
  base: {
    backgroundColor: 'red',
  },
  active: {
    backgroundColor: 'blue',
  }
})

const transitionProperties = <some logic here>

export default ({ style, active, ...passedProps }) => {
  const transitionedStyles = useTransitions(transitionProperties)

  return (
    <Animated.View
      style={[styles.base, active && styles.active, transitionedStyles, style]}
      {...passedProps}
    />
  )
}

jacobp100 avatar May 01 '19 10:05 jacobp100

FWIW, I want to refactor Cssta from a HOC to be a transform of CSS to JS component (+ runtime) - example below. Then the runtime could be used to make a HOC, or it could be a babel transform, or something else

That is a very interesting idea. I wonder if that would also work with a CSS file that has class selectors (CSS modules).

kristerkari avatar May 01 '19 19:05 kristerkari

FWIW, I want to refactor Cssta

Just a note to say this is done! Have a look at the tests if you wanna see how stuff works — the output code is hopefully somewhat understandable

That is a very interesting idea. I wonder if that would also work with a CSS file that has class selectors (CSS modules).

There's not a lot stopping this! Some of Cssta might be repurposable for that too

jacobp100 avatar Oct 06 '19 12:10 jacobp100

hi @jacobp100, @kristerkari,

Thanks for your awesome work on cssta and react-native-css-modules!

I made a POC for the standalone CSS idea: https://github.com/dmapper/babel-plugin-cssta-stylename

You can check out __tests__ to quickly understand what is going on.

Instead of implementing the stuff from scratch, taking pieces from cssta, I decided to try to make it work the other way around -- just construct the current cssta templating components out of the CSS file, using class names as the anchors (through the styleName attribute).

So overall the functionality of cssta should hopefully stay exactly the same, with all the awesome features of dynamic variables, transitions and animations. And with the neatness of having stuff in separate css files (and being able to also preprocess them with post-css, SASS, etc.) like in Krister's react-native-css-modules

I also think it makes sense to try to eventually reuse the same stack for react-native-web, especially since the support for custom class names was dropped in 0.12 (https://github.com/necolas/react-native-web/issues/1146) and the need for rich native-first CSS features is there. I don't know all the codebase of cssta yet, but I have a bold hope that some sort of replacement of css-to-react-native with simple camel-casing of CSS properties might be good enough to achieve this 😄

cray0000 avatar Jan 28 '20 07:01 cray0000

@cray0000 Looks very nice! I have been thinking of something similar to properly integrate CSS modules and a runtime (cssta).

kristerkari avatar Jan 28 '20 12:01 kristerkari