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 发生变化时需要去计算。
相关推荐
|晴 天|3 小时前
Vue 3 + LocalStorage 实现博客游戏化系统:成就墙、每日签到、积分商城
前端·vue.js·游戏
前端那点事6 小时前
Vue响应式原理|从底层实现到面试考点,一文吃透(Vue2+Vue3全解析)
前端·vue.js
walking9576 小时前
Vite 打包优化终极指南:从 30MB 到 800KB 的性能飞跃
前端·vue.js·vite
|晴 天|7 小时前
Vue 3 实战:打造可拖拽歌词、播放列表的嵌入式音乐播放器
前端·javascript·vue.js
Liu.7747 小时前
Vue 3 开发中遇到的报错(2)
前端·javascript·vue.js
|晴 天|8 小时前
Vue 3 实现实时通知系统:支持未读计数、红点提醒、一键已读
javascript·vue.js·ecmascript
前端那点事8 小时前
Vue3 Tree-Shaking 原理解析
前端·vue.js
爱怪笑的小杰杰8 小时前
uni-app Vue3 国际化最佳实践:告别应用重启,优雅实现多语言切换
前端·vue.js·uni-app
踩着两条虫10 小时前
VTJ.PRO 新手入门:从环境搭建到 AI 生成首个 Vue3 应用
前端·javascript·数据库·vue.js·人工智能·低代码
前端那点事11 小时前
Vue3 defineModel 完全不破坏单向数据流!底层原理+实战解析
vue.js