I’m using CSS transition
with top
and left
to animate the movement of an element in React.
To achieve this, the component must first be rendered with top: 0; left: 0
, and then re-rendered with altered top
and left
properties to start the transition.
This is my component:
export default class Animator extends React.Component{ render(){ var top = 0; var left = 0; if(this.state.moving){ top = this.props.top; left = this.props.left; } return( <div className="red-box animator" style={{ top: `{top}px`, left: `{left}px`}}> </div> ); } componentDidMount(){ this.setState({moving: true}); } }
.animator{ position: absolute; transition: top 0.5s, left 0.5s; } .red-box{ width: 50px; height: 50px; background-color: red; }
I expected the component to be rendered by the browser before componentDidMount
fires, but I found that this isn’t the case.
As a result, the top
and left
properties are updated too soon and the component appears immediately in its destination position with no transition.
Changing my componentDidMount function to have a delay solves the problem, but too short of a delay makes the functionality unreliable (especially on slower devices), and I don’t want a longer delay as I want the element to be moving as soon as it appears on the screen.
componentDidMount(){ setTimeout(() => { this.setState({moving: true}); }, 150); }
Is there a way to ensure that I only update the state once the browser has rendered the element?
Advertisement
Answer
Browsers optimize render and are not rerendering stuff that changed in the same animation frame. They merge changes and render the end result. To mitigate that you may use requestAnimationFrame
trick:
componenetDidMount() { self.requestAnimationFrame(() => this.setState({ moving: true }) }
Keep in mind that though requestAnimationFrame
is widely supported among browsers some really old ones might need a polifill or prefix to make it work.