如何在移动状态中的项目或使用地图渲染的道具时添加过渡

How to add transition while moving items in a state or props rendered with a map

假设我正在使用 .map 函数从状态的道具渲染列表。

我想更改列表的顺序;所以我改变了我的状态或我的道具的顺序。更改会相应地反映出来,但是在这种情况下我如何添加平滑过渡,以便用户感觉 he/she 已经做了一些事情。否则用户体验会下降。

他是我想要实现的基本思路

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

export class App extends React.Component {
  constructor() {
    super();
    this.state = {
      items: [
        { key: 0, name: "Hello" },
        { key: 0, name: "Sello" }, // move this down with transition
        { key: 0, name: "Wello" }, // move this up with transition
        { key: 0, name: "Zello" },
        { key: 0, name: "Pello" },
        { key: 0, name: "Aello" }
      ]
    };
  }
  reShuffle = () => {
    this.setState({
      items: this.state.items.map((item, index) => {
        if (index === 1) {
          return this.state.items[2];
        }
        if (index === 2) {
          return this.state.items[1];
        }
        return item;
      })
    });
  };
  render() {
    return (
      <div className="App" style={{ transition: "all 0.5s ease" }}>
        <button onClick={this.reShuffle}> Click Me </button>
        {this.state.items.map(item => <li key={item.key}> {item.name} </li>)}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

从这里可以轻松复制 https://codesandbox.io/s/648z61kvrn

在 React 元素上进行 css 转换时,您需要处理一个额外的状态:转换本身。

所以首先你改变你的组件的状态,这样 css transition 会改变它们,然后你改变 transition 结束时的状态,使 react 匹配结束时组件的样式过渡。

在这种情况下,当您单击以切换元素时,您必须计算一个 css 更改,它与切换元素具有相同的效果,然后当过渡结束时,您实际上切换了元素。

我们需要为项目添加一个顶部 属性 和一个正确的键:

constructor() {
super();
this.state = {
  items: [
    { key: 0, name: "Hello", top: 0 },
    { key: 1, name: "Sello", top: 0 }, // move this down
    { key: 2, name: "Wello", top: 0 }, // move this up
    { key: 3, name: "Zello", top: 0 },
    { key: 4, name: "Pello", top: 0 },
    { key: 5, name: "Aello", top: 0 }
  ],
  transitioning: {}
};
this.itemTransitionCount = 0;

}

记下 itemTransitionCount 以备后用。

首先将 <li> 元素包装在 <ul> 元素中,然后将 ref 附加到 <ul>:

ulLoad = c => (this.ulDom = c);
render() {
    ...
    <ul
        ref={this.ulLoad}
        style={{ position: "relative", display: "inline-block" }}
    >
        {
            this.state.items.map(item => (
                <li
                    key={item.key}
                    onTransitionEnd={this.itemTransitionEnd}
                    style={{
                        transition: item.top ? "top 0.5s ease" : "none",
                        position: "relative",
                        top: item.top
                    }}
                >
                    {item.name}
                </li>
            ))
        }
    </ul>
    ....
}

这将允许计算 children <li>s

的相对位置

然后以这种方式更改您的 reShuffle 处理程序:

reShuffle = () => {
this.setState({
  items: this.state.items.map((item, index) => {
    if (index === 1) {
      const top2 = this.ulDom.children[2].offsetTop;
      const top1 = this.ulDom.children[1].offsetTop;
      const top = top2 - top1;
      return { ...this.state.items[1], top };
    }
    if (index === 2) {
      const top1 = this.ulDom.children[1].offsetTop;
      const top2 = this.ulDom.children[2].offsetTop;
      const top = top1 - top2;
      return { ...this.state.items[2], top };
    }
    return item;
  })
});

此代码将启动我们要在 li 项目上设置的顶部过渡 => 切换动画将在下一次渲染期间发生。

我们用 onTransitionEnd 处理程序捕捉到两个动画的结尾(检查 evt.target !== evt.currentTarget 是因为过渡事件冒泡,计数是因为两个元素都会提高事件,但我们想在两个转换结束时执行一次):

itemTransitionEnd = evt => {
if (evt.target !== evt.currentTarget) return;
// alert("transitionEnd");
this.itemTransitionCount++;
if (this.itemTransitionCount < 2) return;
this.itemTransitionCount = 0;
this.setState({
  items: this.state.items.map((item, index) => {
    if (index === 1) {
      return { ...this.state.items[2], top: 0 };
    }
    if (index === 2) {
      return { ...this.state.items[1], top: 0 };
    }
    return item;
  })
});

};

上面的代码实际上在转换结束时切换了元素并重置了它们的相对顶部(转换计数也为下一次转换重置)。

这说明了 React 中 css 过渡动画的两步渲染。

Working sandbox here