textarea 自适应高度必须用 js 动态设置 style.height = 'auto' 后读取 scrollheight,而非依赖 rows 属性;因 rows 受字体、系统影响且 ie11 不同步,直接修改会引发重排、卡高、跳变等问题。

rows 属性本身不能让 textarea 自适应高度,它只设定初始行数;真正起效的是用 JS 动态读取 scrollHeight 并配合 style.height = 'auto' 重置高度 —— 否则删内容后高度卡死、移动端回退时错位、React/Vue 中 re-render 滚动跳变都会发生。
为什么直接改 rows 值不可靠
浏览器对 rows 的解析依赖默认行高和 padding,不同系统/字体下 rows="3" 实际像素高度可能差 15px 以上;rows 是 HTML 属性,不是 CSS 样式,用 JS 修改会触发 layout 重排,尤其在频繁输入时性能明显下降;更重要的是:IE11 及更早版本中,rows 修改后 scrollHeight 不同步更新,导致删内容后高度不收缩。
常见错误现象:textarea 输入多行后高度撑开,删掉几行,高度却没变,底部留大片空白。
- 不要用
elem.setAttribute('rows', n)替代高度控制 - 避免在 React/Vue 中把
rows绑定为响应式 prop(每次渲染都重设) - 若必须用
rows控制最小高度,请只设一次初始值(如rows="2"),后续交由 JS 管理style.height
scrollHeight 必须配合 style.height = 'auto' 才有效
直接写 elem.style.height = elem.scrollHeight + 'px' 在删除内容时大概率失效 —— 因为 scrollHeight 在内容未超出可视区时不会更新(MDN 明确说明:无滚动条时 scrollHeight ≈ clientHeight)。解决办法是先强制清空高度,让浏览器重新计算内容真实尺寸。
实操建议:
- 每次 input 触发时,第一行必须写
elem.style.height = 'auto' - 紧接着再读
elem.scrollHeight,此时值才可靠 - 记得减去上下
padding(例如getComputedStyle(elem).paddingTop),否则高度偏大 - 加防抖(如
debounce300ms)避免连续输入时过度重绘
移动端聚焦/键盘弹出时的高度挤压问题
iOS Safari 和部分安卓 WebView 中,textarea 聚焦后页面视口被键盘顶起,scrollHeight 计算值可能包含被遮挡区域,导致高度虚高;失焦瞬间又因视口恢复而缩回,造成“抽搐”感。
关键处理点:
- 监听
focus和blur事件,在focus后延迟 100ms 再执行 resize(等键盘动画完成) - 失焦时不要立即重算,而是缓存当前高度,
blur后 200ms 再恢复最小高度(如min-height: 80px) - 避免在
resize事件里调用自适应逻辑 —— 移动端键盘弹出会触发 window.resize,形成死循环 - CSS 上加
overflow-y: hidden,防止临时高度溢出引发横向滚动条
最易被忽略的点是:所有基于 scrollHeight 的方案都默认 textarea 有明确的 font-size 和 line-height;如果用了 rem/em 或继承了不稳定的父级字号,scrollHeight 就会漂移 —— 建议给 textarea 显式设置 font-size: 14px; line-height: 1.5;。











