inspiredtolive inspiredtolive - 3 months ago 31
React JSX Question

How do you cancel and reset css animations in react?

What I am trying to do



I have a sprite sheet and I am using it for some simple animations. I have a "player" that has two animations. One for attacking and one for standing. The current behavior is as follows: "stand" is the default animation. When the player is clicked, the "attack" animation is played and switched back to "stand."

For the special case that the player is clicked while attacking, I want the animation "attack" animation to reset.

My code



// React Component
class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
timeout: null
};
}

attack(e) {
if (this.state.timeout) { clearTimeout(this.state.timeout); }
var $el = $(e.target);
$el.css({
backgroundPosition: '0 220px',
animationName: 'attack',
width: '300px'
});
this.state.timeout = setTimeout(function() {
$el.css({
backgroundPosition: 'left top',
animationName: 'stand',
width: '250px'
});
}, 2000);
}

render () {
return (
<div onClick={this.attack.bind(this)} className="player main">{this.props.name}</div>
);
}
}


/* The css */
.player {
width: 250px;
height: 220px;
background: url('assets/player.png') left top;
animation: stand 1.2s steps(3) infinite;
animation-direction: alternate;
}
@keyframes stand {
100% {
background-position: -750px 0;
}
}
@keyframes attack {
100% {
background-position: -900px 220px;
}
}


What I tried to do



I knew that since I setTimeout the "stand" animation, I would have to cancel it and make a new setTimeout to go after the "stand" animation. However, it still looks a bit funky. I tried to use forceUpdate() to see if I could just rerender and reset everything and then start the "attack" animation, but it doesn't seem to do anything.

I have another idea using jquery to remove and reattach the dom element but I feel like it would get messy and I believe I might be approaching this problem the wrong way. Do you guys have any ideas?

Answer

For complex animations, you can use https://github.com/chenglou/react-motion which can learnt from https://egghead.io/playlists/react-react-animation-using-react-motion and https://egghead.io/lessons/react-react-motion-introduction-to-the-spring-component

For simple ones, I would rather add and remove classes from element to enable the animation and reset it.

And if I am understanding it correctly, you are using jQuery inside react. Well to update the CSS, you should not use it inside react.

There should be more strong reasoning to use jQuery in react because updating inline css is very simple in react.

Following is the way I would attempt to do it:

// React Component
class Player extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        direction: null,
        timeout: null
      };
    }

    attack(e) {

      //following line can be used to identify multiple targets
      //let target = (e.target);
      if (this.state.timeout !== null) {
        window.clearTimeout(this.state.timeout)
      }
      let timeout = setTimeout(() => {
        this.setState({
          direction: null,
          timeout: null
        });
      }, 8000)
      let direction = null;
      if (e.ctrlKey) {
        direction = 'anticlockwise'
      } else {
        direction = 'clockwise'
      }
      if (this.state.direction !== direction) {
        this.setState({
          direction, timeout
        });
      }

    }

    render() {
        let classNames = ["player", "main"];
        let styles = {}
        if (this.state.direction !== null) {
          if (this.state.direction === 'clockwise')
            styles.zoom = 0.8
          else
            styles.zoom = 1.2
          classNames.push(this.state.direction)
        }
        return ( < div onClick = {
            this.attack.bind(this)
          }
          style={styles}
          className = {
            classNames.join(' ')
          } > {
            this.props.name
          } < /div>
    );
  }
}
ReactDOM.render(<Player / > , document.getElementById('game'));
.player {
  width: 100px;
  height: 100px;
  margin: 50px;
  zoom: 1;
  transition: all linear 0.3s;
  display: inline-block;
  background-color: #ccc;
  animation-timing-function: linear;
}
.clockwise {
  animation: clockwise 5s infinite;
}
.anticlockwise {
  animation: anticlockwise 5s infinite;
}
@keyframes clockwise {
  0% {
    transform: rotate(0deg)
  }
  50% {
    transform: rotate(180deg)
  }
  100% {
    transform: rotate(360deg)
  }
}
@keyframes anticlockwise {
  0% {
    transform: rotate(360deg)
  }
  50% {
    transform: rotate(180deg)
  }
  100% {
    transform: rotate(0deg)
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="game"></div>

Comments