Element-ui 之 Textarea 自适应文本高度功能的源码分析

一. 自适应高度功能的介绍

1.1 自适应高度功能描述

自适应高度功能是指文本域跟随字数的增加或减少,从而动态的调整文本域的高度。

1.2 自适应高度功能相关属性

在 element-ui 的 Input 组件中,设置了 type="textarea",再设置 autosize 属性,可以开启自适应文本高度功能。

序号 参数 说明 类型 可选值 默认值
1 autosize 自适应内容高度,只对 type=textarea 有效,可传入对象,如,{ minRows: 2, maxRows: 6 } boolean / object -- false

二. 自适应高度功能的具体实现

2.1 自适应高度功能的实现思路

创建一个隐藏的 textarea,放到 body 标签下,将 Textarea 组件的 value 值赋值给隐藏的 textarea,通过获取这个隐藏的 textarea 的 scrollHeightscrollHeight 会返回该元素在不使用滚动条时的高度),来设置组件上面的 textarea 的高度。

2.2 计算自适应高度的方法

在 element-ui 源代码中,calcTextareaHeight.js 文件的 calcTextareaHeight 方法用于计算 textarea 的动态高度。

计算的步骤分析如下:

  1. 首先获取 Input type="textarea" 组件的与元素大小有关全部 CSS 属性,将 textarea 的 box-sizing,上下边距的 padding,上下两边的 border 分别解构出来。
  2. 创建一个 textarea 元素,将刚刚获取到的组件全部的样式,以及隐藏元素的样式全部给创建的 textarea。
  3. 获取隐藏元素的 scrollHeight,返回的值是该元素在不使用滚动条的时候的 content 高度和 padding 整体高度,不包含 border 的高度。
  4. 判断是标准盒子模型还是IE盒子模型,标准盒子模型的高度需要减去 padding 的上下边距总和,IE盒子模型的高度需要加上上下的 border 的总和。
  5. value 值设置为空,计算出一行的高度,用 scrollHeight 减去上下两边的内边距,就是一行文字内容的高度。
  6. 如果传入了最小行数 minRows,则根据传入的最小行数计算出 textarea 的最小高度:
    (1)如果是IE盒子模型,则最小高度需要加上 paddingborder
    (2)取隐藏 textarea的高度和最小高度中较大的作为组件的最小高度 minHeight(取较大的是因为不能小于最小高度)。
  7. 如果传入了最大行数 maxRows,则根据传入的最大行数计算出 textarea 的最大高度:
    (1)如果是IE盒子模型,则最小高度需要加上 paddingborder
    (2)取隐藏 textareas的高度和最大高度中较小的作为组件的高度 height(取较小的是因为不能大于最大高度)。

具体代码如下:

js 复制代码
// 设置变量 hiddenTextarea 去存放隐藏的元素
let hiddenTextarea;

// 隐藏元素的 style
const HIDDEN_STYLE = `
  height:0 !important;
  visibility:hidden !important;
  overflow:hidden !important;
  position:absolute !important;
  z-index:-1000 !important;
  top:0 !important;
  right:0 !important
`;

// 影响 textarea 高度的全部相关的 CSS 属性
const CONTEXT_STYLE = [
  'letter-spacing',
  'line-height',
  'padding-top',
  'padding-bottom',
  'font-family',
  'font-weight',
  'font-size',
  'text-rendering',
  'text-transform',
  'width',
  'text-indent',
  'padding-left',
  'padding-right',
  'border-width',
  'box-sizing'
];

// 获取目标元素的 box-sizing、padding、border、以及其他与元素大小有关的全部属性
function calculateNodeStyling(targetElement) {
  const style = window.getComputedStyle(targetElement);

  const boxSizing = style.getPropertyValue('box-sizing');

  const paddingSize = (
    parseFloat(style.getPropertyValue('padding-bottom')) +
    parseFloat(style.getPropertyValue('padding-top'))
  );

  const borderSize = (
    parseFloat(style.getPropertyValue('border-bottom-width')) +
    parseFloat(style.getPropertyValue('border-top-width'))
  );
  // 首先获取传入的 textarea 的与元素大小有关全部CSS属性
  const contextStyle = CONTEXT_STYLE
    .map(name => `${name}:${style.getPropertyValue(name)}`)
    .join(';');

  return { contextStyle, paddingSize, borderSize, boxSizing };
}
// 计算 textarea 自适应高度的方法
export default function calcTextareaHeight(
  targetElement,
  minRows = 1,
  maxRows = null
) {
  // 设置一个隐藏的 textarea 用于获取其 scrollHeight
  if (!hiddenTextarea) {
    hiddenTextarea = document.createElement('textarea');
    document.body.appendChild(hiddenTextarea);
  }
  

  let {
    paddingSize,
    borderSize,
    boxSizing,
    contextStyle
  } = calculateNodeStyling(targetElement);

  hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
  hiddenTextarea.value = targetElement.value || targetElement.placeholder || '';

  // scrollHeight 返回该元素在不使用滚动条时的高度,包含元素内容content的高度,以及内边距padding的高度,但是
  let height = hiddenTextarea.scrollHeight;
  const result = {};

  if (boxSizing === 'border-box') {
    // 如果是『IE盒子模型』则高度需要加上边框的高度,因为IE盒子模型的高度是 content+padding+border
    height = height + borderSize;
  } else if (boxSizing === 'content-box') {
    // 如果是『标准盒子模型』则高度需要减去 padding 的高度,因为标准盒子模型的高度只有 content 的高度
    height = height - paddingSize;
  }

  // 将value设置为空,计算出1行的高度
  hiddenTextarea.value = '';
  let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;

  // 如果传入了最小行数 minRows
  if (minRows !== null) {
    // 根据传入的最小行数计算出最小高度
    let minHeight = singleRowHeight * minRows;
    // 如果是IE盒子模型,则最小高度需要加上 padding 和 border
    if (boxSizing === 'border-box') {
      minHeight = minHeight + paddingSize + borderSize;
    }
    // 取隐藏textarea和最小高度中较大的作为组件的最小高度 minHeight
    // 这样 textarea 在小于最小高度时取最小高度,在大于最小高度时取自适应高度
    height = Math.max(minHeight, height);
    result.minHeight = `${ minHeight }px`;
  }
  
  // 如果传入了最大行数 maxRows
  if (maxRows !== null) {
    // 则根据传入的最大行数计算出最大高度
    let maxHeight = singleRowHeight * maxRows;
    // 如果是IE盒子模型,则最大高度需要加上 padding 和 border
    if (boxSizing === 'border-box') {
      maxHeight = maxHeight + paddingSize + borderSize;
    }
    // 取隐藏textarea和最大高度中较小的作为组件的高度 height
    // 这样超过最大高度时出现滚动条,小于最大高度时自适应
    height = Math.min(maxHeight, height);
  }
  result.height = `${ height }px`;
  
  hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
  hiddenTextarea = null;
  return result;
};

2.3 计算自适应高度方法的调用时机

  1. mounted 钩子里要进行计算。
  2. 监听 value 值改变时需要去计算。
  3. Input 组件的 type 发生变化时需要去计算。
相关推荐
飞天大河豚42 分钟前
2025前端框架最新组件解析与实战技巧:Vue与React的革新之路
vue.js·react.js·前端框架
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS医疗报销系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
道不尽世间的沧桑2 小时前
第9篇:插槽(Slots)的使用
前端·javascript·vue.js
B站计算机毕业设计超人2 小时前
计算机毕业设计SpringBoot+Vue.jst房屋租赁系统(源码+LW文档+PPT+讲解)
vue.js·spring boot·后端·eclipse·intellij-idea·mybatis·课程设计
bin91533 小时前
DeepSeek 助力 Vue 开发:打造丝滑的滑块(Slider)
前端·javascript·vue.js·前端框架·ecmascript·deepseek
杰九3 小时前
【环境配置】maven,mysql,node.js,vue的快速配置与上手
java·vue.js·spring boot·mysql·node.js·maven
秋意钟3 小时前
Element UI日期选择器默认显示1970年解决方案
前端·javascript·vue.js·elementui
程序员黄同学4 小时前
请谈谈 Vue 中的 key 属性的重要性,如何确保列表项的唯一标识?
前端·javascript·vue.js
繁依Fanyi5 小时前
巧妙实现右键菜单功能,提升用户操作体验
开发语言·前端·javascript·vue.js·uni-app·harmonyos
web182854825125 小时前
nginx 部署前端vue项目
前端·vue.js·nginx