使用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;
关键修复点
- 移除对DOM
style.height的直接操作,所有高度变化由Framer Motion的animate属性控制 - 组件挂载时初始化高度为textarea的初始scrollHeight,避免
auto状态导致的布局不稳定 - 直接通过ref获取scrollHeight更新state,确保动画的数据源准确
- 基于ref的实时scrollHeight判断overflow状态,避免state更新延迟带来的判断偏差
内容的提问来源于stack exchange,提问作者Sayem
相关产品推荐
相关产品推荐

