React CSS过渡效果无法正常工作

36

我写了一个使用CSS过渡效果的React应用程序,但其中一些组件的过渡效果不正确。在我的应用中,只有向上移动的组件能正常工作,向下移动的组件会立即移动而没有动画。(我希望它们都具备动画效果。)

这是我在那里使用的CSS:

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 */

}

更新:

我还建立了一个codepen.io项目来展示这个问题。它包含了此演示项目的完整代码。

我尝试向componentDidUpdatecomponentDidMountcomponentWillUnmount方法中添加日志记录条目,以显示这些组件是重新创建还是更新,结果显示它们每秒都被全部更新(而不是被重新创建或删除)。


我们无法调试仅有2个CSS规则的代码,您需要发布一个能够重现问题的工作代码片段。 - Asons
@LGSon 抱歉,我已经建立了一个codepen.io项目,并很快会更新问题。 - cmal
通过简单地交换一些“top”值,它们也开始向下动画。我猜你需要检查这些值在项目中如何被切换。https://codepen.io/anon/pen/ZJazQa...还要注意,在CSS中,非前缀属性应始终位于前缀属性之后。 - Asons
6个回答

33
当你使用绝对定位(或者相对定位,就像你的情况一样)时,如果每次重新渲染整个列表,React会重新排序DOM中的元素(正如你所说的,元素并没有被重新创建,只是更新了)。但是这会导致过渡效果的问题...显然,如果在过渡动画运行时移动一个元素,那么动画就会被中断。
所以,对于想要使用绝对定位的情况,关键的概念是只渲染元素的容器一次(在这种情况下,只是div),并且根据新的顺序只改变内部内容。如果需要添加更多元素,只需将它们添加到末尾。
我修改了你的codepen,以反映我所说的。我的示例非常简单,因为我只创建了4个临时的div,但它说明了这个思想:根据需要创建尽可能多的容器,但不要使用每次重新创建它们的映射,否则你的过渡效果会被中断。

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>
  );
}

所以,问题基本上是如何创建一个静态容器列表以及如何迭代该列表,以便第一个容器呈现数据的第一个元素,第二个容器呈现第二个元素,依此类推。
希望这能帮到你,这个问题也让我疯狂了! :)

3
我希望我能够为这个答案投上千倍的赞...谢谢! - MarkoCen
有没有办法让它与.map()一起工作?我想呈现的列表非常长,如果我写出每个元素,它就无法正常工作。 - yangcodes
@yangcodes 我建议你将列表的内容缓存到useMemo中,或者找到一种缓存内容的方法。如果元素为空,它们可以被渲染,除非你正在渲染100k个空元素,否则不会对性能产生太大影响。 - damianmr

9

我知道这不是情况,但因为我也在寻找 React css transition does not work correctly 的解决方案,所以我想分享一下:

如果你render中使用箭头函数创建元素,它将无法得到正确的动画效果,因为一个新组件总是被创建。 你应该在外部创建一个函数,并在“render”中调用它。


5
你可以通过使用 index 作为键来欺骗 React。如果你想到 elindex 分别代表起始位置(index)和结束位置(el),那么在过渡结束时,元素已经移动到旧的结束位置,并且到达那里时,它被新的起始位置接管,并且(index)被切换以匹配新的设置。这是因为当你在 React 中为元素设置 key 时,虚拟 DOM 总是将其解释为相同的元素。而且,通常情况下,将索引设置为“id”是正确的做法。
我通过交换 index/el(并将元素位置设置为绝对位置)制作了一个可行的示例。

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>


3
如果您从虚拟DOM中删除元素,则React将更新其内容,因此您将无法看到动画效果。您可以使用react-transition-group,或在调用事件后告诉应用程序等待x毫秒以更新DOM,或者使用可见性在隐藏和显示之间切换,而不是完全从DOM中删除它。

谢谢。但是元素并没有从虚拟 DOM 中删除,只是更新了组件。我已经更新了问题。 - cmal

3

每次都重新创建了DOM元素。
你应该定义收集的键值。 我将你的键值'' + el改为'' + index

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

只需更改css属性即可 :)


0

我会把这个留在这里,以防对某人有帮助,但对我来说,问题是通过移动从解决的。

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

目标:

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

export default Contact


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接