從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

wonyun發表於2019-07-25

從前面從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計可以知道,小程式大部分是通過web技術進行渲染的,也就是最終通過瀏覽器的dom tree + cssom來生成渲染樹;既然最終是通過css來繪製ui佈局,我們知道小程式提供的自適應css單位rpx在瀏覽器環境根本不被識別,所以小程式最終還是將rpx單位轉化為瀏覽器識別的css長度單位,到底是怎麼轉化的呢,本節就來探討一下轉化機制。

小程式樣式轉換

從微信小程式開發者工具原始碼看實現原理(二)- - 小程式技術實現中可以知道,小程式中的wxss樣式檔案進行的主要轉換轉換rpx單位,檢視層模板注入轉換後的wxss程式碼如下圖:

從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

上面的內容就是注入到檢視層pageframe模板中的css程式碼,其內容包括:

  • 提供rpx單位到px單位的轉換
  • 提供動態插入轉換後樣式內容到dom中的js方法
  • 每個頁面引入公共樣式,即app.wxss轉換後的css內容

上面提到的這些轉換操作都是內建到小程式的wcsc可執行程式中,通過呼叫可執行程式來完成具體轉換工作。最終注入到頁面中的css內容如下圖所示:
從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

小程式自適應單位rpx轉換

小程式的自適應佈局採用的內部實現的rpx來完成,但是其不被web識別,所以rpx單位轉換是指:

是將小程式的css單位rpx轉換為web識別的css單位px

那麼小程式怎麼來進行rpx與px之間的轉換呢?先來看一下官網有關rpx的描述:

rpx(responsive pixel): 可以根據螢幕寬度進行自適應。規定螢幕寬為750rpx。如在 iPhone6 上,螢幕寬度為375px,共有750個物理畫素,則750rpx = 375px = 750物理畫素,1rpx = 0.5px = 1物理畫素。

由此可以看出,小程式在實現rpx轉換時,不論是什麼螢幕的手機,都是將螢幕寬度固定設為750rpx,然後根據實際螢幕的裝置畫素比dpr(dpr = 裝置畫素 / css畫素)來進行轉換的。具體對應關係如下:

1rpx = (number/ 750) * 裝置寬度 px

下面通過小程式開發者工具簡單分析小程式wcsc可執行命令程式生成的有關rpx轉換的js程式碼

首先獲取小程式的裝置寬度

從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

小程式開發者工具在初始渲染一個頁面時會首先獲取裝置寬度deviceWidth和dpr,然後會通過checkDeviceWidth方法(wcsc可執行命令注入的程式碼)檢查修正二者的值,因為螢幕orientation方向可能變化,在上面程式碼有這麼一段:

if (window.screen.orientation && /^landscape/.test(window.screen.orientation.type || "")) {
   newDeviceWidth = newDeviceHeight;
}

該程式碼利用window.screen.orientation來判斷手機的橫豎方向,若處於橫屏的時候,webview的寬度與高度值會互換,即高度值就是螢幕的真實寬度;需要注意的是小程式開發者工具的webview這一點與移動端手機表現不太一致。

另外,需要補充兩點:

  • 利用window.screen.orientation這個判斷手機方向的特性大部分瀏覽器支援情況比較差,具體可以看這裡。但是小程式開發者工具使用基於chrome的webview,這個是支援的。
  • 程式碼的window.__checkDeviceWidth__在小程式的一些基礎庫(如2.3.2)中是沒有定義的;但是新的版本(2.7.7)是有該方法定義的,但是從什麼版本開始支援的不得而知。

rpx單位轉換

從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

正如官網所描述的,小程式將螢幕固定750rpx,然後根據當前螢幕寬度以及設定的rpx值,最終推算出rpx對應的px值。
補充一點,在設定的rpx值轉換為px值大於0小於1時,不論設定的rpx值是多少,最終在dpr不是1的ios情況下會始終返回0.5px,其他情況始終返回1px;例如下面程式碼:

.text {
  height: 1rpx;
  background: #333;
}

最終在開發者工具中轉換的px值為0.5,如下圖:
從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

小程式螢幕旋轉自適應轉換過程

通過上面轉換rpx值,一旦轉換完成後轉換值就固定了;但是對於支援螢幕旋轉的情況,這顯然不是我們希望的結果,期望根據螢幕旋轉的方向來重新轉換對應的rpx值。
小程式從2.4.0基礎版本開始通過配置"pageOrientation": "auto"開始支援螢幕旋轉,這就需要知道螢幕發生變化的時機來做對應的處理。具體分兩個方面轉換:wxss樣式檔案轉換style內聯樣式轉換

wxss樣式檔案自適應轉換

首先,在檢視層,wxss樣式檔案經rpx初始轉換後並將樣式注入到頁面過程中,會向window.__rpxRecalculatingFuncs__陣列中收集視窗變化時的回撥;先看wcsc可執行程式輸出的處理rpx轉換相關的setCssToHead函式實現,其最終返回rewritor函式,對應程式碼如下圖:
從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

可以看出在轉換後的樣式嵌入到document.head中後,依然儲存有建立的style元素的控制程式碼,在頁面視窗變更時執行對應的回撥來修正rpx轉換後的px值。

然後,在小程式基礎庫WAWebview內部初始時會使用wx.onWindowResize(fn)來註冊視窗變更的事件回撥,註冊事件內部會執行window.__rpxRecalculatingFuncs__中的回撥,具體程式碼如下圖:
從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

這樣,檢視視窗變更時就會通知樣式檔案進行重新rpx轉換,最後將最新轉換的樣式內容更新到頁面中。

那麼,小程式如何把握螢幕切換的觸發時機呢?

這個觸發時機在微信環境是由native提供感知能力,開發環境則是小程式開發工具本身提供支援。拿開微信開發者工具來說明具體的整個過程:

  • 檢視層與業務邏輯層分別註冊onViewDidResize事件回撥
  • 開發者工具感知到視窗變化會通過websocket方式向檢視層和業務邏輯層同時傳送執行onViewDidResize回撥的訊息
  • onViewDidResize會分別執行通過wx.onWindowResize(fn)註冊的回撥

內聯樣式自適應轉換

內聯樣式轉換在底層基礎庫是採用transformRpx方法來轉換rpx值的,思路與上面介紹的一樣,唯一不同點就是是否對0進行修正,具體程式碼如下:

var $ = function(e) { // e為要轉換的rpx值,V為裝置寬度
   return 0 === e && function(e) {
      var t = window.__wcc_version_info__;
      if (t) return t[e];
    }("fixZeroRpx") ? 0 : (e = e / 750 * V, 0 === (e = Math.floor(e + 1e-4)) ? 1 !== dpr && isIPhone ? .5 : 1 : e)
 }

通過獲取window.__wcc_version_info__.fixZeroRpx的值來判斷rpx為0時如何轉換;而window.__wcc_version_info__的定義賦值是在wcc可執行命令轉換wxml檔案生成的js指令碼中完成的,下面是wcc生成有關賦值程式碼:
從微信小程式開發者工具原始碼看實現原理(四)- - 自適應佈局

具體樣式檔案自適應轉換過程如下:

  • 檢視層在生成virtual dom過程中會收集每個元素的屬性,其中包括style屬性
  • 在生成dom過程中,針對元素的style屬性使用transformRpx進行轉換,轉換後內容應用到具體dom元素
  • 為含有rpx單位內聯樣式dom元素繫結視窗變化回撥,視窗變化時style中的rpx進行重新轉換並應用到dom元素上

相關文章