React 中,一个根据文本量自动放缩的组件,怎么减少动画闪烁?

React 中,一个根据文本量自动放缩的组件,怎么减少动画闪烁?

import { useState, useEffect, useRef, FC } from 'react';

const AutoScalingText: FC<{ text: string }> = ({ text }) => {
  const [scale, setScale] = useState(1);
  const nodeRef = useRef<HTMLDivElement>(null);
  const parentNodeOffsetWidth = useRef<number>(0); //全局变量,免得每次都重新计算parentNodeOffsetWidth

  useEffect(() => {
    parentNodeOffsetWidth.current = (
      nodeRef.current!.parentNode as HTMLElement
    ).offsetWidth;
  }, []);

  useEffect(() => {
    const node = nodeRef.current;
    if (!node) return;

    // 每次text变化时,根据文本长度设定scale 范围
    const availableWidth = parentNodeOffsetWidth.current - 32;
    const actualWidth = node.offsetWidth;
    const actualScale = availableWidth / actualWidth;
    if (scale !== actualScale) {
      if (actualScale < 1) {
        setScale(actualScale);
      } else if (scale < 1) {
        setScale(1);
      }
    }
  }, [text]);

  return (
    <div
      className='auto-scaling-text'
      style={{ transform: `scale(${scale},${scale})` }}
      ref={nodeRef}
    >
      {text}
    </div>
  );
};

export default AutoScalingText;

求求了!
在线demo,可能在审核中:https://inscode.csdn.net/@weixin_41983540/React-TypeScript-Te...

阅读 1.8k
avatarAI BotBETA

在React中,减少动画闪烁的一个常见方法是确保DOM更新和样式变化尽可能平滑且高效。对于你的AutoScalingText组件,闪烁可能由于以下几个原因引起:

  1. 频繁的重渲染:每次文本变化时,组件都会重新计算scale并触发重渲染。
  2. 布局抖动:当scale变化时,可能会触发父容器的重新布局,进而影响子元素的尺寸,形成一个循环。
  3. CSS动画或变换的即时性transform属性的即时变化可能导致视觉上的不连续。

为了减少闪烁,你可以尝试以下策略:

1. 使用requestAnimationFrame

将scale的更新放在requestAnimationFrame中,这可以确保DOM的更新与浏览器的重绘和回流同步,从而可能减少闪烁。

useEffect(() => {
  const updateScale = () => {
    const node = nodeRef.current;
    if (!node) return;

    const availableWidth = parentNodeOffsetWidth.current - 32;
    const actualWidth = node.offsetWidth;
    const actualScale = availableWidth / actualWidth;
    if (scale !== actualScale) {
      if (actualScale < 1) {
        setScale(actualScale);
      } else if (scale < 1) {
        setScale(1);
      }
    }
  };

  window.requestAnimationFrame(updateScale);
}, [text, scale, parentNodeOffsetWidth.current]);

注意,这里将parentNodeOffsetWidth.current也加入了依赖数组,因为parentNodeOffsetWidth可能也会变化(尽管在你的例子中它被设置为只计算一次)。

2. 防抖(Debouncing)或节流(Throttling)

如果文本变化非常频繁,你可以考虑使用防抖或节流技术来减少重计算的频率。但在这个场景中,由于scale的计算依赖于DOM的实际尺寸,这可能不是最佳选择。

3. 优化CSS

确保CSS样式尽可能高效。例如,使用will-change属性来提示浏览器该元素可能会改变其变换,这可以优化性能。

.auto-scaling-text {
  will-change: transform;
}

4. 考虑使用CSS的font-size调整而非transform: scale()

如果可能的话,考虑使用CSS的font-size来直接调整文本大小,而不是通过transform: scale()。这可以避免一些与变换相关的性能问题。

5. 使用React的useLayoutEffect

如果scale的更新需要立即反映在DOM上,并且你希望这些更新在浏览器绘制之前完成,可以使用useLayoutEffect代替useEffect。但在这个例子中,useEffect加上requestAnimationFrame应该足够了。

结论

对于你的特定情况,使用requestAnimationFrame来同步DOM更新和样式变化可能是减少闪烁的最有效方法。同时,确保你的CSS样式和React组件的逻辑尽可能高效。

2 个回答
useLayoutEffect(() => {
  const node = nodeRef.current;
  if (!node) return;

  // 每次text变化时,根据文本长度设定scale 范围
  const availableWidth = parentNodeOffsetWidth.current - 32;
  const actualWidth = node.offsetWidth;
  const actualScale = availableWidth / actualWidth;
  if (scale !== actualScale) {
    if (actualScale < 1) {
      setScale(actualScale);
    } else if (scale < 1) {
      setScale(1);
    }
  }
}, [text]);

将 useEffect 换成 useLayoutEffect 在渲染前计算完就行了。

推荐问题
logo
Microsoft
子站问答
访问
宣传栏