前言
寫過移動端的同學(xué)或多或少都遇到過軟鍵盤帶來的各種各樣的問題,最典型的就是輸入框被軟鍵盤遮擋、fixed元素失效等問題,并且這些問題在iOS上的表現(xiàn)讓人難以接受。
webview的差異
在移動端上,我們的H5頁面一般是運行在宿主APP提供的webview
中,簡單點理解,你其實可以把它當(dāng)作瀏覽器,就是用來展現(xiàn)頁面內(nèi)容的。目前移動端主流系統(tǒng)分為Android
與iOS
,然而兩者提供的webview
容器也存在著諸多差異,今天我們就只探討兩者軟鍵盤帶來的影響。
首先,我們先來寫個簡單的頁面布局:頭部fixed+中間自適應(yīng)+底部fixed
Android
事實上Android的表現(xiàn)并不會有太大問題,它只不過是在鍵盤彈起來之后把webview
的高度減小了,變成了:原來的webview高度減去鍵盤的高度
這樣的表現(xiàn)正是我們期待的,完全沒有影響整個頁面的布局
iOS
軟鍵盤
在iOS 8.2 之后,iOS 唯一指定瀏覽器內(nèi)核、Webkit 鼻祖 Safari 將 fixed
元素的布局基準(zhǔn)區(qū)域從鍵盤上方的可見區(qū)域改成了鍵盤背后的整個視窗,也就是說此時的webview高度并不會發(fā)生變化,鍵盤是直接蓋在webview上方的。
這樣是為了在鍵盤彈起來之后,不用重新渲染頁面,他們是方便了,但遭殃的是我們前端開發(fā)人員...
比如上面這個頁面,我們看看iOS的表現(xiàn)是怎樣的:
可以看到,iOS為了不讓webview
壓縮,并且為了不讓軟鍵盤遮擋輸入框,他們自作聰明地把webview
整體往上移動,最大移動距離為軟鍵盤的高度。
這樣就導(dǎo)致我們的頭部以及頁面上半部分內(nèi)容移動到了可視區(qū)之外,這個表現(xiàn)是難以接受的,至少頭部應(yīng)該還要在可視區(qū)。(這就會讓我們誤以為fixed失效,實際上它相對于webview的位置并沒有變,只不過是webview發(fā)生了移動)
這個移動似乎沒有邏輯,不信大家可以試試把輸入框放到頁面的各個位置,我發(fā)現(xiàn)只有輸入框在最頂部,webview
才不會發(fā)生上移,其它位置都或多或少的會產(chǎn)生移動。
還有一個問題就是,此時的webview是可以滑動的,那么就會出現(xiàn)有用戶會將輸入框滑動到鍵盤下方,想想這個體驗也是難以接受的...
并且你會發(fā)現(xiàn),在頁面的上方與下方都多出了一個不論是 Viewport
還是 VisualViewport
都無法到達(dá)的白色襯底區(qū)域,我們可以嘗試把頁面所有元素背景都改成黑色再來看,會更加明顯
看到這些奇奇怪怪的問題你心里作何感想??
所有問題產(chǎn)生的根本原因是:iOS為了不用在鍵盤彈起之后重新渲染頁面,他們并沒有去壓縮webview
容器的高度,而是對webview整體進(jìn)行平移處理
軟鍵盤監(jiān)聽
對于Android,我們通??梢酝ㄟ^監(jiān)聽resize
事件來實現(xiàn),但對于iOS,我們從上面了解到鍵盤彈起,iOS的webview
高度并不會發(fā)生變化,所以也就觸發(fā)不了resize
事件。
在iOS中,可以通過focusin & focusout
事件來進(jìn)行監(jiān)聽
export const watchKeyBoard = (callback: (isShow: boolean) => void) => {
// IOS
if (isIOSByUA()) {
document.body.addEventListener('focusin', () => {
//軟鍵盤彈出的事件處理
callback(true)
})
document.body.addEventListener('focusout', () => {
//軟鍵盤收起的事件處理
callback(false)
})
} else {
// Android
const originalHeight =
document.documentElement.clientHeight || document.body.clientHeight
window.addEventListener('resize', () => {
const resizeHeight =
document.documentElement.clientHeight || document.body.clientHeight
if (resizeHeight - 0 < originalHeight - 0) {
// 鍵盤彈起事件
callback(true)
} else {
// 鍵盤收起事件
callback(false)
}
})
}
}
解決方案
了解完產(chǎn)生問題的原因,我們就可以來嘗試著解決問題,但想要純前端去解決這個問題,或多或少都會存在一些體驗問題,也許你可以去推動你們的客戶端同學(xué)來協(xié)助處理這個問題,只要讓iOS的webview在鍵盤彈起時的表現(xiàn)與Android一致,就不會存在這些奇怪的問題了,但似乎他們處理起來也非常棘手...
模仿Android的處理
雖然我們改不了webview的高度,但我們可以改我們布局的高度,我們只需要將頁面高度改為頁面可視區(qū)的高度即可,如果頁面內(nèi)容有滾動交互的話,需要額外處理,要與webview的滾動隔離開。
VisualViewport
先來了解下這個API,它可以用來獲取對應(yīng) window 的視覺視口
VisualViewport.offsetLeft
:返回視覺視口的左邊框到布局視口的左邊框的 CSS 像素距離。
VisualViewport.offsetTop
:返回視覺視口的上邊框到布局視口的上邊框的 CSS 像素距離。
VisualViewport.pageLeft
:返回相對于初始的 viewport 屬性的 X 軸坐標(biāo)所對應(yīng)的 CSS 像素數(shù)。
VisualViewport.pageTop
:返回相對于初始的 viewport 屬性的 Y 軸坐標(biāo)所對應(yīng)的 CSS 像素數(shù)。
VisualViewport.width
:返回視覺視口的寬度所對應(yīng)的 CSS 像素數(shù)。
VisualViewport.height
:返回視覺視口的高度所對應(yīng)的 CSS 像素數(shù)。
VisualViewport.scale
:返回當(dāng)前視覺視口所應(yīng)用的縮放比例。
這里我們需要的就是這個VisualViewport.height
,用來獲取可視區(qū)的高度。
但需要注意的是,這個API最低只支持iOS13,ios13以下的使用window.innerHeight
兜底
頁面布局
整體布局采用flex布局,頭部和底部也就不需要fixed來定位了,中間自適應(yīng)撐滿剩余高度,超長滾動
鍵盤打開計算高度重新布局
我們需要在鍵盤彈起后,計算可視區(qū)的高度,并將最外層容器高度賦值為可視區(qū)高度
watchKeyBoard((status) => {
setTimeout(() => {
console.log(
'status',
status ? '鍵盤打開' : '鍵盤關(guān)閉',
)
const container = document.getElementById('container')
if (status) {
container.style.height = `${
window.visualViewport.height || window.innerHeight
}px`
window.scrollTo(0, 0)
} else {
container.style.height = `100vh`
document.removeEventListener('touchmove', this.stopMove)
}
}, 100)
})
這樣頁面展示算是正常了
但是隨之而來的是滾動問題??
處理滾動
我們需要禁用全局的滾動,但對一些需要滾動的區(qū)域需要放開,比如中間的列表部分
if (utils.isIOSByUA()) {
watchKeyBoard((status) => {
setTimeout(() => {
console.log(
'status',
status ? '鍵盤打開' : '鍵盤關(guān)閉',
window.innerHeight,
)
const container = document.getElementById('container')
if (status) {
container.style.height = `${
window.visualViewport.height || window.innerHeight
}px`
window.scrollTo(0, 0)
document.addEventListener('touchmove', this.stopMove, {
passive: false,
})
document.addEventListener('touchend', this.scroll)
} else {
container.style.height = `100vh`
document.removeEventListener('touchmove', this.stopMove)
document.removeEventListener('touchend', this.scroll)
}
}, 100)
})
}
stopMove(e) {
// 排除可以滾動的區(qū)域
if (['content', 'keyboard_center'].includes(e.target?.className)) return
e.preventDefault()
}
scroll() {
window.scrollTo(0, 0)
}
完整體驗如下
比起它原本帶來的遮擋、滾動、fixed失效等體驗,現(xiàn)在的體驗算是可以接受的(這里所有的操作我們只需要在iOS上執(zhí)行即可)
作者:前端南玖
出處:https://www.cnblogs.com/songyao666/
該文章在 2024/4/29 17:47:49 編輯過