最近一個專案掉進了移動端的大坑,包括ios下fixed佈局,h5喚起鍵盤等問題,作為一個B端程式設計師,弱項就是瀏覽器的相容性和移動端的適配(畢竟我們可以要求使用chrome),還好這次讓我學習了一下相關知識。讓我們一起來看一下我怎麼掙扎出這個大坑的。
一、背景
先看一下要做什麼,也就是一個文章評論的版塊,下面依次有輸入框,點贊,收藏等 。大概長下面這個樣子:
要求也很常規,吸底,輸入評論提交。那麼上來就輸程式碼吧。
二、ios下fixed佈局
關於這種吸底操作,上來就直接選用fixed了,這種場景舍他其誰。初步的佈局就是這個樣子了。(因為我是用的react,jsx的寫法貼上上來簡直讓人崩潰,就隨手寫一段程式碼代表下,勿怪)
1 <body>
2 <div class='top'></div>
3 <div class='main'></div>
4 <div class="fix-bottom"></div>
5 </body>複製程式碼
然後css就不多寫了,瀏覽器上一看還挺想那麼回事。然後在ios上就出現了點意外情況。就成了這個樣子(希望沒有我廠的工友)
就這樣被頂起來了。。。。出現問題就算當時要快速解決沒有時間去深究,那麼下來也要去搞清楚因果,畢竟我們不是為了解決問題而解決問題。一起看下原因
2.1 ios下fixed失效的原因
軟鍵盤喚起後,頁面的 fixed 元素將失效(ios認為使用者更希望的是元素隨著滾動而移動,也就是變成了 absolute 定位),既然變成了absolute,所以當頁面超過一屏且滾動時,失效的 fixed 元素就會跟隨滾動了。
不僅限於 type=text
的輸入框,凡是軟鍵盤(比如時間日期選擇、select 選擇等等)被喚起,都會遇到同樣地問題。
2.2 如何解決
既然ios就是這個樣子,我們只能選擇接受現狀,只能想辦法繞過去了。大致說來兩個方向:
1、既然會變成absolute,索性直接使用absolute算了,
bottom直接以body作為父元素來進行絕對定位,不過這種網上都不推薦,想來有更多的問題等待修正,前人的經驗還是要借鑑的,所以我也沒有去嘗試,有興趣的同學可以嘗試一下。
2、不讓頁面滾動,而是讓主體部分自己滾動
如果fixed的失效,但是頁面並沒有超過一屏的長度,那麼無論absolut或者fixed也沒什麼差別。順著這個思路我們回顧一下上面的結構,完全可以讓main直接滾著玩就行了。將吸底的元素和主題作為兩大容器,主體部分,設定絕對定位,固定在螢幕中間,超出部分就自行滾動,吸底元素就可以自己玩了
大概就是下面這個樣子:
1 <body>
2 <div class='warper'>
3 <div class='top'></div>
4 <div class='main'></div>
5 <div>
6 <div class="fix-bottom"></div>
7 </body>複製程式碼
對應樣式如下:
1 .cont-warper{
2 position: absolute;
3 width: 100%;
4 left: 0;
5 right: 0;
6 top: 0;
7 bottom: 0;
8 overflow-y: scroll;
9 -webkit-overflow-scrolling: touch;/* 解決ios滑動不流暢問題 */
10 }
11 .fix-bottom{
12 position:fixed;
13 bottom:0;
14 width: 100%;
15 }複製程式碼
這樣就能避免上面那個問題了。但是ios下,對於吸底元素而言在螢幕下半部分喚起鍵盤的時候,會被遮住部分東西,有的資料提到是第三方輸入法的toolbar,我看到的現象是吸底元素被遮住了,對於這種情況,我們只好加個監聽事件,當喚起鍵盤的時候,設定scrollTop值,也就是說你不上來,我強迫你上來:
/**
* 喚起鍵盤,滾動
*/
scrollContent() {
this.interval = setInterval(() => {
this.scrollToEnd();
}, 500)
}
scrollToEnd() {
document.body.scrollTop = document.body.scrollHeight;
}
clearSrcoll() {
clearInterval(this.interval);
}複製程式碼
設定延時切換,input當失去焦點的時候清除。
三、h5呼叫虛擬鍵盤
解決了佈局問題,下面就開始happy的寫功能吧,開始之前,讓我們回頭繼續看下上面的視覺圖,是不是感覺少了點什麼。我們的提交button呢?一般來說是這樣:
這讓我如何下手,還好請教了下老大,作為一個老司機他輕輕的告訴我三個字:網上搜。。。。。
不扯淡了言歸正傳,對於這種顯然是要利用軟鍵盤上的回車來提交資訊的。你最常見的一定是搜尋按鈕,就是type=‘search’的使用。如果想通過鍵盤來提交資訊,就要把form表單拉出來用用了。
3.1 鍵盤提交事件
一般來說是這樣做的,將input包括在form表單內,這樣就可以監聽submit事件了。如果有人問我是走的ajax不是form表單的話,請記得有個onSubmit事件可以來做一些你想做的事情。程式碼如下:
1 <form onClick={::this.changeInput} onSubmit={this.comment.bind(this, postID)}>
2 <Input type="text" placeholder="請輸入" id='commentInput' value={::this.getVal()} onFocus={::this.scrollContent} onBlur={::this.clearSrcoll}/>
3 </form>複製程式碼
可能看起來比較蛋疼,沒辦法react的jsx就是這麼蛋疼。上面的那麼多事件還真的都有必要。思路如下:
1、submit事件可以監聽到使用者軟鍵盤的Enter鍵,對於ajax提交,這裡需要我們阻止下form的預設事件,避免form提交的重新整理頁面的行為
1 comment(postID, e) {
2 e.preventDefault()
3 // 阻止多次提交
4 // 你的程式碼
5
6 }複製程式碼
這樣,監聽軟鍵盤的提交事件就完成了,但是其他問題又來了
3.2 其他區域喚起軟鍵盤
再看一眼視覺圖,不僅僅是點選input可以提交評論,還有一種回覆別人評論的需求,點選回覆的label也需要喚起鍵盤來進行操作。
移動端而言對於h5的input,focus和blur可以喚起和收起鍵盤,這樣順理成章的我們可能這樣做。給每個回覆繫結個事件,點選的時候讓input獲取焦點即可,程式碼如下
1 <div className="comment-resquetion" onClick={this.changeParent.bind(this, comment.comment)}>
2 回覆
3 </div>
4 //input獲取焦點,並現實被回覆人暱稱
5 changeparentComment(val) {
6 let commentInput = document.querySelector('#commentInput')
7 commentInput.focus()
8 this.setState({
9 parentComment: val
10 })
11 }複製程式碼
這樣在android下面是可以的,在ios下面又遇到了問題,非input觸發的事件是不能喚起鍵盤的,這樣和window.open的限制差不多,只有使用者主動的操作才會允許喚起鍵盤,所以這樣是不可以的。
針對這種情況,我們可以投機取巧一下,既然必須要是input觸發的操作,那麼回覆那裡我直接用一個透明的input置於上方不就可以了。
1 <div className="comment-resquetion" onClick={this.changeParent.bind(this, comment.comment)}>回覆<input type='text' className='hide-input'/></div>複製程式碼
css樣式如下:
.comment-resquetion{
position: relative;
font-size: 0.28rem;
color: #3E93C2;
letter-spacing: -1px;
line-height: 0.45rem;
padding: 0.05rem 0 0.15rem 0.32rem;
}
.hide-input{
position: absolute;
width: 100%;
height: 0.28rem;
opacity: 0;
z-index: 1000;
left: 0.32rem;
top: 0;
}複製程式碼
當點選回覆的時候,其實點選的是input,這樣就能避開限制喚起軟鍵盤了。
3.3 關閉鍵盤
到這裡,以為就這樣結束了,結果發現還有個問題,當點選軟鍵盤提交完成的時候,鍵盤並不能隱藏,這讓人有點尷尬,因為點選提交按鈕之後,沒有主動收起鍵盤。因為當在軟鍵盤上操作是,io使用者的輸入行為還在繼續,所以不會收起鍵盤,如果是點選旁邊的button提交,就會自動收起了。既然不能主動收起,只能我們手動強制了,在提交事件返回後,可以手動將焦點移除,這裡最好做個延時,不然體驗有點太快。
1 this.commentInput = document.querySelector('#commentInput')
2 setTimeout(() => {
3 this.commentInput.blur()
4 // 評論成功都置空
5 this.props.changeParent(null)
6 }, 500)複製程式碼
到這裡這個看起來很小的功能終於結束了,有必要做個總結以供自己及有需要的同學做個參考。
參考文章: