39

I've written a React app, using CSS transitions. But those transitions does not work correctly in some of the components. In my app, only the components who are moving upwards works well, those who are moving downwards moves instantly without animation. (I want them both moves with animation.)

Here is the CSS I used there:

div.canvas {
    position: absolute;
    top: 90px;
    left: 60px;
    width: 640px;
    height: 480px;
    border: 1px solid #999;
    background: white;

}

div.canvas-rect {
    position: relative;
    margin-left: 20px;
    margin-top: 10px;
    height: 20px;
    background: green;

    transition: all 1s linear;
    -moz-transition: all 1s linear; /* Firefox 4 */
    -webkit-transition: all 1s linear; /* Safari 和 Chrome */
    -o-transition: all 1s linear; /* Opera */

}

UPDATED:

I also built a codepen.io project to show the problem. It has the complete code of this demo project.

I've tried to add a log entry to componentDidUpdate, componentDidMount and componentWillUnmount methods to show whether these component are re-created or updated, it shows that they are all updated (not re-created, or removed) every second.

3
  • We can't debug just 2 CSS rules, you have to post a working code snippet reproducing the issue Commented Aug 14, 2017 at 16:07
  • @LGSon Sorry for that, I've built a codepen.io project and will update the question soon. Commented Aug 15, 2017 at 2:14
  • By simply swapping the top values some, they start animate downwards as well. I guess you need to check up on how those values being switched on the items. codepen.io/anon/pen/ZJazQa ... also note, the non prefixed properties should always be after the prefixed in the CSS Commented Aug 15, 2017 at 9:23

6 Answers 6

34

When you are using absolute position (or relative, as in your case), if you re-render the whole list every time, React will re-order the elements in the DOM (as you said, the elements are not being recreated, just updated). But this creates the problem with the transitions... apparently, if you move an element while the transition is running then you end up cutting the animation.

So, for cases in which you want to use position absolute, the key concept is to render the containers of your elements once (in this case, just divs) and only change the inner contents based on the new order. If you need to add more elements, just add them at the end.

I modified your codepen so that it reflects what I am saying. My example is very dumb because I just created 4 ad-hoc divs, but it illustrates the idea: create as many containers as you need, but DO NOT use a map that recreates them every time, or your transitions will be cut.

https://codepen.io/damianmr/pen/boEmmy?editors=0110

const ArrList = ({
  arr
}) => {
  return (
    <div style={{position: 'relative'}}>
      <div className={`element element-${arr[0]} index-${arr[0]}`}>{arr[0]}</div>
      <div className={`element element-${arr[1]} index-${arr[1]}`}>{arr[1]}</div>  
      <div className={`element element-${arr[2]} index-${arr[2]}`}>{arr[2]}</div>  
      <div className={`element element-${arr[3]} index-${arr[3]}`}>{arr[3]}</div>  
    </div>
  );
}

So, the problem is basically how you create a static list of containers and how you iterate through that list so that the first container renders the first element of your data, the second container the second element, etc.

Hope that it helps, this problem was driving me crazy too! :)

Sign up to request clarification or add additional context in comments.

2 Comments

Is there any way to get this to work with .map()? The list that I want to render is very long and won't work if I write out each element.
@yangcodes I'd suggest you cache the contents of the list in useMemo or find a way to cache the contents. The elements, if empty, can be rendered and it won't really affect the performance unless you are rendering, say, 100k empty elements.
9

I know this wasn't the case, but since I got here also looking for React css transition does not work correctly, I just wanted to share:

If you create an element using arrow functions inside render, it won't get properly animated, since a new componente is always being created. You should create a function outside and invoke it in 'render'.

Comments

5

You can trick React by using index as key. If you think about el, and index as starting position (index) and end position (el), the element has moved to the old end position by the end of the transition, and by when it's there, it's taken over by the new start position and (index) is switched to match the new setup. This is because when you set key in an element in react, the virtual DOM will always interpret it as it is the same element. And for the sake of it, you're right in setting index as the "id" in general.

I made a working example only by switching index/el (and setting element position to absolute).

const {combineReducers, createStore} = Redux;
const { Provider, connect } = ReactRedux;

const ArrList = ({
  arr
}) => (
  <div>{
  arr.map((el, index)=>
          <div
            key={""+index}
            className={`element element-${el}` + ` index-${el}`}
            >
            {el}
          </div>) }
  </div>
)
      
const mapStateToArrList = (state) => {
  return {
    arr: state.appReducer.arr
  }
};

const App = connect(mapStateToArrList, null)(ArrList);

const initialState = {
  arr: [1, 2, 3, 4]
}

const appReducer = (state = initialState, action) => {
  switch(action.type) {
    case "tick":
      return {
        ...state,
        arr: _.shuffle(state.arr)
      }
    default:
      return state
   }
}

const reducer = combineReducers({
  appReducer
})


const store = createStore(reducer)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

const dispatcher = () => {
  store.dispatch({
     type: "tick"
  })
  setTimeout(dispatcher, 1000)
}

dispatcher()
.element {
    position: absolute;
    height: 20px;
    background: green;
    margin-left: 20px;
    margin-top: 20px;

    text-align: right;
    color: white;
    line-height: 20px;

    transition: all 1s ease-in;
    -moz-transition: all 1s ease-in; /* Firefox 4 */
    -webkit-transition: all 1s ease-in; /* Safari 和 Chrome */
    -o-transition: all 1s ease-in; /* Opera */

}

.element-1 {
    width: 20px;
}

.element-2 {
    width: 40px;
}

.element-3 {
    width: 60px;
}

.element-4 {
    width: 80px;
}

.index-1 {
  top: 20px;
}

.index-2 {
  top: 40px;
}

.index-3 {
  top: 60px;
}

.index-4 {
  top: 80px;
}
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js"></script>

<div id="root"></div>

Comments

3

If you are removing the element from the virtual DOM, then the react will update its contents, so you won't see the animations. What you can do is either use react-transition-group OR tell your app to wait x ms before updating the dom once the event is called OR use visibility to toggle between hidden and showing instead of removing it completely from the DOM.

1 Comment

Thanks. But the elements has not been removed from V-DOM, just updated the component. I've updated the question for that.
3

You did recreate DOM elements each time.
You should define collect key value. I changed your key value '' + el to '' + index.

<div key={'' + index} className={'element element-' + el + ' index-' + index} >

Just change css properties only :)

Comments

0

For me it was solved by moving from:

export default function Contact(props) {...}

To:

const Contact = (props) => {...}

export default Contact

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.