前言
業(yè)務(wù)開發(fā)中,經(jīng)常會(huì)遇到文本輸入框高度隨著輸入內(nèi)容高度變化的情況,下面我們來詳細(xì)說明一下實(shí)現(xiàn)這種輸入框的方案和解題思路
方案一為一種擴(kuò)展思路,僅供參考
方案二為常規(guī)思路,急著用的小伙伴建議直接看二
第三個(gè)板塊 附上了react native和react native to web的代碼實(shí)現(xiàn)方案
※方案一:contenteditable屬性法
- contenteditable屬性表示元素是否可編輯,變?yōu)榭删庉嫚顟B(tài)的元素還保留其原有的特性,屬性值為如下兩者之一
-true或空字符串,表示元素是可編輯的;
-false表示元素不是可編輯的; - 該屬性是一個(gè)枚舉屬性,而非布爾屬性。這意味著必須顯式設(shè)置其值為
true、false或空字符串中的一個(gè),最好不要簡(jiǎn)寫為<label contenteditable>Example Label</label> - 正確的用法是 :
<element contenteditable="value"> --value=true/false
- ??分析
<style type="text/css">
.container {
padding: 20px;
}
.auto-input {
min-height: 100px;
font-size: 30px;
border: 1px solid red;
}
</style>
<body>
<h1>contenteditable實(shí)現(xiàn)的高度自適應(yīng)輸入框</h1>
<div class="container">
<div class="auto-input" contenteditable="true" id="auto-input"></div>
</div>
</body>
- 注意事項(xiàng):如想要設(shè)置文本輸入框的默認(rèn)高度,設(shè)置
min-height即可,文本輸入框同時(shí)支持focus,blur事件,但是即使外表偽裝的和textarea一模一樣,還是有需要注意的坑點(diǎn),試著分別輸入和復(fù)制內(nèi)容,查看dom節(jié)點(diǎn)的變化:
以下為手打內(nèi)容和復(fù)制黏貼的區(qū)別
image
可以看出內(nèi)容其實(shí)并不是真正的純文本,而是帶有樣式的富文本格式,黏貼進(jìn)去的內(nèi)容呈現(xiàn)的還是復(fù)制前的樣式,我們可以通過innerText獲取到里面的純文本內(nèi)容
image
保留此功能很適合展示圖片,復(fù)制一張圖片進(jìn)入輸入框中可直接展示
image
- 純文本設(shè)置方法:為了完成和
textarea同樣的作用,我們可以在輸入時(shí)進(jìn)行過濾,保證輸入的是純文本文件,有兩種辦法
- 在css中設(shè)置
div[contenteditable] {
user-modify: read-write-plaintext-only
}
user-modify 可以控制普通元素是否可讀寫
user-modify: read-only; // 只讀
user-modify: read-write; // 可讀寫,支持富文本
user-modify: write-only; // 只寫,支持的瀏覽器很少
user-modify: read-write-plaintext-only;//可讀寫,純文本,目前只有webkit內(nèi)核瀏覽器支持
- 在html中給div增加屬性
<div contenteditable="plaintext-only">
- 結(jié)果分析:
這種方案的缺點(diǎn)在于,一個(gè)元素加上contenteditable,即使解決了可編輯的問題,但是表單控件的一些特性placeholder,maxlength,autofocus,只能js去輔助完成,在移動(dòng)端會(huì)有一些兼容性的問題
方案二:純textarea文本框?qū)崿F(xiàn)
思路初始化—?jiǎng)?chuàng)建textarea元素
- 我們預(yù)期通過一個(gè)文本輸入框textarea即可完成高度自適應(yīng),但是實(shí)際表明,如果只是通過textarea和一些簡(jiǎn)單的css方法,設(shè)置textarea的
min-height后會(huì)發(fā)現(xiàn),當(dāng)輸入的元素內(nèi)容的高度超過設(shè)置的最小高度時(shí),會(huì)產(chǎn)生滾動(dòng)條,顯然不符合我們的預(yù)期
進(jìn)展1—獲取元素的scrollTop和offsetHeight并設(shè)置高度
- 既然產(chǎn)生了滾動(dòng)條,那就可以嘗試著獲取文本框的
scrollTop,加上文本框的原有高度,在監(jiān)聽到文本框內(nèi)容改變的時(shí)候重新設(shè)置文本框的高度 - 我們用
offsetHeight來獲取文本框的高度,該屬性包含文本框的border + padding + content的高度,因此我們要在css中將textarea設(shè)置為border-box,方便設(shè)置的時(shí)候統(tǒng)一 - 最后元素高度的設(shè)置后只是相當(dāng)于增加了scrollTop部分的值
- 實(shí)現(xiàn)步驟如下:
- input監(jiān)聽文本框內(nèi)容的改變
- 獲取文本框的滾動(dòng)距離
scrollTop - 獲取文本框的高度
offsetHeight - 設(shè)置文本框的新
height為scrollTop+offsetHeight
- ??分析:
css:
<style>
body,
html {
padding-left: 0.1rem;
margin: 0;
}
.auto-input {
display: block;
box-sizing: border-box;
outline: none;
resize: none;
padding: 0;
width: 2rem;
height: 30px;
border: 1px solid #000;
font-size: 0.2rem;
}
.title {
font-size: 0.2rem;
}
</style>
html:
<body>
<h1 class="title">textarea js高度自適應(yīng)輸入框</h1>
<textarea class="auto-input" id="autoInput"></textarea>
</body>
script:
<script>
var autoInput = document.getElementById("autoInput");
autoInput.addEventListener("input", function() {
var inputScrollTop = autoInput.scrollTop;
var inputHeight = autoInput.offsetHeight;
console.log("inputScrollTop:" + inputScrollTop + 'px')
autoInput.style.height = inputScrollTop + inputHeight + "px";
});
</script>
- 測(cè)試結(jié)果如下:
image
- 結(jié)果分析: 在輸入到下一行的時(shí)候,第一個(gè)導(dǎo)致?lián)Q行的字符在觸發(fā)input事件的時(shí)候獲取到的
scrollTop的值并未改變,仍然為舊值,直到輸入新一行的第二個(gè)字符的時(shí)候才有所響應(yīng),猜測(cè)原因是scrollTop的獲取時(shí)機(jī)太早導(dǎo)致的問題
進(jìn)展2—增加定時(shí)器延緩執(zhí)行
- 我們嘗試在js中獲取
scrollTop時(shí)外加入定時(shí)器,延緩獲取時(shí)機(jī)
var autoInput = document.getElementById("autoInput");
autoInput.addEventListener("input", function() {
setTimeout(()=>{
var inputScrollTop = autoInput.scrollTop;
var inputHeight = autoInput.offsetHeight;
console.log("inputScrollTop:"+inputScrollTop+'px');
autoInput.style.height = inputScrollTop + inputHeight + "px";
},0)
});
- 測(cè)試結(jié)果如下:
image
- 結(jié)果分析:確實(shí)如預(yù)期,添加定時(shí)器延緩了獲取scrollTop的時(shí)機(jī)后,在換行時(shí)獲取到的
scrollTop為準(zhǔn)確的。
進(jìn)展3—獲取元素的scrollHeight并設(shè)置高度
- 雖然在開發(fā)過程中定時(shí)器確實(shí)能解決很多頭疼的問題,但是個(gè)人覺得不是很優(yōu)雅,這里我們嘗試著去獲取另一個(gè)屬性,
scrollHeight,對(duì)于滾動(dòng)元素來說scrollHeight代表的是元素原有的高度加上內(nèi)容滾動(dòng)到底部時(shí)的scrollTop,換句話說也就是元素的完整內(nèi)容高度,這個(gè)屬性包含元素的padding,不包含border和margin - 需要注意獲取的
scrollHeight本身是不包含邊框的高度的,但是我們要重置的height,因?yàn)樵O(shè)置為border-box,是包含邊框的,因此需要將scollHeight加上邊框后再設(shè)置給textarea的height - 實(shí)現(xiàn)步驟如下:
- 監(jiān)聽元素變化時(shí)我們?nèi)カ@取
scrollHeight - 設(shè)置
scrollHeight+border為元素高度
- 監(jiān)聽元素變化時(shí)我們?nèi)カ@取
- ??分析:
script:
autoInput.addEventListener("input", function() {
var inputScrollHeight = autoInput.scrollHeight + 2;
console.log("inputScrollHeight" + inputScrollHeight+"px");
autoInput.style.height = autoInput.scrollHeight + "px";
});
- 測(cè)試結(jié)果:
image
- 結(jié)果分析:當(dāng)輸入元素內(nèi)容的時(shí)候,確實(shí)可以如我們預(yù)期的,隨著內(nèi)容的增加高度增加,但是刪除的時(shí)候表現(xiàn)卻發(fā)現(xiàn)textarea的高度沒有變化,
scrollHeight是用來獲取元素滾動(dòng)的scrollTop,padding,以及內(nèi)容的高度的,那么當(dāng)刪除文本內(nèi)容時(shí)它是否會(huì)發(fā)生變化呢?從上面視頻打印的結(jié)果來看刪除的時(shí)候元素的高度并未發(fā)生變化
原因如下:如果我們沒有給文本框設(shè)置高度,隨著內(nèi)容的增加
scrollHeight = scrollMaxTop+clientHeight;//元素高度加滾動(dòng)最大距離
其中scrollTop會(huì)隨著內(nèi)容增加可滾動(dòng)的距離變大而增加,所以在添加文字的情況下我們可以發(fā)現(xiàn)scrollHeight會(huì)不斷增大
我們將盒子本身的高度設(shè)置成scrollHeight,
newClientHeight = scrollHeight
刪除的情況下,盒子高度足夠是沒有滾動(dòng)距離的,因此scrollMaxTop為0,newClientHeight不會(huì)再更新,因此盒子也就維持了之前的高度
scrollHeight = 0 + newClientHeight
進(jìn)展4—重置元素的高度
- 上面因?yàn)閯h除時(shí)
scrollHeight并不會(huì)變化導(dǎo)致元素的高度維持在了之前的最大值,那么我們?nèi)绻趧h除元素時(shí),將元素的高度設(shè)置成根據(jù)內(nèi)容自適應(yīng)(auto)/(""),這樣textarea的高度會(huì)被重置成最小化 - 最小化之后重新獲取到的
scrollHeight,又是可以讓當(dāng)前內(nèi)容自適應(yīng)的高度 - 需要注意auto和""兩者的區(qū)別 如果設(shè)置為auto的話 textarea的高度會(huì)被重置為默認(rèn)高度,默認(rèn)高度不是指css中設(shè)置的高度,而是瀏覽器默認(rèn)的,但是如果設(shè)置為(“”)那么相當(dāng)于清楚的是內(nèi)聯(lián)的高度樣式,并不會(huì)覆蓋css的高度,textarea本身的css高度還是存在的,因此表現(xiàn)不同點(diǎn)在于textarea最小時(shí)的高度,所以這里建議使用"",可以保留原本設(shè)置的高度,但是如果原本設(shè)置的是textarea的
min-height而不是height,那兩個(gè)屬性均可 - 實(shí)現(xiàn)步驟如下:
- 監(jiān)聽元素變化時(shí)我們將元素的height設(shè)置為““,目的是為了清楚上一次的高度
- 重新獲取元素的
scrollHeight - 設(shè)置元素的高度為
scrollHeight+border
- ??分析
autoInput.addEventListener("input", function() {
autoInput.style.height = "";
//注意順序 需要先重置 再獲取
var inputScrollHeight = autoInput.scrollHeight + 2;
console.log("inputScrollHeight" + inputScrollHeight+"px");
autoInput.style.height = inputScrollHeight + "px";
});
- 測(cè)試結(jié)果:
image
- 結(jié)果分析:到此我們終于完成了輸入框的基本功能。但是現(xiàn)在每次Input監(jiān)聽時(shí),我們都會(huì)將元素的高度重置為空,并且每次都會(huì)獲取
scrollHeight的高度,無疑會(huì)對(duì)性能有一些損耗,因此我們后面會(huì)嘗試一下優(yōu)化方案。
進(jìn)展5—優(yōu)化方案
- 我們嘗試增加一些判斷條件來減少不必要的執(zhí)行
- 在元素內(nèi)容增多的時(shí)候,我們期望只有當(dāng)元素內(nèi)容換行的時(shí)候才進(jìn)行重置操作,但是怎么去監(jiān)聽元素的換行呢,我們可以通過獲取到的
scrollHeight,當(dāng)該值增加的時(shí)候再去進(jìn)行設(shè)置高度的操作 - 另一方面 當(dāng)元素內(nèi)容減少的時(shí)候我們才需要將元素的高度置空,是否也可以通過判斷
scrollHeight的值是否變小才進(jìn)行這種判斷呢,答案無疑是否定的,因?yàn)槿绻蝗ピO(shè)置高度為空的話,scrolllHeight的值并不會(huì)發(fā)生變化,目前想到的判斷字符減少的方案為監(jiān)聽輸入的字符個(gè)數(shù) - 如果字符數(shù)減少的話我們需要將元素的height置為空,然后重新獲取元素的
scrollHeight,- 如果減少的字符導(dǎo)致了換行,那么
scrollHeight的值會(huì)發(fā)生變化 - 如果減少的字符沒有導(dǎo)致?lián)Q行,那么
scrollHeight沒有發(fā)生變化
- 如果減少的字符導(dǎo)致了換行,那么
- 無論哪種情況我們都需要去把
scrollHeight的值賦值給textarea的height,否則會(huì)變?yōu)閏ss中設(shè)置的最小高度 - 因此textarea高度重新設(shè)置的的條件為
scrollHeight增加導(dǎo)致?lián)Q行或者文字內(nèi)容減少 - 實(shí)現(xiàn)步驟如下:
- 我們先獲取textarea原本的
scrollHeight和字符串長(zhǎng)度 - 在監(jiān)聽到內(nèi)容改變的時(shí)候,我們獲取一下新內(nèi)容的長(zhǎng)度
- 如果長(zhǎng)度變小 重置文本框的高度
- 然后獲取文本框的
scrollHeight - 如果
scrollHeight變大或者元素的height不存在進(jìn)入到if判斷中 - 將之前存儲(chǔ)的文本框的高度設(shè)置成新的方便下一次比較
- 設(shè)置文本框的高度為新獲取到的
scrollHeight+邊框 結(jié)束if語句 - 最后重置下文本框的新內(nèi)容的長(zhǎng)度
- 我們先獲取textarea原本的
- ??分析
var lastScrollHeight = autoInput.scrollHeight;
var lastTextLength = autoInput.value.length;
autoInput.addEventListener("input", function() {
var inputTextLength = autoInput.value.length;
if (inputTextLength < lastTextLength){
autoInput.style.height = "";
}
//注意這句話一定要寫在設(shè)置height為空的后面,否則獲取不到最新的scrollHeight
var inputScrollHeight = autoInput.scrollHeight;
//注意如果height為空的話也需要重置高度 否則高度有問題
if(lastScrollHeight < inputScrollHeight || !autoInput.style.height){
lastScrollHeight = inputScrollHeight;
autoInput.style.height = inputScrollHeight + 2 + "px";
}
lastTextLength = autoInput.value.length;
});
- 測(cè)試結(jié)果:同上
- 結(jié)果分析:至此已經(jīng)完成了比較完善的自適應(yīng)輸入框
react native和rn2web的實(shí)現(xiàn)方法
RN的實(shí)現(xiàn)方案
- rn方法提供
onContentSizeChange的函數(shù),onContentSizeChange是在內(nèi)容布局改變(如換行)的時(shí)候能獲取到當(dāng)前contentSize中的高度,然后通過state調(diào)整為input的高度 - ??分析
_onChange=(event)=> {
this.setState({
text: event.nativeEvent.text,
});
}
_onContentSizeChange=(event)=> {
this.setState({
height: event.nativeEvent.contentSize.height
});
}
render() {
return (
<TextInput {...this.props}
multiline={true}
onChange={this.onChange}
onContentSizeChange={this.onContentSizeChange}
style={[styles.textInputStyle, {height: Math.max(35, this.state.height)}]}
value={this.state.text}/>
);
}
}
- 結(jié)果分析:當(dāng)檢測(cè)到文本框內(nèi)容布局變化時(shí),我們便會(huì)將獲取到的高度置給
TextInput組件,該方法已經(jīng)兼容了刪除時(shí)的操作
react native to web的實(shí)現(xiàn)方案
- rn-to-web中只實(shí)現(xiàn)了高度變化時(shí)會(huì)觸發(fā)
onContentSizeChange,但是沒有實(shí)現(xiàn)內(nèi)部的邏輯event.nativeEvent屬性是不存在的,所以我們要通過類似方案二的解決辦法,獲取到原生的scrollHeight屬性 - ??分析
render() {
return (
<TextInput {...this.props}
multiline={true}
onChange={this.onChange}
onContentSizeChange={event => {
const node = this.input._node
if (node) {
node.style.height = 'inherit'
const height = node.scrollHeight
node.style.height = `${height}px`
this.setState({ height })
}
}}
style={[styles.textInputStyle, {height: Math.max(35, this.state.height)}]}
value={this.state.text}/>
);
}
- 結(jié)果分析:
this.input._node獲取到的是原生的dom節(jié)點(diǎn),因此里面采用和是方案二同樣的處理方式, - 基本原理和方案二相同,但是由于
onContentSizeChange的觸發(fā)時(shí)機(jī)本就是在高度變化的時(shí)候,所以react native和react native to web的這兩種實(shí)現(xiàn)方式均不需要進(jìn)行優(yōu)化處理
源碼參考:
寫在最后:
第一篇分享文章,感覺寫的略有點(diǎn)啰嗦,比較適合新手閱讀,后面會(huì)逐漸改進(jìn),大家有什么想法和優(yōu)化思路歡迎來交流啊,一起努力成為合格的前端工程師!
最后的最后 附上wuli超級(jí)無敵可愛的琪琪
image