【问题标题】:CSS transition when sorting a list排序列表时的 CSS 过渡
【发布时间】:2019-09-24 13:15:44
【问题描述】:

当项目从非活动状态变为活动状态时,我正在尝试在表格的行上创建一个简单的 CSS 背景过渡。

过渡应该从蓝色(活动 === false)淡化为粉红色(活动 === true)。

当从蓝色变为粉红色并且顺序发生变化时,过渡按预期工作,但当从粉红色变为蓝色并且顺序发生变化时,则不会。

我为我的问题做了一个简单的例子https://codesandbox.io/s/simple-react-8pf4s

提前感谢您的帮助!

【问题讨论】:

    标签: css reactjs css-transitions


    【解决方案1】:

    这可能就是 React 处理列表协调的方式。

    当顶部项变为非活动状态时,它首先从 DOM 中移除,然后作为新子项附加。这就是为什么没有过渡。当您将底部项目变为活动状态时,过渡会起作用,因为它再次是要移除的顶部项目,因此过渡项目保留在 DOM 中。

    在这种特殊情况下,在表行上设置键并没有帮助,因为据我从this 得知,它们仅用于优化,实际上并不能保证相同的 DOM 元素会被重用.

    您可能会偶然发现here 提到的相同问题。

    您可以查看一些用于转换列表的库以找到潜在的解决方案。例如。 react-flip-move

    我的一部分希望我错了,因为它有点糟糕,让一个简单的事情变得相当复杂。

    【讨论】:

    • 谢谢 Z!是的,这是有道理的。是的,这很糟糕!
    【解决方案2】:

    我能够为您解决排序问题,但真正的难题是 CSS 问题..

    问题与keys 和创建项目映射有关。根据我的理解,React 会将转换应用到特定键。在切换到非活动状态后,就像 React 将其视为全新的节点,由于某种原因,它没有正确应用过渡..

    我不确定为什么这是一种方式,但不是另一种方式,这对我来说真的很奇怪......我无法在网上找到解决方案,我测试了一堆东西......你应该看看 CSS使用 React 和键进行转换..

    话虽如此,我确实想出了一些“hacky”方法来实现这一点..

    CSS 作为 JS 对象:

    const { useState, useEffect } = React;
    const { render } = ReactDOM;
    
    const styles = {
      fontFamily: "sans-serif",
      textAlign: "center"
    };
    
    const sourceData = [
      {
        id: "es",
        1: "uno",
        2: "dos",
        3: "tres",
        active: true
      },
      {
        id: "de",
        1: "eine",
        2: "zwei",
        3: "drei",
        active: false
      },
      {
        id: "abc",
        1: "a",
        2: "b",
        3: "c",
        active: false
      }
    ];
    
    const activeClass = {
      background: "pink",
      color: "blue",
      transition: "all 1s"
    };
    
    const inactiveClass = {
      background: "blue",
      color: "pink",
      transition: "all 1s"
    };
    
    const App = () => {
      const [data, setData] = useState(sourceData);
    
      useEffect(() => {
        setClassNames();
      }, [data]);
    
      const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
    
      const sortAllData = d => [
        ...sortData(d.filter(i => i.active)),
        ...sortData(d.filter(i => !i.active))
      ];
    
      const handleToggle = index => event => {
        let clone = [...data];
        clone[index].active = !clone[index].active;
        setData(sortAllData(clone));
      };
    
      const setClassNames = () => {
        let actives = document.querySelectorAll(`[dataactive=${true}]`);
        let inactives = document.querySelectorAll(`[dataactive=${false}]`);
        setTimeout(() => {
          actives.forEach(a => {
            Object.keys(activeClass).forEach(k => a.style[k] = activeClass[k])
          });
          inactives.forEach(ina => {
            Object.keys(inactiveClass).forEach(k => ina.style[k] = inactiveClass[k])
          });
        }, 10);
      };
    
      return (
        <div style={styles}>
          <table>
            <thead>
              <tr>
                <td>Active?</td>
                <td>1</td>
                <td>2</td>
                <td>3</td>
              </tr>
            </thead>
            <tbody>
              {data.map((d, index) => {
                return (
                  <tr dataactive={d.active.toString()} key={d.id} id={d.id}>
                    <td>{d.active.toString()}</td>
                    <td>{d[1]}</td>
                    <td>{d[2]}</td>
                    <td>{d[3]}</td>
                    <td>
                      <button onClick={handleToggle(index)}>TOGGLE</button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      );
    };
    
    render(<App />, document.body);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>

    使用 .CSS 文件:

    const { useState, useEffect } = React;
    const { render } = ReactDOM;
    
    const styles = {
      fontFamily: "sans-serif",
      textAlign: "center"
    };
    
    const sourceData = [
      {
        id: "es",
        1: "uno",
        2: "dos",
        3: "tres",
        active: true
      },
      {
        id: "de",
        1: "eine",
        2: "zwei",
        3: "drei",
        active: false
      },
      {
        id: "abc",
        1: "a",
        2: "b",
        3: "c",
        active: false
      }
    ];
    
    
    const App = () => {
      const [data, setData] = useState(sourceData);
    
      useEffect(() => {
        setClassNames();
      }, [data]);
    
      const sortData = d => d.sort((a, b) => (a[1] < b[1] ? -1 : 1));
    
      const sortAllData = d => [
        ...sortData(d.filter(i => i.active)),
        ...sortData(d.filter(i => !i.active))
      ];
    
      const handleToggle = index => event => {
        let clone = [...data];
        clone[index].active = !clone[index].active;
        setData(sortAllData(clone));
      };
    
      const setClassNames = () => {
        let actives = document.querySelectorAll(`[dataactive=${true}]`);
        let inactives = document.querySelectorAll(`[dataactive=${false}]`);
        setTimeout(() => {
          actives.forEach(a => a.className = "active");
          inactives.forEach(ina => ina.className = "inactive");
        }, 10);
      };
    
      return (
        <div style={styles}>
          <table>
            <thead>
              <tr>
                <td>Active?</td>
                <td>1</td>
                <td>2</td>
                <td>3</td>
              </tr>
            </thead>
            <tbody>
              {data.map((d, index) => {
                return (
                  <tr dataactive={d.active.toString()} key={d.id} id={d.id}>
                    <td>{d.active.toString()}</td>
                    <td>{d[1]}</td>
                    <td>{d[2]}</td>
                    <td>{d[3]}</td>
                    <td>
                      <button onClick={handleToggle(index)}>TOGGLE</button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      );
    };
    
    render(<App />, document.body);
    .active {
      background: pink;
      color: blue;
      transition: all 1s;
    }
    .inactive {
      background: blue;
      color: pink;
      transition: all 1s;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>

    【讨论】:

    • 感谢您的回复和示例!不确定我真的想在制作中使用这些,但我会权衡我的选择。 @Zdenek 的回答在技术上是正确的
    • 我不能说我怪你。这让我很困扰,这很难实现。不过,很高兴你得到了你正在寻找的答案。干杯!
    【解决方案3】:

    这是一种奇怪的行为,但我假设当 react 控制将 dom 节点插入到新位置时,该类已经添加,因此会立即呈现。为了解决这个问题,我创建了一个关键帧动画和一堆 id,这些 id 正在动画中应用这个动画。我可以通过在创建时记录 ref 来确认相同的元素正在被重用(而不是被销毁和重新创建)。

    https://codesandbox.io/s/objective-dijkstra-ifren

    const App = () => {
      const [animating, setAnimating] = useState([]);
      const removeAnimation = id => {
        setAnimating(animating.filter(x => x !== id));
      };
    
      const [data, setData] = useState(sourceData);
      const toggle = id => {
        let data2 = [...data];
        const index = data.findIndex(x => x.id === id);
        data2[index] = { ...data2[index], active: !data2[index].active };
        setAnimating([...animating, data2[index].id]);
        data2 = [...data2.filter(x => x.active), ...data2.filter(x => !x.active)];
        setData(data2);
      };
    
      return (
        <div className="App">
          {data.map(d => (
            <div
              key={d.id}
              className={cl(
                "row",
                d.active && "row_active",
                !d.active && animating.includes(d.id) && "row_hasAnimation",
                d.active && animating.includes(d.id) && "row_active_hasAnimation"
              )}
              onAnimationEnd={() => removeAnimation(d.id)}
            >
              <div className="cell">{d[1]}</div>
              <div className="cell">{d[2]}</div>
              <div className="cell">{d[3]}</div>
              <div className="cell">
                <button onClick={() => toggle(d.id)}>Toggle</button>
              </div>
            </div>
          ))}
        </div>
      );
    };
    

    【讨论】:

    • 数据需要排序。 active 顶部的项目。如果我使用data 而不是sortedData 那么顺序不会改变
    • 作为测试,您可以将其更改为 const sortedData = [...data.filter(x =&gt; x.active),...data.filter(x =&gt; !x.active)]; 并让我知道这是否是所需的行为,否则我认为我不明白所需的过渡应该是什么样子
    • 我已经更新了我的示例,所以这两个项目都是蓝色(未激活)开始。如果您单击底部,它将向上移动并过渡到粉红色。这是预期的效果并且正在发挥作用。如果您将它们都设为粉红色,然后单击顶部的,它将向下移动并变为蓝色,但没有过渡。这就是问题所在。
    • 奇怪。我重新创建了您的示例以排除任何奇怪的错误:codesandbox.io/s/ecstatic-rgb-lb2mz 但它仍然存在问题。我用类组件 codesandbox.io/s/intelligent-curran-17xls 创建它,问题仍然存在:/
    • 查看更新的答案以及切换活动/非活动的动画。不是最好的解决方案,但可以避免直接与 dom 混淆
    猜你喜欢
    • 2023-02-21
    • 1970-01-01
    • 2014-11-17
    • 1970-01-01
    • 2015-06-22
    • 2016-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多