網站(zhan)在蘋果 Safari 進行適配遇到(dao)的問題
在(zai)網(wang)站進(jin)行(xing)移動端 Web 適配開發中(zhong),彈窗和導(dao)航(hang)欄彈出等常常會(hui)(hui)出現一些問題,如果(guo)是奇奇怪(guai)怪(guai)的(de)客(ke)戶嚴格要求的(de)話,那么就會(hui)(hui)有下面這(zhe)些情況:
- 打開彈窗后頁面自動放大,視圖區被放大到看不全
- 打開對話框打開后背景仍然能滾動
- 導航欄彈窗后,背后內容可滾動,影響體驗
- 點擊聚焦輸入框,導致視圖放大
下面通過 Nuxtjs3 示例,分(fen)析(xi)問題原因并提供一些解決方案(an),有錯誤或者有其他想法可以評論提出!
1. 問題分析
在 iOS Safari 中,可(ke)能開發者都(dou)看(kan)(kan)到了(le)已經(jing)對(dui) body 進行超(chao)出(chu)隱藏了(le),但是就(jiu)會出(chu)現穿透滑動滾動現象(查了(le)下資料說是 iOS Safari 對(dui)這個屬性無效,實際大家(jia)可(ke)以自己去看(kan)(kan)看(kan)(kan));
彈窗中有輸入框或效果切換時(shi),瀏覽(lan)器會觸(chu)發自動縮放行為(wei),導致全頁視圖放大(da)一(yi)丟丟,視圖溢出(chu)和亂移(yi)。
2. 解決思路
視圖放大問題
通過 meta viewport 可以(yi)限制瀏覽器的縮放操作(zuo)(也就是 html 原生放在 head 上的 meta):
useHead({
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' }
]
})
禁滾 body & 禁滑
當(dang)彈窗或(huo)菜單(dan)打開(kai)時(shi),通(tong)過動態修改(gai) body CSS 來防(fang)止(zhi)背景滾(gun)動以及監聽觸碰(peng)滑(hua)動(可單(dan)獨拎出):
const preventDefault = (e: Event) => {
e.preventDefault()
}
const disableBodyScroll = () => {
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
document.body.style.top = `-${window.scrollY}px`;
document.addEventListener('touchmove', preventDefault, { passive: false })
};
const enableBodyScroll = () => {
const scrollY = document.body.style.top;
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
document.body.style.top = '';
if (scrollY) {
window.scrollTo(0, parseInt(scrollY || '0') * -1);
}
document.removeEventListener('touchmove', preventDefault)
};
通(tong)過記錄當前滾動位置(zhi),關閉(bi)彈窗后恢復滾動狀態(tai),可(ke)以(yi)防(fang)止頁(ye)面上下移動。
注:這里是自定義(yi)的彈(dan)窗組件,如(ru)果禁滑的話(hua)要在對應彈(dan)窗內(nei)部加上單防穿透(tou)(@touchmove.stop),避免內(nei)部超出不可(ke)觸(chu)摸滾動(dong),原(yuan)理(li)如(ru)下(可(ke)以(yi)看看官方屬性進行(xing)理(li)解):
當用戶在頁面上滑動時:
[觸摸點]
↓ 觸發 touchmove
[某個 div]
↓ 冒泡
[父元素]
↓ 冒泡
[body]
↓ 冒泡
[html]
↓ 冒泡
[document] ← 在這里被攔截并 preventDefault()
↓ 冒泡
[window]
當彈窗內部滑動時:
[彈窗內的滾動區域]
↓ 觸發 touchmove
↓ 遇到 @touchmove.stop
停止冒泡(stopPropagation)
不會到達 document,因此不會被 preventDefault
保留默認滾動行為
Vue 中通過 watch 監聽
watch(visible, (newVal) => {
if (newVal) {
disableBodyScroll();
} else {
enableBodyScroll();
}
});
當 visible 為 true 時禁滾,其(qi)他情況恢復滾動(dong)。
3. 實現例子
客服對話窗 (Chat Window)
watch(visible, (newVal) => {
if (newVal) {
minimized.value = false;
loadMessagesFromStorage();
disableBodyScroll();
} else {
enableBodyScroll();
}
});
每(mei)次彈(dan)窗(chuang)打開(kai),禁滾(gun)(gun) body;關閉后恢復滾(gun)(gun)動。
通過 Teleport 把對話(hua)窗注入(ru) body,保證層級(ji)級(ji)次上(shang)在(zai)最上(shang)方(fang),避免(mian)被其他元素(su)遮擋或發生 z-index 第一問題。
頂部導航欄 (Navbar)
移動端(duan)打開橫向(xiang)菜(cai)單后,同(tong)樣需(xu)要禁(jin)滾 body:
const toggleMobileMenu = () => {
mobileMenuOpen.value = !mobileMenuOpen.value;
if (mobileMenuOpen.value) {
disableBodyScroll();
} else {
enableBodyScroll();
}
};
5. 總結
移動端(duan)彈窗和橫向菜(cai)單(dan)的滾動漏洞,在(zai)經典 WebApp 中(zhong)很容易(yi)被忽視。使(shi)用上(shang)述方法,可(ke)以在(zai) Vue3 / Nuxt3 項目中(zhong)簡單(dan)且穩定地處理這些(xie)問題,但是(shi)并沒有分開詳細介(jie)紹,只給大家(jia)(jia)提供思(si)路,因為現在(zai) AI 較多,大家(jia)(jia)參考(kao)完進行頭(tou)腦風暴比(bi)較好。
如需查看全部(bu)實現代(dai)碼,可以留(liu)言,會(hui)把彈窗代(dai)碼分(fen)享下(xia)。
