You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中自动调整尺寸的输入框初始宽度计算错误原因排查

自动调整宽度的文本输入框首次渲染宽度计算异常问题

实现代码

ResizableInput 组件

const ResizableInput = () => {
  const width = useTextRenderWidth({
    text: currentName,
    className: 'text-lg font-display',
  });

  return (
    <input
      type={'text'}
      value={currentName}
      style={{ width }}
      onChange={({ target }) => setCurrentName(target.value)}
    />
  );
};

useTextRenderWidth 钩子

const useTextRenderWidth = ({ text, className }) => {
  const [width, setWidth] = useState(0);

  const elem = useMemo(() => {
    const span = document.createElement('span');

    span.style.position = 'fixed';
    span.style.whiteSpace = 'pre';
    span.style.top = '-100%';

    document.body.appendChild(span);

    return span;
  }, []);

  useEffect(() => {
    elem.innerText = text;
  }, [text]);
  useEffect(() => {
    if (!isNil(className)) {
      elem.className = className;
    }
  }, [className]);

  useEffect(() => {
    setWidth(elem.offsetWidth);
  }, [text, className]);

  return width;
};

遇到的问题

首次渲染时输入框宽度计算略小于所需值,但编辑内容(如添加再删除空格,无实质内容变化)或Vite热重载后,宽度计算恢复正常。DevTools中生成的span元素宽度显示正确,不清楚首次渲染无法设置正确宽度的原因,想了解是否有可监听的事件来捕获元素宽度正确设置的时机。

解决方案及分析

问题根源

首次渲染时,设置className和text后立即读取offsetWidth,此时浏览器可能还未完成字体加载、样式计算和重排,导致获取的宽度不是最终渲染后的真实宽度。而后续编辑或热重载时,字体已经加载完成,样式也已生效,所以宽度计算正确。

解决方法

  • 用requestAnimationFrame延迟读取宽度
    把读取宽度的逻辑放到浏览器下一次重绘周期执行,确保获取最终渲染后的宽度:

    useEffect(() => {
      const updateWidth = () => {
        setWidth(elem.offsetWidth);
      };
      const rafId = requestAnimationFrame(updateWidth);
      return () => cancelAnimationFrame(rafId);
    }, [text, className]);
    
  • 监听字体加载完成事件
    如果使用了自定义字体,可等待字体加载完成后再计算宽度,避免因字体未加载导致的宽度偏差:

    useEffect(() => {
      async function waitForFonts() {
        if ('fonts' in document) {
          await document.fonts.ready;
          setWidth(elem.offsetWidth);
        } else {
          // 降级方案:延迟一段时间再计算
          setTimeout(() => setWidth(elem.offsetWidth), 100);
        }
      }
      waitForFonts();
    }, [text, className]);
    
  • 合并逻辑减少异步顺序问题
    把设置文本、样式和计算宽度的逻辑合并到同一个useEffect,确保前序操作完成后再读取宽度:

    useEffect(() => {
      elem.innerText = text;
      if (!isNil(className)) {
        elem.className = className;
      }
      const rafId = requestAnimationFrame(() => {
        setWidth(elem.offsetWidth);
      });
      return () => cancelAnimationFrame(rafId);
    }, [text, className]);
    

可选优化

  • 给输入框设置最小宽度,避免内容为空时输入框过窄;
  • 在组件卸载时移除创建的span元素,避免内存泄漏:
    useEffect(() => {
      return () => {
        document.body.removeChild(elem);
      };
    }, [elem]);
    

内容的提问来源于stack exchange,提问作者k.meinkopf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:47:23