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 发生变化时需要去计算。
相关推荐
苹果酱05671 小时前
Golang的数据库备份与恢复
java·vue.js·spring boot·mysql·课程设计
小画家~2 小时前
第五十九:子传父 defineEmits
前端·javascript·vue.js
itmacy8 小时前
Element UI-Select选择器结合树形控件终极版
vue.js·elementui
鱼樱前端11 小时前
vue3+three.js入门小demo
前端·vue.js
徐同保12 小时前
vue3后端管理项目,左侧菜单可以拖拽调整宽度
前端·javascript·vue.js
麦麦大数据14 小时前
vue+neo4j 四大名著知识图谱问答系统
vue.js·人工智能·python·django·问答系统·知识图谱·neo4j
计算机-秋大田14 小时前
基于Spring Boot的企业车辆管理系统设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·spring·课程设计
没有鸡汤吃不下饭16 小时前
vue3项目实现数据字典、下拉数据缓存最佳方案,解决同一下拉数据并发多次调用接口
前端·javascript·vue.js
全栈小董16 小时前
arco design vue 表格Table自定义筛选
前端·vue.js·arco design
Vvc_b17 小时前
Vue3的核心语法【未完】
前端·javascript·vue.js