實現高度「聽話」的多行文本輸入框

1、前言

  經過建立 textarea 標籤,而且指定其 rows 和 cols 屬性,就能夠建立一個多行文本輸入框。html

  可是當輸入的內容超過指定的 rows 以後,就會出現滾動條,若是用戶想要查看所有內容,那就必須來回的拖動滾動條。並且這個滾動條只有在用戶滾動的時候纔會出現,在一些狀況下,用戶可能並不知道該區域有更多的內容。前端

  一般狀況下,爲了給用戶一個良好的體驗,須要讓這個多行文本輸入框的高度自適應,從而避免滾動條帶來的問題。git

2、高度自適應

  實現高度自適應的文本輸入框的思路很簡單:監聽輸入相關的事件,獲取到元素的內容高度,修改 textarea 的固定高度。github

  其中涉及不少基礎的知識,也就是咱們常說的細節問題處理:web

一、scrollHeight

  scrollHeight 這個只讀屬性是一個元素內容高度的度量,包括因爲溢出的視圖中不可見的內容。瀏覽器

  scrollHeight 包含元素的 padding,可是不包含元素的 border 和 margin 。當元素中不存在溢出內容,則 scrollHeight 與 clientHeight 是相同的。函數

  接下來只要將獲取到的 scrollHeight 屬性值賦給元素樣式中的 height 屬性,是否是就能夠動態的更改高度呢?固然,事情並無那麼簡單,這裏又要引出另外一個基礎知識點。優化

二、box-sizing

  CSS 中的盒模型基本上是常考的一個知識點,CSS 中能夠經過設置 box-sizing 屬性值,從而更改盒模型高度和寬度的計算,下面以高度爲例:ui

  • content-box:是默認值。若是你設置一個元素的高度爲100px,意味着元素內容區域的高度爲100px,若是再設置 padding 和 border ,那麼最終元素的高度爲 100px + border-top + border-bottom + padding-top + padding-bottom 。
  • border-box:若是你設置一個元素的高度爲100px,則意味着元素的最終高度就是100px,而元素內容區域的高度爲 100px - border-top - border-bottom - padding-top - padding-bottom 。

  因而可知,爲元素設置樣式中的 height 屬性時,須要弄清楚元素的 box-sizing 、 padding 以及 border。this

三、getComputedStyle

  對於前端新手來講,要獲取到元素樣式的 box-sizing 屬性值,可能第一時間會想到:

document.getElementById('demo').style.boxSizing
複製代碼

  可是大部分狀況下,該屬性獲取的是空值,由於它只可以獲取行內樣式,若是 style 屬性中並無設置 boxSizing 屬性值,那天然就是空值。

  在 CSS 中,開發者能夠經過不少方式去設置元素的樣式,而且它們的優先級各不相同,那麼就須要一個 API 來肯定元素最終的樣式,而 Window.getComputedStyle() 方法正是所以而生。

  Window.getComputedStyle() 方法返回一個實時的 CSSStyleDeclaration 對象,經過調用其 getPropertyValue() 方法,獲取相應的屬性值:

const style = window.getComputedStyle(el)
  style.getPropertyValue('box-sizing')
複製代碼
四、實現

  有了上述三個知識點的補充,接下來就是代碼實現:

function AutoSize (el) {
  if (!(this instanceof AutoSize)) {
    return new AutoSize(el)
  }
  if (!el) {
    throw new Error('element can not be empty')
  }
  if (typeof el === 'string') {
    el = document.querySelector(el)
  }
  this.el = el
  const attrs = ['box-sizing', 'padding-top', 'padding-bottom', 'border-top', 'border-bottom']

  // 初始化信息
  this.heightOffset = 0
  const style = window.getComputedStyle(el)
  const [boxSizing, paddingTop, paddingBottom, borderTop, borderBottom] = attrs.map(item => style.getPropertyValue(item))
  if (boxSizing === 'content-box') {
    this.heightOffset = -(parseFloat(paddingTop)) - parseFloat(paddingBottom)
  } else {
    this.heightOffset = parseFloat(borderTop)  + parseFloat(borderBottom)
  }
  this.initEvent()
}

AutoSize.prototype = {
  initEvent () {
    this.listener = this.handleAction.bind(this)
    this.el.addEventListener('input', this.listener, false)
  },
  destroy () {
    this.el.removeEventListener('input', this.listener, false)
    this.listener = null
  },
  handleAction (e) {
    const event = e || window.event
    const target = event.target || event.srcElement
    target.style.height = ''
    target.style.height = target.scrollHeight + this.heightOffset + 'px'
  }
}
複製代碼

!

高度自適應多行文本輸入框

  對於 input 這樣高頻度觸發的事件,通常須要採用函數節流或者函數防抖的方式進行優化,這裏就留給讀者折騰吧。

3、contenteditable

  HTML 中還有一個很特別的屬性 -- contenteditable,該屬性能夠規定當前元素是否可編輯(下文統稱這樣的元素爲可編輯元素),該屬性的取值有如下幾種:

  • true 或者空字符串,表示元素是能夠編輯的;
  • false 表示元素是不可編輯的;
  • plaintext-only 只處理文本內容;
  • 更多取值,請查看W3C ContentEditable

  當用戶向可編輯元素中輸入內容時,瀏覽器會生成對應的 DOM 元素,因此可編輯元素能夠作富文本編輯功能,例如:medium-editor

  可是因爲各個瀏覽器對於標籤的生成規則不一樣,兼容性方面的處理是很大的難題。

  如今回到實現高度自適應的多行文本輸入框的需求上來,考慮到用戶可能輸入或者粘貼富文本內容,這裏須要將 contenteditable 屬性設置爲 plaintext-only :

<div contenteditable="plaintext-only" class="demo" id="js-div" data-placeholder="這是佔位文字"></div>
複製代碼

  如今這個 div 標籤變成了一個高度自適應的文本輸入框,是否是很神奇!不過不要高興的太早,還有須要考慮一些事情:

一、placeholder

  對於一個正兒八經的輸入框,是否是應該有一個 placeholder 效果啊:

[contenteditable=true]:empty::before {
    content: attr(data-placeholder);
    color: red;
    display: block;
  }
複製代碼
二、value

  對於 textarea 標籤,能夠經過 value 屬性值獲取用戶輸入的內容。可是對於設置 contenteditable 屬性的元素來講,就須要具體狀況具體分析了:

  • 若是須要獲取 HTML 結構,那麼就須要採用 innerHTML 屬性;
  • 若是僅僅獲取文本內容,那麼能夠考慮 innerText 和 textContent。

  innerText 和 textContent 是否是又讓你傻傻分不清了,關於它們的區別主要有:

  • textContent 會獲取全部元素的內容,包括 script 和 style 標籤元素,而 innerText 不會;
  • innerText 受 CSS 樣式的影響,不會返回隱藏元素的文本,而 textContent 會;
  • innerText 返回的文本內容會格式化。

  因爲上述 contenteditable 屬性指定了 plaintext-only 屬性值,因此這三種屬性獲取到的值是同樣的。

三、禁止富文本輸入的其它方式

  除了指定 plaintext-only 屬性值的方法,張鑫旭大神在不少年前寫過一個div 模擬 textarea 實現高度自適應,其中是經過 CSS 屬性實現只容許輸入文本內容:

-webkit-user-modify: read-write-plaintext-only;
複製代碼

   現在 user-modify 這個 CSS 屬性已經被 contenteditable 替代了,不過這依然是一個很神奇的 CSS 屬性。

  到此,纔算實現一個高度自適應的多行文本輸入框。不過仍然有不少奇怪的問題,歡迎踩過這方面坑的同窗留言討論。

四、填空題輸入框的實現

  除了實現高度自適應的多行文本輸入框以外,可編輯元素與 input 和 textarea 標籤還有一個很大的不一樣,它能夠完美的融入到文本當中,例如實現這樣一個填空題輸入框的效果:

填空題輸入框

4、總結

  以上即是高度自適應多行文本輸入框的兩種實現方式:

  • scrollHeight + getComputedStyle + input(事件)
  • contenteditable + 一堆騷操做

  相比較後者,前者的適用性更強,也是大部分組件庫所採用的方式。

相關文章
相關標籤/搜索