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

使用Framer Motion实现textarea高度动画时首行文本闪烁问题求助

解决React+Framer Motion实现textarea高度动画时的文本闪烁问题

我在React中使用Framer Motion实现textarea的高度动画,组件初始为1行,当用户输入内容超过首行时自动展开。但存在视觉bug:高度增加时,首行文本会短暂闪烁,表现为向上跳动后随高度动画回落。

原实现代码

import { motion } from 'motion/react';
import { useRef, useState } from 'react';
import FormFieldStyles from './App.module.css';

const Message = () => {
  const textareaRef = useRef<HTMLTextAreaElement>(null);

  const [height, setHeight] = useState<'auto' | number>('auto');

  const handleInput = () => {
    const textarea = textareaRef.current;
    if (textarea) {
      textarea.style.height = 'auto'; // Reset height
      textarea.style.height = `${textarea.scrollHeight}px`; // Set height to fit content

      setHeight(textarea.scrollHeight);
    }
  };

  return (
    <>
      <div className={`${FormFieldStyles.inputContainer}`}>
        <motion.textarea
          onInput={handleInput}
          rows={1}
          ref={textareaRef}
          autoComplete="off"
          className={`${FormFieldStyles.input} ${FormFieldStyles.textarea}`}
          animate={{ height }}
          style={{
            overflow:
              typeof height === 'number' && height > 96 ? 'auto' : 'hidden',
          }}
        />
      </div>
    </>
  );
};

export default Message;

问题原因

直接操作DOM的style.height和Framer Motion的animate属性控制高度产生了冲突:handleInput里先重置textarea高度为auto再设为scrollHeight,这会让DOM先同步更新高度,随后Framer Motion再触发动画过渡,导致文本出现跳动闪烁的视觉bug。另外初始状态用'auto'和数值类型切换也会引发布局不稳定。

修复方案

完全交由Framer Motion控制高度,不再直接操作DOM样式,通过ref获取scrollHeight更新状态即可:

import { motion } from 'motion/react';
import { useRef, useState, useEffect } from 'react';
import FormFieldStyles from './App.module.css';

const Message = () => {
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  // 初始高度设为textarea的初始scrollHeight,避免auto带来的布局波动
  const [height, setHeight] = useState<number>(0);

  // 组件挂载时初始化高度
  useEffect(() => {
    const textarea = textareaRef.current;
    if (textarea) {
      setHeight(textarea.scrollHeight);
    }
  }, []);

  const handleInput = () => {
    const textarea = textareaRef.current;
    if (textarea) {
      // 直接获取scrollHeight更新状态,交给Framer Motion处理动画
      setHeight(textarea.scrollHeight);
    }
  };

  return (
    <div className={`${FormFieldStyles.inputContainer}`}>
      <motion.textarea
        onInput={handleInput}
        rows={1}
        ref={textareaRef}
        autoComplete="off"
        className={`${FormFieldStyles.input} ${FormFieldStyles.textarea}`}
        // 初始高度和目标高度都用数值,保证动画平滑
        initial={{ height }}
        animate={{ height }}
        style={{
          // 基于scrollHeight判断是否显示滚动条,避免依赖state更新延迟
          overflow: textareaRef.current?.scrollHeight > 96 ? 'auto' : 'hidden',
          // 移除默认的resize,避免干扰动画
          resize: 'none'
        }}
      />
    </div>
  );
};

export default Message;

关键修复点

  • 移除对DOMstyle.height的直接操作,所有高度变化由Framer Motion的animate属性控制
  • 组件挂载时初始化高度为textarea的初始scrollHeight,避免auto状态导致的布局不稳定
  • 直接通过ref获取scrollHeight更新state,确保动画的数据源准确
  • 基于ref的实时scrollHeight判断overflow状态,避免state更新延迟带来的判断偏差

内容的提问来源于stack exchange,提问作者Sayem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:15:15