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 发生变化时需要去计算。
相关推荐
前端大白话19 分钟前
震惊!90%前端工程师都踩过的坑!computed属性vs methods到底该怎么选?一文揭秘高效开发密码
前端·vue.js·设计模式
HuaHua的世界25 分钟前
说说Vue 模板是如何编译的?
vue.js·面试
Jet_closer_burning26 分钟前
axios封装
前端·javascript·vue.js·react.js·ajax
7643326 分钟前
Nuxt3-Vue3
前端·javascript·vue.js
佛系菜狗1 小时前
element-ui、element-plus表单resetFields()无效的坑
前端·javascript·vue.js
梦境之冢4 小时前
在 Vue3 中封装的 Axios 实例中,若需要为部分接口提供手动取消请求的功能
javascript·vue.js
qq_456001655 小时前
在Vue3中,如何在父组件中使用v-model与子组件进行双向绑定?
前端·javascript·vue.js
可爱的秋秋啊8 小时前
vue3,element ui框架中为el-table表格实现自动滚动,并实现表头汇总数据
前端·vue.js·笔记·elementui
HED11 小时前
VUE项目发版后用户访问的仍然是旧页面?原因和解决方案都在这啦!
前端·vue.js
清风细雨_林木木12 小时前
Vue开发网站会有“#”原因是前端路由使用了 Hash 模式
前端·vue.js·哈希算法