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 发生变化时需要去计算。
相关推荐
真滴book理喻26 分钟前
Vue(四)
前端·javascript·vue.js
不是鱼2 小时前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
开心工作室_kaic2 小时前
springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
前端·javascript·vue.js
川石教育2 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
搏博2 小时前
使用Vue创建前后端分离项目的过程(前端部分)
前端·javascript·vue.js
isSamle2 小时前
使用Vue+Django开发的旅游路书应用
前端·vue.js·django
ss2733 小时前
基于Springboot + vue实现的汽车资讯网站
vue.js·spring boot·后端
武昌库里写JAVA4 小时前
浅谈怎样系统的准备前端面试
数据结构·vue.js·spring boot·算法·课程设计
TttHhhYy4 小时前
uniapp+vue开发app,蓝牙连接,蓝牙接收文件保存到手机特定文件夹,从手机特定目录(可自定义),读取文件内容,这篇首先说如何读取,手机目录如何寻找
开发语言·前端·javascript·vue.js·uni-app
CodeChampion6 小时前
61.基于SpringBoot + Vue实现的前后端分离-在线动漫信息平台(项目+论文)
java·vue.js·spring boot·后端·node.js·maven·idea