如何實現 textarea 的 autoHeight 功能

這個功能還比較常見,用來獲取文本的長寬(避免了計算不許的問題),主要用於實現 textarea 自動變長。前端

image.png

能夠看到在咱們使用 textarea 的時候,有時候須要感知內容的高度,而後動態撐開。(elementUI 的 textarea 就提供了 autosize 的功能。)git

那咱們也想實現這樣的功能應該怎麼辦呢?github

  1. 獲取內容,而後統計字符個數估算。中文算兩個,英文算一個。可是仍是有問題的,好比說非等寬字體。
  2. 聰明的讀者已經看到了咱們中間的 div 效果,就是咱們想要的高度。也是 elementUI 的方案,建立一個擁有相一樣式的 div,而後獲取他的高度

構建相一樣式的 DOM

看上去這個方案是最妙的。那麼如何構建相同的DOM呢?segmentfault

  1. 既然要構建相同的 DOM,那麼咱們須要知道 DOM 長什麼樣子
    那麼如何獲取樣式呢?獲取class?獲取style?
    nonono,咱們要用 window.getComputedStyle(el),而後就能夠快樂的拿到計算後的屬性。
  2. 以後咱們須要知道什麼屬性會影響字體排列
    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']
  3. 由於咱們須要從新搞一個DOM節點,並且咱們不但願這個過程被用戶看到,因此咱們要隱藏起來。有什麼方案呢?微信

    • display:none 這個是不行的,由於 none 以後不會繪製了。也就獲取不到寬高了。
    • opacity:0 這個能夠
    • visibility: hidden; 這個也能夠
    • height:0;overflow:hidden 這個也能夠,獲取scrollHeight
    • z-index:-999 這也能夠的。
    • position:absolute;top:-9999px;left:-9999px 也是能夠的

elementUI 實現

https://github.com/ElemeFE/element/blob/dev/packages/input/src/calcTextareaHeight.jsapp

let hiddenTextarea;

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
`;

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'
];

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'))
  );

  const contextStyle = CONTEXT_STYLE
    .map(name => `${name}:${style.getPropertyValue(name)}`)
    .join(';');

  return { contextStyle, paddingSize, borderSize, boxSizing };
}

export default function calcTextareaHeight(
  targetElement,
  minRows = 1,
  maxRows = null
) {
  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 || '';

  let height = hiddenTextarea.scrollHeight;
  const result = {};

  if (boxSizing === 'border-box') {
    height = height + borderSize;
  } else if (boxSizing === 'content-box') {
    height = height - paddingSize;
  }

  hiddenTextarea.value = '';
  let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;

  if (minRows !== null) {
    let minHeight = singleRowHeight * minRows;
    if (boxSizing === 'border-box') {
      minHeight = minHeight + paddingSize + borderSize;
    }
    height = Math.max(minHeight, height);
    result.minHeight = `${ minHeight }px`;
  }
  if (maxRows !== null) {
    let maxHeight = singleRowHeight * maxRows;
    if (boxSizing === 'border-box') {
      maxHeight = maxHeight + paddingSize + borderSize;
    }
    height = Math.min(maxHeight, height);
  }
  result.height = `${ height }px`;
  hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
  hiddenTextarea = null;
  return result;
};

微信公衆號:前端linong

clipboard.png

相關文章
相關標籤/搜索