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