FormidableLabs / radium

A toolchain for React component styling.
http://formidable.com/open-source/radium/
MIT License
7.39k stars 308 forks source link

I have a problem about radium.keyframes #912

Open HsuTing opened 7 years ago

HsuTing commented 7 years ago

I try to change the animation when I click a button and do something when the animation is complete. Here is a simple example:

import React from 'react';
import radium, {StyleRoot} from 'radium';

const isClickedStyle = {
  opacity: '0'
};

const normalStyle = {
  opacity: '1'
};

const normalAnimation = radium.keyframes({
  '0%': isClickedStyle,
  '100%': normalStyle
});

const isClickAnimation = radium.keyframes({
  '0%': normalStyle,
  '100%': isClickedStyle
});

const style = isClicked => ({
  width: '100px',
  height: '100px',
  background: 'blue',
  animation: 'x 0.5s ease-in-out',
  animationName: isClicked ? isClickAnimation : normalAnimation,
  ...(isClicked ? isClickedStyle : normalStyle)
});

@radium
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isClicked: false
    };

    this.animationEnd = true;
    this.onClick = this.onClick.bind(this);
  }

  render() {
    return (
      <div>
        <StyleRoot style={testStyle(this.state.isClicked)}
          onClick={this.onClick}
          onAnimationEnd={() => (this.animationEnd = true)}
        />
      </div>
    );
  }

  onClick() {
    if(this.animationEnd) {
      this.animationEnd = false;
      this.setState({isClicked: !this.state.isClicked});
    }
  }
}

This code can work in chrome, but can not work in safari, iphone`s chrome. The problem is that keyframe is added to style tag after the browser add style to component. As a result, onAnimationEnd will not be called because the animation does not work. Here is my solution:

  render() {
    return (
      <div>
        <StyleRoot style={{animationName: isClickAnimation}} />
        <StyleRoot style={{animationName: normalAnimation}} />

        <StyleRoot style={testStyle(this.state.isClicked)}
          onClick={this.onClick}
          onAnimationEnd={() => (this.animationEnd = true)}
        />
      </div>
    );
  }

This can add keyframe to style tag at the begin. animation will work because keyframe does exist. However, I don`t think this is a good solution. Is any another way to solve it?

solancer commented 7 years ago

I'm facing the same problem is there a possible fix to this? or keyframes animation inline is just not possible at all?

PatrickNausha commented 7 years ago

Here's a minimal repro. Works in Chrome 60 but not Safari 10.1.1: https://codepen.io/patricknausha/pen/GvQxKR

PatrickNausha commented 7 years ago

It seems my codepen sometimes works (the box rotates) the first time in Safari. After a refresh, it won't.

solancer commented 7 years ago

I ended up using GASP tweenmax to achieve this, worked like a charm!

apiv commented 6 years ago

My suspicion is that this is a race condition, in which the @keyframes are added to the stylesheet on the page after the animationName style property is added to the element. Safari doesn't find the animation, and doesn't refresh those elements when the animation is added to the stylesheet.

This can also be verified by doing the following: 1) Find the element which is not animating in the web inspector. 2) Remove the animation style rule and then re-add it 3) Safari will now find the animation in the existing stylesheet and the element will animate.

Looking at the code, the update of the global stylesheet (src/components/style-sheet.js:43) is deferred by 0 milliseconds, while the addition of the style rule to the element is synchronous. This would cause the race condition described above.

Does anyone know why the _onChange method of StyleSheet in src/components/style-sheet.js is deferred?

tonyhallett commented 4 years ago

You could also try this AnimationAwareStyle component