從前面從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計可以知道,小程式大部分是通過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元素上