【问题标题】:re-triggering css animations with react用反应重新触发css动画
【发布时间】:2021-12-28 22:17:43
【问题描述】:

我有一个表格中的项目列表。由于某些外部事件,我希望某个项目的行以突出显示的方式短暂闪烁。

我有一个 CSS 动画 fadingHighlight 来实现所需的视觉效果,当有事件发生时,我在所需的行上设置一个类 last-updated 以触发动画。

但是,当同一行连续多次更新时,只有第一次更新会导致闪烁。 (我相信这是因为 react 将 last-updated 类保留在行上而不是重新渲染它,因此 css 不会重新启动动画。)

如果同一项目连续多次更新,如何重新触发动画?

演示和代码:https://codesandbox.io/s/pensive-lamarr-d71zh?file=/src/App.js

反应的相关部分:

const Item = ({ id, isLastUpdated }) => (
  <div className={isLastUpdated ? "last-updated" : ""}>Item {id}</div>
);

const App = () => {
  const [lastUpdatedId, setLastUpdatedId] = React.useState(undefined);

  const itemIds = ["1", "2", "3"];
  return (
    <div className="App">
      <h2>The list of items</h2>
      {itemIds.map((id) => (
        <Item key={id} id={id} isLastUpdated={lastUpdatedId === id} />
      ))}

      <h2>Trigger updates</h2>
      {itemIds.map((id) => (
        <button onClick={() => setLastUpdatedId(id)}>Update item {id}</button>
      ))}
    </div>
  );
}

样式:


@keyframes fadingHighlight {
  0% {
    background-color: #ff0;
  }
  100% {
    background-color: #fff;
  }
}

.last-updated {
  animation: fadingHighlight 2s;
}

【问题讨论】:

    标签: css reactjs css-animations


    【解决方案1】:

    在这里,我们与 React 的原则作斗争:不要更新不会改变的东西,我从来没有做过这样的事情,但它似乎可以解决你的问题。 p>

    由于等待代码运行的反应行为,然后将结果与当前值进行比较,这将不起作用:

    // Let suppose it is currently `1`.
    
    setLastUpdatedId(undefined); // <-- it does not apply.
    setLastUpdatedId(1);         // <-- it does not trigger any changes.
    

    解决方案(某种):

    我使用 useEffect 和辅助状态 (waitAndHold) 来处理 useStateuseEffect 的异步行为,而不是仅仅设置与 setLastUpdatedId 一致的新值。所以我们可以重置为undefined,等待更改生效,然后再次重置为与之前相同的值。

    工作解决方案here

    App 的新功能:

    const [waitAndHoldId, setWaitAndHoldId] = React.useState([]);
    
    useEffect(() => {
      setLastUpdatedId(waitAndHoldId[0]);
    }, [waitAndHoldId]);
    
    function resetLastUpdatedId(id) {
      if (lastUpdatedId !== id) {
        // If it's different: just set it and we are done.
        setLastUpdatedId(id);
      } else {
        // If not:
        setWaitAndHoldId([id]);
    
        /**
         * Setting an array will always trigger the useEffect (even if
         * it remains the same). The useEffect will take an instant to
         * run. Meanwhile, force the `lastUpdatedId` to reset with a
         * different value (e.g.: undefined).
         */
    
        // This will run before the useEffect.
        setLastUpdatedId(undefined);
      }
    }
    
    // ...
    
    <button onClick={() => resetLastUpdatedId(id)}>
      Update item {id}
    </button>
    

    【讨论】:

      【解决方案2】:

      更新

      这个解决方案有效,但我从队友那里得到了一些反馈,set... 函数通常不应该在渲染循环中调用,而应该在 useEffect 中调用。当我进行更改时,演示仍然有效,但在我们的生产代码中却没有。

      我的猜测是,依赖多个 useEffect 会导致 React 的事件循环内部对这些效果调用的顺序以及它们何时折叠为单个渲染步骤产生一定的敏感性。

      避开这个问题导致this answer(这也涉及更少的代码。)

      原答案

      Leo's answer(尤其是useEffect的使用)有效!它让我找到了一个经过修订的更新解决方案 (demo here),它具有一些理想的属性。

      与 Leo 回答的主要变化是每个Item 组件现在负责独立管理高亮效果的状态,并且需要通过最近更新的时间。 (或者,更准确地说,它需要在每次更新时传递一些新值,但时间似乎是最自然的。)

      这种方法的一个很好的结果是可以同时突出显示多个项目(例如,如果一个项目的更新在另一个项目的突出显示消失之前出现)。

      const Item = ({ id, updateTime }) => {
        const [lastUpdateTime, setLastUpdateTime] = React.useState(undefined);
        const [showHighlight, setShowHighlight] = React.useState(false);
        const [triggerHighlight, setTriggerHighlight] = React.useState(false);
      
        if (updateTime && updateTime !== lastUpdateTime) {
          // Update time has changed! 
          setLastUpdateTime(updateTime);
          // Remove the highlighting class so that re-adding
          // it restarts the animation.
          setShowHighlight(false);
          // And record that we need to re-add the class (but don't do it yet).
          setTriggerHighlight(true);
        }
      
        useEffect(() => {
          if (triggerHighlight) {
            // Re-add the highlight class inside useEffect so 
            // that it will happen in a separate render step.
            setShowHighlight(true);
            setTriggerHighlight(false);
          }
        }, [triggerHighlight]);
      
        return <div className={showHighlight ? "updated" : ""}>Item {id}</div>;
      };
      

      我们需要应用级状态来跟踪最近的更新时间:

        const [updateTimes, setUpdateTimes] = React.useState({});
      
        // ...
      
              <Item key={id} id={id} updateTime={updateTimes[id]} />
      
        // ...
              <button
                onClick={() => {
                  setUpdateTimes({ ...updateTimes, [id]: Date.now() });
                }}
              >
      
         // ...
      
      

      【讨论】:

      • 我绝对同意让每个元素对自己的行为负责,这是一个更优雅的解决方案。很高兴它对您有所帮助!
      【解决方案3】:

      (再次感谢@leo this answer 帮助我找到了这个!)

      我得到了想要的行为(强制动画重新启动)

      1. 立即删除高亮类,然后
      2. 为添加高亮类设置超时(10 毫秒似乎没问题)。

      超时似乎迫使 react 单独渲染没有高亮类的组件,然后再次使用高亮类,导致动画重新启动。 (如果没有超时,react 可能会将这些更改合并到一个渲染步骤中,导致 DOM 将整个事情视为无操作。)

      每个Item 管理自己的突出显示状态的这种方法的一个很好的结果是可以同时突出显示多个项目(例如,如果一个项目的更新在另一个项目的突出显示消失之前进入)。

      Demo here

      const Item = ({ id, updateTime }) => {
        const [showHighlight, setShowHighlight] = React.useState(false);
      
        // By putting `updateTime` in the dependency array of `useEffect,
        // we re-trigger the highlight every time `updateTime` changes.
        useEffect(() => {
          if (updateTime) {
            setShowHighlight(false);
            setTimeout(() => {
              setShowHighlight(true);
            }, 10);
          }
        }, [updateTime]);
      
        return <div className={showHighlight ? "updated" : ""}>Item {id}</div>;
      };
      
      const App = () => {
        // tracking the update times at the top level
        const [updateTimes, setUpdateTimes] = React.useState({});
      
        // ...
              <Item key={id} id={id} updateTime={updateTimes[id]} />
      
        // ...
              <button
                onClick={() => {
                  setUpdateTimes({ ...updateTimes, [id]: Date.now() });
                }}
              >
         // ...
      }
      
      

      【讨论】:

        猜你喜欢
        • 2020-11-20
        • 2013-05-10
        • 2018-07-28
        • 1970-01-01
        • 2011-10-04
        • 2011-06-15
        • 2019-01-03
        • 1970-01-01
        • 2020-03-18
        相关资源
        最近更新 更多