你們有沒有發現淘寶的H5移動端沒有使用任何rem和vw單位,而是和web端項目同樣,使用的是px單位。雖然是px但它也很完美的將整個頁面渲染了出來。那淘寶的FE是怎麼實現的呢?
最近在研究關於佈局的設計方案,經過學習理解阿里的fusion.design的設計思想並結合手機淘寶H5版的px佈局問題。逐漸有了一些想法,這裏進行綜合整理,也算是拋磚引玉吧。javascript
rem和vw都是爲了解決移動端適配問題。rem方案中最成功的就是淘寶的lib-flexible了,它是經過javascript將整個佈局分割成10份,從而進行有效佈局。不過有計算dpr的問題,在一些dpr比較怪異的手機上會出現脫相的問題。後來又產生了vw佈局,使用了vw以後,也再無需經過javascript的幫助進行佈局的切分,而是自動的將整個佈局切割爲等分的100份,也就是1vw = 1%的頁面寬度。css
雖然rem和vw能夠很好完成它們的初衷,不過同時它們也是有代價的(就是它們存在的問題)。那有沒有一種方案能夠規避掉以上rem和vw的問題又能夠很好的完成初衷哪?html
DP爲UI設計中的惟一可用單位vue
因爲DP在不一樣設備中的叫法不一樣,且用於描述字號的單位有所不一樣(如SP,PT),但其基本計算方法和原則相同且通用,因此在設計過程當中,咱們考慮到嚴謹性,統一採用只寫數字不帶單位的方法書寫。java
選用DP的緣由react
像素密度PPI:指每英寸包含的像素(Px)個數webpack
如圖同一物理尺寸(肉眼所見尺寸)下,低密度顯示器的像素個數明顯小於高密度顯示器的像素個數,因此像素(PX)在多變的設備和分辨率下不是一個穩定可用的單位ios
與密度無關的像素(DP):設備獨立像素css3
如圖,DP與PX的對比可見,DP能夠自適應屏幕的密度,無論屏幕密度怎麼變化,實際顯示的物理尺寸相同,DP能夠保證物理尺寸的一致性,DP是目前最適合UI設計的單位,同時也是使代碼語言相通的尺寸。web
轉換公式
當屏幕的PPI爲160時,1DP=1PX;例:Iphone4,Iphone5,Iphone6,PPI爲326,在這些屏幕之下1DP=2PX
DP=(PX*160)/PPI
PX=DP*(PPI/160)
切圖規則
DP是與開發代碼共用的語言,但一些須要置入的jpg,png等圖片非矢量,依舊採用px做爲單位,這個時候咱們須要將圖片適配到不一樣PPI的屏幕中去。
圖示,爲一塊banner適配到不一樣分辨率屏幕時的像素值:
但實際場景中,沒法爲各類屏幕作切圖適配,我麼遵循大圖可壓縮小,小圖不可變大的原則:
畫布設置規則
切圖規則
和畫布設計規則
固然,若是稿子是px的也能夠手動將px轉換爲dp。
想要實現這個總體方案,核心就在於第4條(實現設計稿的dp到真實應用中px的映射關係),而且這個過程只靠工程化的編譯階段是沒法完美解決的,必須和瀏覽器運行時一塊兒配合工做纔可以達成咱們的目標。
dp
,標識這是在設計稿上的尺寸(相似於小程序中的rpx
)。dp
,不然繼續使用px。假設咱們根據Mobile設計稿定義一個移動端H5的容器元素:
<div class="box"> <div class="tip">this is tip</div> </div>
.box { /* 這裏使用的單位爲dp,表示須要根據設備大小進行彈縮 */ width: 100dp; height: 150dp; background: red; } .box .tip { /* 使用的單位爲px,不須要根據設備大小進行彈縮。不管設備怎麼變化,該元素的寬高都是10像素。 */ width: 10px; height: 10px; background: blue; }
最終,元素.box
會根據設備的寬高的改變而改變自身的大小,下方就是.box
元素在不一樣設備下的寬和高:
設備 | 寬度 | 高度 |
---|---|---|
設計稿 | 100dp | 150dp |
iPhone 5/SE | 85.33px | 128px |
iPhone 6/7/8 | 100px | 150px |
iPhone 6/7/8 Plus | 110.4px | 165.60px |
iPhone X | 100px | 150px |
Galaxy S5 | 96px | 144px |
在實現這個功能必須先提供一個轉換dp爲px的幫助函數:unitParser。由於接下來的兩種方式中都須要這個函數來幫助咱們實現最終目的。
const allowMiniPixel = () => { let allow = false; if (window.devicePixelRatio && devicePixelRatio >= 2) { let ele = document.createElement("div"); ele.style.border = ".5px solid transparent"; document.body.appendChild(ele); allow = 1 === ele.offsetHeight; document.body.removeChild(ele); } return allow; }(); function unitParser(unit) { let type = void 0 === unit ? "undefined" : getType(unit); if ("number" === type) { unit += "dp" } if ("string" !== type) { return unit; } let regExp = /^([\d\.]+)(np|dp)?$/g; return unit.replace(regExp, (chars, count, suffix) => { count = Number(count) switch (suffix) { case "np": // np不作轉換。1np就是1px 100np就是100px break; case "dp": default: // 注意這裏375。說明的上文說了,設計稿是按照iphone 6的375進行設計的。 // deviceWidth爲屏幕的寬度。iphone 5/SE爲320、iphone 6/7/8爲375 count = count / 375 * deviceWidth }; if (!allowMiniPixel && count < 1) { count = 1 } return count + "px"; }) }
Vue:
import styled from 'vue-emotion' import unitParser from './unitParser.js'; const box = styled('div')` width: ${unitParser("100dp")}; height: ${unitParser("150dp")}; background: red; ` const tip = styled(div)` width: 10px; height: 10px; background: blue; ` new Vue({ render() { return ( <box> <tip>this is a tip</tip> </box> ) } })
react:
import styled from 'styled-components'; import unitParser from './unitParser.js'; const Box = styled.div` width: ${unitParser("100dp")}; height: ${unitParser("150dp")}; background: red; ` const Tip = styled.div` width: 10px; height: 10px; background: red; ` render( <Box> <Tip>this is a tip</Tip> </Box> )
注意:
使用此方案須要注意和sass、post-css等工具的結合使用問題,會增長必定的工程複雜度。另外這種方案會產生大量的元素style屬性,致使dom複雜度增長。
自定義一個webpack的css-loader,進行unitParser處理。
function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css = ".box {width: " + unitParser("100dp") + "; height: " + unitParser("150dp") + "; background: red;} .box .tip {width: 10px; height:10px; background: blue}"; styleInject(css);