Cytrogen 的个人博客

万圣节恶魔的领地

马上订阅 Cytrogen 的个人博客 RSS 更新: https://cytrogen.icu/atom.xml

React 的 Textarea 高度自适应

2024年2月20日 12:11

上次提到过我接触了一个新项目,是校友们策划的一个类 ChatGPT 的项目,我负责前端部分,用的是 React + TailwindCSS 的组合。 我这个刚接触 React 一个月的小白肯定是搓手等着上手、跃跃欲试。

像是 ChatGPT、Claude,甚至是 Discord 这样的聊天室 App,输入框都是能够让用户换行、输入代码块的。我们的项目也不例外。 但是,textarea 组件就算是默认单行,换行时也会向下增加高度,导致脱离原本的父容器,甚至跑到屏幕外面去。

最终结果

result

环境

先说父容器的样式,一个带了 flexdiv

<div className="flex flex-col h-full">    <div className="flex-1">{/* 信息内容 */}</div>        {/* 主角 */}    <Textbox /></div>

Textbox 组件的样式差不多是这样的:

<div className="flex items-center w-full">    <textarea        name="message"        className="w-full resize-none"        placeholder="Type a message..."        rows="1"    />    <button>发送</button></div>

正常来说,textarea 的大小是朝下无限增长的,但这不是我们想要的,我们希望它能够朝上增长、挤压信息内容的高度,直到达到一定高度后出现滚动条。

解决方案

为什么要特意说到父容器是一个带了 flexdiv 呢?因为这是解决问题的关键。

包含了信息内容的兄弟元素会铺满剩余没有被 Textbox 组件占用的空间,而 Textbox 组件的高度是可以动态修改的。

也就是说,我们可以通过监听 textareascrollHeight 属性,来动态修改 Textbox 整个组件的高度,最终保持让它一直待在父容器的里面。

先创建一个 useRef 来引用 textarea

const textareaRef = useRef(null);// ... <textarea ref={textareaRef} />

我们还需要创建一个 useState 来保存我们想要的高度:

const [height, setHeight] = useState(40);

这里的 40 (像素)是我自己设定的一个最小高度。

创建 useEffect 来监听 textareascrollHeight 属性:

useEffect(() => {    const handleResize = () => {        const textareaElement = textareaRef.current;        textareaElement.style.height = "auto";        textareaElement.style.height = `${textareaElement.scrollHeight}px`;        setHeight(Math.max(40, textareaElement.scrollHeight));    }        const textareaElement = textareaRef.current;    textareaElement.addEventListener("input", handleResize);        return () => textareaElement.removeEventListener("input", handleResize);}, []);

handleResize 中,我们先将 textarea 的高度设置为 auto,这样就可以让它自己决定高度,然后用 setHeight 来保存 scrollHeight 的值。

Math.max(40, textareaElement.scrollHeight) 是为了保证 textarea 的高度不会小于 40 像素,也就是我刚才说的,我自己设定的一个最小高度。

handleResize 需要在用户输入时触发,所以还要用 addEventListener 来监听 input 事件。

最后别忘了卸载监听器。

那么我们得到的 height 要用在哪里呢?Textbox 组件的最外层 div 上:

<div className="flex items-center w-full" style={{ height: `${height}px` }}>    <textarea        name="message"        className="w-full resize-none"        placeholder="Type a message..."        rows="1"        ref={textareaRef}    />    <button>发送</button></div>

每次用户输入,height 都会被更新,Textbox 组件的高度也会被更新。拥有着固定高度的 Textbox 组件能够在 Flexbox...

剩余内容已隐藏

查看完整文章以阅读更多