動態

詳情 返回 返回

網站在蘋果 Safari 進行適配遇到的問題 - 動態 詳情

在網站進行移動端 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 較多,大家參考完進行頭腦風暴比較好。

Add a new 評論

Some HTML is okay.