React的Textarea高度自适应
上次提到过我接触了一个新项目,是校友们策划的一个类ChatGPT的项目,我负责前端部分,用的是React+TailwindCSS的组合。 我这个刚接触React一个月的小白肯定是搓手等着上手、跃跃欲试。
像是ChatGPT、Claude,甚至是Discord这样的聊天室App,输入框都是能够让用户换行、输入代码块的。我们的项目也不例外。 但是,textarea
组件就算是默认单行,换行时也会向下增加高度,导致脱离原本的父容器,甚至跑到屏幕外面去。
最终结果
环境
先说父容器的样式,一个带了flex
的div
:
1 | <div className="flex flex-col h-full"> |
而 Textbox
组件的样式差不多是这样的:
1 | <div className="flex items-center w-full"> |
正常来说,textarea
的大小是朝下无限增长的,但这不是我们想要的,我们希望它能够朝上增长、挤压信息内容的高度,直到达到一定高度后出现滚动条。
解决方案
为什么要特意说到父容器是一个带了 flex
的 div
呢?因为这是解决问题的关键。
包含了信息内容的兄弟元素会铺满剩余没有被 Textbox
组件占用的空间,而 Textbox
组件的高度是可以动态修改的。
也就是说,我们可以通过监听 textarea
的 scrollHeight
属性,来动态修改 Textbox
整个组件的高度,最终保持让它一直待在父容器的里面。
先创建一个useRef
来引用textarea
:
1 | const textareaRef = useRef(null); |
我们还需要创建一个useState
来保存我们想要的高度:
1 | const [height, setHeight] = useState(40); |
这里的
40
(像素)是我自己设定的一个最小高度。
创建useEffect
来监听textarea
的scrollHeight
属性:
1 | useEffect(() => { |
在 handleResize
中,我们先将 textarea
的高度设置为 auto
,这样就可以让它自己决定高度,然后用 setHeight
来保存 scrollHeight
的值。
Math.max(40, textareaElement.scrollHeight)
是为了保证textarea
的高度不会小于40
像素,也就是我刚才说的,我自己设定的一个最小高度。
handleResize
需要在用户输入时触发,所以还要用 addEventListener
来监听 input
事件。
最后别忘了卸载监听器。
那么我们得到的 height
要用在哪里呢?Textbox
组件的最外层 div
上:
1 | <div className="flex items-center w-full" style={{ height: `${height}px` }}> |
每次用户输入,height
都会被更新,Textbox
组件的高度也会被更新。拥有着固定高度的 Textbox
组件能够在Flexbox布局中自由伸缩,装有信息内容的兄弟元素则会根据 Textbox
组件的高度自动调整。
不过呢还是有需求没能完成:Textbox
组件要在伸展到一定高度时出现滚动条。之后再说吧。
后话
其实之后测试时发现还是有问题的,原因很简单:
1 | onKeyDown={e => { |
因为没有添加 e.preventDefault()
导致每次发送消息时都会多出一行,scrollHeight
也会多出一行,最终导致 Textbox
组件的高度一直在增加、甚至超出父容器。
切记不要忘了啊!血的教训!让我白白修了一天的bug!
handleSize
也可以更新为:
1 | const handleResize = e => { |
这样当 scrollHeight
超过 232
时(在我的案例中也就是9行),就会停止增加高度、出现滚动条。