一、前言
通過建立 textarea 標籤,並且指定其 rows 和 cols 屬性,就可以建立一個多行文字輸入框。
但是當輸入的內容超過指定的 rows 之後,就會出現滾動條,如果使用者想要檢視全部內容,那就必須來回的拖動滾動條。而且這個滾動條只有在使用者滾動的時候才會出現,在一些情況下,使用者可能並不知道該區域有更多的內容。
通常情況下,為了給使用者一個良好的體驗,需要讓這個多行文字輸入框的高度自適應,從而避免滾動條帶來的問題。
二、高度自適應
實現高度自適應的文字輸入框的思路很簡單:監聽輸入相關的事件,獲取到元素的內容高度,修改 textarea 的固定高度。
其中涉及很多基礎的知識,也就是我們常說的細節問題處理:
1、scrollHeight
scrollHeight 這個只讀屬性是一個元素內容高度的度量,包括由於溢位的檢視中不可見的內容。
scrollHeight 包含元素的 padding,但是不包含元素的 border 和 margin 。當元素中不存在溢位內容,則 scrollHeight 與 clientHeight 是相同的。
接下來只要將獲取到的 scrollHeight 屬性值賦給元素樣式中的 height 屬性,是不是就可以動態的更改高度呢?當然,事情並沒有那麼簡單,這裡又要引出另一個基礎知識點。
2、box-sizing
CSS 中的盒模型基本上是常考的一個知識點,CSS 中可以通過設定 box-sizing 屬性值,從而更改盒模型高度和寬度的計算,下面以高度為例:
- 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。
3、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')
複製程式碼
4、實現
有了上述三個知識點的補充,接下來就是程式碼實現:
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 這樣高頻度觸發的事件,一般需要採用函式節流或者函式防抖的方式進行優化,這裡就留給讀者折騰吧。
三、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 標籤變成了一個高度自適應的文字輸入框,是不是很神奇!不過不要高興的太早,還有需要考慮一些事情:
1、placeholder
對於一個正兒八經的輸入框,是不是應該有一個 placeholder 效果啊:
[contenteditable=true]:empty::before {
content: attr(data-placeholder);
color: red;
display: block;
}
複製程式碼
2、value
對於 textarea 標籤,可以通過 value 屬性值獲取使用者輸入的內容。但是對於設定 contenteditable 屬性的元素來說,就需要具體情況具體分析了:
- 如果需要獲取 HTML 結構,那麼就需要採用 innerHTML 屬性;
- 如果僅僅獲取文字內容,那麼可以考慮 innerText 和 textContent。
innerText 和 textContent 是不是又讓你傻傻分不清了,關於它們的區別主要有:
- textContent 會獲取所有元素的內容,包括 script 和 style 標籤元素,而 innerText 不會;
- innerText 受 CSS 樣式的影響,不會返回隱藏元素的文字,而 textContent 會;
- innerText 返回的文字內容會格式化。
由於上述 contenteditable 屬性指定了 plaintext-only 屬性值,所以這三種屬性獲取到的值是一樣的。
3、禁止富文字輸入的其它方式
除了指定 plaintext-only 屬性值的方法,張鑫旭大神在很多年前寫過一個div 模擬 textarea 實現高度自適應,其中是通過 CSS 屬性實現只允許輸入文字內容:
-webkit-user-modify: read-write-plaintext-only;
複製程式碼
如今 user-modify 這個 CSS 屬性已經被 contenteditable 替代了,不過這依然是一個很神奇的 CSS 屬性。
到此,才算實現一個高度自適應的多行文字輸入框。不過仍然有很多奇怪的問題,歡迎踩過這方面坑的同學留言討論。
4、填空題輸入框的實現
除了實現高度自適應的多行文字輸入框之外,可編輯元素與 input 和 textarea 標籤還有一個很大的不同,它可以完美的融入到文字當中,例如實現這樣一個填空題輸入框的效果:
四、總結
以上便是高度自適應多行文字輸入框的兩種實現方式:
- scrollHeight + getComputedStyle + input(事件)
- contenteditable + 一堆騷操作
相比較後者,前者的適用性更強,也是大部分元件庫所採用的方式。