天天看點

[極緻使用者體驗] 微信設定大字号後,iOS加載網頁時閃動怎麼辦?背景分析原因開發代碼,解決問題補充說明寫在最後

大家好,我是公衆号「線下聚會遊戲」作者HullQin,開發了《聯機桌遊合集》,是個網頁,可以很友善的跟朋友聯機玩鬥地主、五子棋等遊戲。

背景

之前的文章,我提到網頁開發的一個常見問題:《在微信大字号模式下,網頁樣式亂了怎麼辦?》。上文中提供了一種解決方案,在使用者調整微信字型大小後,可以保證網頁字型大小不變,解決了樣式錯亂的問題。

但是上面的解決方案,背離了微信「關懷模式」和「大字号模式」的初衷。是以我又寫了一篇文章:《讓你的網頁,适配微信大字号模式!體驗超好,快來收藏》,介紹了幾種方案,助你網頁适配微信的大字号。

但是在iOS作業系統下(iPhone或iPad),仍然有個問題。如果使用者之前設定了網頁的大字号,進入任意網頁時,就有個明顯的閃動:

[極緻使用者體驗] 微信設定大字号後,iOS加載網頁時閃動怎麼辦?背景分析原因開發代碼,解決問題補充說明寫在最後

但是在安卓,沒有這個問題,會直接以大字号展示。

分析原因

這與微信大字号的實作方式有關。而實作方式又與機型有關。包括以下3種:

安卓

在安卓,網頁不會閃動,是因為安卓使用setTextZoom設定字型比例。早在html加載前,就已經設定好了,它也無需修改dom。

[極緻使用者體驗] 微信設定大字号後,iOS加載網頁時閃動怎麼辦?背景分析原因開發代碼,解決問題補充說明寫在最後

後續使用者修改網頁字号時,隻需要安卓調用

setTextZoom

即可(這是微信用戶端實作的,我們Web開發者無需關注)。

iPhone

iPhone閃動,是因為它必須等到html加載完,渲染頁面的JS也執行完,再給

body

标簽添加text-size-adjust這個CSS樣式(它需要修改dom)。

後續使用者修改網頁字号時,隻需修改

body

text-size-adjust

這個CSS樣式即可(這是微信用戶端實作的,我們Web開發者無需關注)。

解決思路

如果我們能知道使用者設定的縮放比例,在我們的JS主動給

body

标簽添加text-size-adjust)這個CSS樣式(在渲染之前就添加),那麼閃動的問題就解決了。

iPad

我今天研究了一下,發現微信iPad大字号和iPhone大字号,實作方式居然不一樣!

iPad閃動,是因為它必須等到html加載完,渲染頁面的JS也執行完,再給所有包含Text文本的标簽添加

style

屬性,裡面直接指定了計算後的

font-size

。當然,網頁元素的原始字号大小儲存在了元素的屬性

mp-original-font-size

裡。

動态指派後,你的DOM結構會變成這樣:

[極緻使用者體驗] 微信設定大字号後,iOS加載網頁時閃動怎麼辦?背景分析原因開發代碼,解決問題補充說明寫在最後

後續使用者修改字型時,會根據

mp-original-font-size

和放大比例,重新計算所有元素的

font-size

,并動态設定所有元素的

style

中的

font-size

可能有朋友要問了:一次性設定這麼多

style

屬性,這不會很卡嗎?

沒錯,是很卡,在安卓、iPhone、iPad上設定大字号,就iPad最卡!

解決思路

如果我們能知道使用者設定的縮放比例,在我們的JS主動給所有的包含Text的元素添加

font-size

這個CSS樣式(在渲染之前就添加),那麼閃動的問題就解決了。

開發代碼,解決問題

我通過神秘方式,擷取了一串神秘代碼:

JSON.parse(window.__wxWebEnv.getEnv()).fontScale

,在微信内置浏覽器中,執行這個方法,是可以獲得使用者設定的縮放大小的。當然,你必須先引用「微信JS SDK」,才能用這個代碼,否則會報錯,建議結合try catch使用它。

需要在html檔案head引入:

<script src="//res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
           

下面針對iPhone和iPad這2種機型,分别給出解決方案。

iPhone

思路介紹:給body設定一個

-webkit-text-size-adjust

樣式,按照字号縮放比例設定即可。

const initIPhoneFontSize = () => {
  try {
    document.body.style.webkitTextSizeAdjust=JSON.parse(window.__wxWebEnv.getEnv()).fontScale+'%';
  } catch {}
};
           

iPad

思路介紹:

  1. 通過user agent判斷浏覽器類型和機型。如果發現是微信浏覽器,且是iPad,那麼就執行本段代碼,調整初始字号。
  2. 如果目前使用者字号

    fontScale

    為1,無需縮放,直接結束函數。
  3. 如果目前使用者字号

    fontScale

    不是1,就先把整個

    body

    隐藏,并繼續以下步驟。
  4. body

    開始,遞歸周遊子節點,設定所有的

    mp-original-font-size

  5. body

    開始,遞歸周遊子節點,設定所有的

    style

    裡的

    font-size

    ,是根據

    mp-original-font-size

    乘以

    fontScale

    計算出來的。
  6. 全部指派後,再把

    body

    展示出來。至此,初始字号設定完畢,畫面不再閃現。
const initIPadFontSize = () => {
  let fontScale = 1;
  try {
    fontScale = JSON.parse(window.__wxWebEnv.getEnv()).fontScale;
  } catch {}
  if (fontScale === 1) return;
  document.body.style.display='none';
  const addOriginFontSize = (root) => {
    let flag = false;
    root.childNodes.forEach((node) => {
      if (node instanceof Text) {
        if (flag) return;
        flag = true;
        root.setAttribute('mp-original-font-size', getComputedStyle(root).fontSize);
      } else if (!(node instanceof HTMLScriptElement)) {
        addOriginFontSize(node);
      }
    });
  };
  const addFontSize = (nodes) => {
    nodes.forEach((node) => {
      const fontSize = node.getAttribute('mp-original-font-size');
      if (fontSize) {
        node.style.fontSize = parseFloat(fontSize) * fontScale + 'px';
      }
      if (node.children) addFontSize(Array.from(node.children));
    });
  };
  addOriginFontSize(document.body);
  addFontSize(Array.from(document.body.children));
  document.body.style.display='';
};
           

判斷機型,執行相應函數

if (/MicroMessenger/i.test(navigator.userAgent) {
  if (/iPad/i.test(navigator.userAgent)) initIPadFontSize();
  else if (/iPhone/i.test(navigator.userAgent)) initIPhoneFontSize();
}
           

補充說明

如何實作:按需引入微信JS SDK?

如果使用者不是使用的微信浏覽器,那麼完全沒有必要引入微信JS SDK。

你可以先判斷user agent,再動态引入:

if (/MicroMessenger/i.test(navigator.userAgent) && /iPad|iPhone/i.test(navigator.userAgent)) {
  const scriptEl = document.createElement('script');
  scriptEl.setAttribute('src', '//res.wx.qq.com/open/js/jweixin-1.6.0.js');
  document.head.appendChild(scriptEl);
  scriptEl.addEventListener('load', () => {
    const fontScale = JSON.parse(window.__wxWebEnv.getEnv()).fontScale;
  });
}
           

這樣,在回調函數中,可以擷取

fontScale

。但是為了解決閃動問題,你可能需要在

document.createElement('script')

前,先把

body

隐藏(

document.body.style.display='none'

)。

iPad的字号縮放方式有辦法跟iPhone統一嗎?

還真有。

隻要我們設定了這個變量:

window.__wxjs_ipadfontsolution = false;
           

那麼在iPad中,縮放字号,也會通過

-webkit-text-size-adjust

方式來縮放了。隻是存在相容性問題,一些iPad不支援這個樣式,使用者就無法通過微信控制字号了。

此時,整個解決方案是最簡潔的:

<script src="//res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<!-- 上面的腳本,放在head裡 -->
<!-- 下面的腳本,放在body裡 -->
<script>
try {
  if (/MicroMessenger/.test(navigator.userAgent) && /iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)) {
    window.__wxjs_ipadfontsolution = false;
    document.body.style.webkitTextSizeAdjust=JSON.parse(window.__wxWebEnv.getEnv()).fontScale+'%';
  }
} catch {}
</script>
           

如果你還希望按需引入微信JS SDK,再做一些容錯處理,整個邏輯就是這樣子:

if (/MicroMessenger/i.test(navigator.userAgent) && /iPhone|iPad|iPod|iOS/i.test(navigator.userAgent)) {
  document.body.style.display = 'none';
  const scriptEl = document.createElement('script');
  scriptEl.setAttribute('src', '//res.wx.qq.com/open/js/jweixin-1.6.0.js');
  document.head.appendChild(scriptEl);
  scriptEl.addEventListener('error', () => {
    document.body.style.display = '';
  });
  scriptEl.addEventListener('load', () => {
    try {
      window.__wxjs_ipadfontsolution = false;
      document.body.style.webkitTextSizeAdjust=JSON.parse(window.__wxWebEnv.getEnv()).fontScale+'%';
      document.body.style.display = '';
    } catch {
      document.body.style.display = '';
    }
  });
}
           

寫在最後