textarea.style.height = 'auto' 必须在读取 scrollHeight 前执行,否则浏览器将上次固定高度视为约束,导致 scrollHeight 计算失真、高度越撑越高且删空不缩回;需配合 requestAnimationFrame、min-height/max-height/box-sizing 等 CSS 规则及 IE11 换行符兼容处理。

textarea.style.height = 'auto' 必须在读取 scrollHeight 前执行
直接写 textarea.style.height = textarea.scrollHeight + 'px' 会导致高度越撑越高,删空后也不收缩。根本原因是浏览器把上一次设置的固定 height 当作布局约束,干扰 scrollHeight 计算——尤其在 iOS Safari 和 IE11 上表现更顽固。
每次调整前必须重置为流式计算基准:
-
textarea.style.height = 'auto'是强制清空内联 height 的唯一可靠方式(''或undefined在某些浏览器下无效) - 这步不能省略,也不能挪到赋值之后;否则粘贴大段文本时底部留白、回车时光标跳动、删文字卡住都是典型症状
- 别用
offsetHeight或clientHeight替代——它们不含溢出内容,永远撑不开
input 事件监听 + requestAnimationFrame 是移动端刚需
仅绑 input 事件还不够。iOS Safari 对 scrollHeight 更新有微小延迟,尤其在 120Hz 屏或快速粘贴后,高度常卡在旧值。
必须用 requestAnimationFrame 等待渲染就绪:
立即学习“前端免费学习笔记(深入)”;
textarea.addEventListener('input', () => {
requestAnimationFrame(() => {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
});
});
注意:setTimeout(0) 不够稳;focus 或 change 事件不反映实时内容变化,完全不可靠。
CSS 的 min-height / max-height / box-sizing 缺一不可
JS 只负责动态设 style.height,但初始最小高度、最大上限、是否滚动,全靠 CSS 控制。漏掉任意一条,就会出现:空文本域塌成一条线、用户狂输时页面被顶飞、超限时内容被裁切。
至少要写这四条:
-
min-height: 80px—— 保证初始有可点击区域(min-height: 1.2em在 Safari 下可能偏小,不推荐) -
max-height: 300px—— 超出才触发滚动,不是可选配置 -
overflow-y: auto—— 不写这句,内容直接被裁掉 -
box-sizing: border-box—— 确保 padding 和 border 不额外加高;否则 JS 算出的高度和实际渲染不一致
顺手加上 resize: none; overflow: hidden;,防止用户拖拽破坏逻辑或隐藏滚动条导致 scrollHeight 失真。
IE11 和换行符归一化是隐藏雷区
IE11 把 \r\n 当一个字符处理,而现代浏览器统一按 \n 算,导致空行末尾的 scrollHeight 偏小约 2px。这不是 bug,是底层文本度量差异。
两种应对路径:
- 若后端不要求保留原始换行:
textarea.value = textarea.value.replace(/\r\n/g, '\n'),再算高度 - 若必须保留
\r\n(如日志类场景):const height = textarea.scrollHeight + (isIE11 ? 2 : 0) - 检测 IE11:
const isIE11 = !!window.MSInputMethodContext && !!document.documentMode
字体、line-height、padding 必须和 JS 计算时渲染完全一致——Chrome DevTools 里关掉 “user agent stylesheet” 再看 computed 值,才能确认真实高度。



















