文本框是很常見的輸入控件,我相信只要寫過表單的肯定接觸過 textarea 這個元素。
OK。但是現在產品經理說了:需要這個文本框可以根據用戶輸入內容自適應其高度。
height: auto
有些初學者可能會想:自適應高度不就是 height: auto
麼?可是你想一下,一個 textarea
沒有手工給它指定過樣式,不應該就默認是 height: auto
麼?但是它還是有自己的初始高度,並沒有像一個 div
那樣高度爲 0。
與 div
不同,textarea
的默認高度不是根據其內容自適應,而是由屬性 rows 指定,其默認值是 2
。rows
這個屬性(Attribute)只接受正整數,指定其他值瀏覽器會忽略掉其值,比如你寫 rows="auto"
那麼 rows
就是 2,rows="0"
也是 2。
所以指定 height: auto
是行不通的,height
屬性必須人工指定其值。
scrollHeight
遇到過這個問題的同學(比如當初的筆者),肯定想到過 scrollHeight
這個 DOM 屬性。想法很簡單,當用戶輸入的文本超過了文本框自身高度時不是會出現滾動條嘛,那麼自然而然就能想到 scrollHeight 這個屬性。scrollHeight
就應該是用戶輸入文本的真實高度,至少超過文本框既定高度時是這樣。
那麼問題來了:如果沒超過呢?
OK 我知道你會先指定 rows="1"
讓文本框默認高度只有一行。但是考慮這種情況:用戶先輸入了很多行文本
然後刪除了一段:
scrollHeight
值沒有變化。MDN 上說了:
沒有垂直滾動條的情況下,scrollHeight值與元素視圖填充所有內容所需要的最小值clientHeight相同
scrollHeight
確實會隨着用戶輸入內容多少而增減,但是僅限於出現滾動條的情況,對於題設這個情況必然不適用,因爲需求就是不能出現滾動條(嚴格來說是超出可視區域)。你可以在獲取 scrollHeight
的值之前先把文本框高度設爲 0 強制讓滾動條出現,但是這樣可能使頁面發生閃爍,而且性能也低。
split('\n')
DOM 屬性靠不住,那我自己算文本高度不行嗎?說我拿出所有文本,按換行符拆分,看有多少行,行數 * 行高
不就是最終文本高度嗎?
額。。。當文本沒有折行的情況下是這樣。。。
contenteditable
contenteditable 確實是一個(相對)可行的方案,但是作爲一個踩過坑的先行者勸解你:不到萬不得已,contenteditable 不要碰。這個玩意各個瀏覽器實現都不一樣,各種奇葩行爲,光一個換行符就足夠折磨你半天。
當然這裏還沒有到那麼複雜的地步,但是你得先會把“複製——粘貼”過去的樣式去掉才行
筆者的方法
說了那麼多廢話,那麼究竟該怎麼辦呢?這裏筆者提供一種方法。
當然首先聲明:筆者的方法未必是最簡單的,如有其它更簡單的方案歡迎留言提出。
我們想一下,textarea
不能按照內容自適應高度,div
可以啊,能不能先把文本填到一個 div
裏,div
的高度就應該是文本框所需高度(當 padding
、line-height
等樣式都一致的情況下),這時獲取 div
的高度賦值給文本框高度不就行了嗎。
就是這樣的思路。我們也不需要專門使用 JS 獲取,只要讓 div
把父元素撐起來,絕對定位 textarea
元素讓文本框佔滿整個父元素大小就好了。
直接上代碼:
<style>
#parent {
width: 500px;
font: 12px monospace;
position: relative;
}
#dummy {
padding: 2px;
border: 1px solid;
visibility: hidden;
}
#dummy::after {
content: "\A";
}
#textarea {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
resize: none;
width: 100%;
font: inherit;
}
</style>
<div id="parent">
<div id="dummy"></div>
<textarea id="textarea"
oninput="document.getElementById('dummy').textContent = this.value"></textarea>
</div>
這裏查看運行結果:https://codepen.io/CarterLi/p...
三個要點:
- 字體相關樣式
#dummy
和textarea
兩元素必須完全一致,差一點就可能出現兩者高度對不上的情況。 -
#dummy
中white-space: pre-wrap
醒目。否則會出現 HTML 中吞空格、換行符的情況。 - 就算有了
white-space: pre-wrap
,HTML 仍然會吞掉最後的換行符。解決方案是在#dummy
最後插入一個換行符元素。可以是<br />
,也可以是一個僞元素。僞元素中換行符的寫法是\A
(即換行符的 ASCII 碼 10 的十六進制表示。不能寫\n
)
代碼中是用 JS 給 #dummy
賦值。項目中如果你用 vuejs
或 angular
等 MVVM 框架,直接把文本框的值綁定到 div
上就好,非常方便。
如果你要限制文本框的最大最小高度,在 #dummy
上直接設置 min-height
max-height
即可。
完
最後說一句:把 textarea
蓋到一個 div
上的做法還可以簡單的實現文本框的語法高亮,讀者可以想想怎麼做。