在網站進行移動端 Web 適配開發中,彈窗和導航欄彈出等常常會出現一些問題,如果是奇奇怪怪的客户嚴格要求的話,那麼就會有下面這些情況:
- 打開彈窗後頁面自動放大,視圖區被放大到看不全
- 打開對話框打開後背景仍然能滾動
- 導航欄彈窗後,背後內容可滾動,影響體驗
- 點擊聚焦輸入框,導致視圖放大
下面通過 Nuxtjs3 示例,分析問題原因並提供一些解決方案,有錯誤或者有其他想法可以評論提出!
1. 問題分析
在 iOS Safari 中,可能開發者都看到了已經對 body 進行超出隱藏了,但是就會出現穿透滑動滾動現象(查了下資料説是 iOS Safari 對這個屬性無效,實際大家可以自己去看看);
彈窗中有輸入框或效果切換時,瀏覽器會觸發自動縮放行為,導致全頁視圖放大一丟丟,視圖溢出和亂移。
2. 解決思路
視圖放大問題
通過 meta viewport 可以限制瀏覽器的縮放操作(也就是 html 原生放在 head 上的 meta):
useHead({
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' }
]
})
禁滾 body & 禁滑
當彈窗或菜單打開時,通過動態修改 body CSS 來防止背景滾動以及監聽觸碰滑動(可單獨拎出):
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)
};
通過記錄當前滾動位置,關閉彈窗後恢復滾動狀態,可以防止頁面上下移動。
注:這裏是自定義的彈窗組件,如果禁滑的話要在對應彈窗內部加上單防穿透(@touchmove.stop),避免內部超出不可觸摸滾動,原理如下(可以看看官方屬性進行理解):
當用户在頁面上滑動時:
[觸摸點]
↓ 觸發 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 時禁滾,其他情況恢復滾動。
3. 實現例子
客服對話窗 (Chat Window)
watch(visible, (newVal) => {
if (newVal) {
minimized.value = false;
loadMessagesFromStorage();
disableBodyScroll();
} else {
enableBodyScroll();
}
});
每次彈窗打開,禁滾 body;關閉後恢復滾動。
通過 Teleport 把對話窗注入 body,保證層級級次上在最上方,避免被其他元素遮擋或發生 z-index 第一問題。
4.頂部導航欄 (Navbar)
移動端打開橫向菜單後,同樣需要禁滾 body:
const toggleMobileMenu = () => {
mobileMenuOpen.value = !mobileMenuOpen.value;
if (mobileMenuOpen.value) {
disableBodyScroll();
} else {
enableBodyScroll();
}
};
5. 總結
移動端彈窗和橫向菜單的滾動漏洞,在經典 WebApp 中很容易被忽視。使用上述方法,可以在 Vue3 / Nuxt3 項目中簡單且穩定地處理這些問題,但是並沒有分開詳細介紹,只給大家提供思路,因為現在 AI 較多,大家參考完進行頭腦風暴比較好。