第三代移動端佈局方案

你們有沒有發現淘寶的H5移動端沒有使用任何rem和vw單位,而是和web端項目同樣,使用的是px單位。雖然是px但它也很完美的將整個頁面渲染了出來。那淘寶的FE是怎麼實現的呢?javascript

最近在研究關於佈局的設計方案,經過學習理解阿里的fusion.design的設計思想並結合手機淘寶H5版的px佈局問題。逐漸有了一些想法,這裏進行綜合整理,也算是拋磚引玉吧。css

一、rem和vw

rem和vw都是爲了解決移動端適配問題。rem方案中最成功的就是淘寶的lib-flexible了,它是經過javascript將整個佈局分割成10份,從而進行有效佈局。不過有計算dpr的問題,在一些dpr比較怪異的手機上會出現脫相的問題。後來又產生了vw佈局,使用了vw以後,也再無需經過javascript的幫助進行佈局的切分,而是自動的將整個佈局切割爲等分的100份,也就是1vw = 1%的頁面寬度。html

1.一、 rem的問題

  • 在奇葩的dpr設備上表現效果不太好,好比 一些華爲的高端機型 用rem佈局會出現錯亂。
  • 設置根字體大小的方式有兩種,一種是媒體查詢,優勢:不須要額外使用js去更改html的字體,缺點:不連續,或者說並能完* 全實現對全部設備的佈局規範統一;
  • 另外一種是js動態更改html字體,優勢:連續;缺點:不如直接寫媒體查詢的體驗好;
  • 不支持css3 calc的須要大量密集的 @media hack;
  • 使用iframe引用也會出現問題;
  • 須要解決在ios上的1px邊框問題,可是這個在lib-flexible中已經解決:(1px變2px, 又被 initial-scale=0.5 縮小了一半
  • rem須要引入一個lib庫
  • html的font-size設置到12px如下仍是會按照12px=1rem來計算,這樣全部使用了rem單位的尺寸都是錯的

1.二、vw的問題

  • 支持程度不太好,安卓4.4如下都不支持

1.三、它們共同的問題

  • 都須要計算以達到適配的目的
  • 額外引入工具,在編譯階段完成轉換
  • UI的迴歸測試不友好。畢竟設計稿是px,而頁面是rem或vm。
  • 都是相對單位。rem 的比例是能夠經過控制 html 字體大小來控制的,而vw的比例是固定的。
  • 沒法和web項目共用統一套工程化方案,由於web項目不須要使用rem和vw單位。

二、移動端佈局的初衷

  1. 能夠輕鬆搞定任意佈局
  2. 經過設計稿,可讓應用在不一樣的設備上有完美的體驗效果

雖然rem和vw能夠很好完成它們的初衷,不過同時它們也是有代價的(就是它們存在的問題)。那有沒有一種方案能夠規避掉以上rem和vw的問題又能夠很好的完成初衷哪?vue

2.一、一個新的Units單位(該小節摘自https://fusion.design/design/doc/16)

DP爲UI設計中的惟一可用單位java

因爲DP在不一樣設備中的叫法不一樣,且用於描述字號的單位有所不一樣(如SP,PT),但其基本計算方法和原則相同且通用,因此在設計過程當中,咱們考慮到嚴謹性,統一採用只寫數字不帶單位的方法書寫。react

選用DP的緣由webpack

像素密度PPI:指每英寸包含的像素(Px)個數ios

如圖同一物理尺寸(肉眼所見尺寸)下,低密度顯示器的像素個數明顯小於高密度顯示器的像素個數,因此像素(PX)在多變的設備和分辨率下不是一個穩定可用的單位css3

與密度無關的像素(DP):設備獨立像素web

如圖,DP與PX的對比可見,DP能夠自適應屏幕的密度,無論屏幕密度怎麼變化,實際顯示的物理尺寸相同,DP能夠保證物理尺寸的一致性,DP是目前最適合UI設計的單位,同時也是使代碼語言相通的尺寸。

轉換公式 當屏幕的PPI爲160時,1DP=1PX;例:Iphone4,Iphone5,Iphone6,PPI爲326,在這些屏幕之下1DP=2PX

DP=(PX160)/PPI PX=DP(PPI/160)

切圖規則 DP是與開發代碼共用的語言,但一些須要置入的jpg,png等圖片非矢量,依舊採用px做爲單位,這個時候咱們須要將圖片適配到不一樣PPI的屏幕中去。

圖示,爲一塊banner適配到不一樣分辨率屏幕時的像素值:

但實際場景中,沒法爲各類屏幕作切圖適配,我麼遵循大圖可壓縮小,小圖不可變大的原則:

  • 【Mobile】選擇3x圖輸出,適配於ios和andirod
  • 【Web】選擇2x的圖輸出,適配普通屏幕和retina屏幕

畫布設置規則

  • 【Mobile】選擇375*667做爲繪圖尺寸
  • 【Andriod】 選擇360*640做爲繪圖尺寸
  • 【Web】使用1440寬做爲繪圖尺寸

三、具體實現

主要思路

  1. 設計稿中統一使用dp做爲像素單位,具體規則參考上面的切圖規則畫布設計規則
  2. rem和vw多多少少存在各類問題,因此統一使用px做爲實現單位
  3. web和wap可使用同一套工程方案
  4. 實現設計稿的dp到真實應用中px的映射關係,而且px會隨着設備窗口大小的改變而改變

固然,若是稿子是px的也能夠手動將px轉換爲dp。

想要實現這個總體方案,核心就在於第4條(實現設計稿的dp到真實應用中px的映射關係),而且這個過程只靠工程化的編譯階段是沒法完美解決的,必須和瀏覽器運行時一塊兒配合工做纔可以達成咱們的目標。

前提

  1. 業務模塊的css不能夠抽離爲獨立的css文件,必須輸出在js文件中(style-loader的能力),這樣纔有改變css內容的基本能力。
  2. 定義一個尺寸單位dp,標識這是在設計稿上的尺寸(相似於小程序中的rpx)。
  3. 並非全部的px都須要作彈性轉換的,對於須要作彈性轉換的容器的px統一改成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";
	})
}
複製代碼

3.2.一、方式一:styled-components + css-in-js + JSX

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複雜度增長。

3.2.二、方式二:在瀏覽器運行時動態計算

自定義一個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);
複製代碼

3.2.三、優缺點

優勢:
  1. 文章開頭所提到的rem和vw都存在衆多問題,該方案均可以完美解決。
  2. 還能夠和其餘任何單位混合使用,這意味這使用這種方案的同時還可使用以前的rem和vw方式。
缺點:
  1. 無論使用方式一仍是方式二,都會對項目的工程化複雜度增長,不過和目前處理rem以及vm的px2rem以及px2viewport等工具相比,這點複雜度根本不值一提。
  2. 增長了js bundle文件的體積,減小了css文件的體積。不過沒有總體的體積沒有增長,僅僅是將部分業務的css內容搬到了js文件裏。

最後

  1. 借用了大部分工程化的能力,主要仍是用來css in js的能力
  2. 可是這樣有個缺點。就是視窗大小改變的時候會出現佈局錯亂。不過影響不大,由於真機裏是不會出現窗口大小改版的。並且這一套方案徹底抹平了web和wap的差別性,在開發層面徹底一直。須要縮放就用dp,不須要就用np和px做爲單位便可
  3. 這套方案仍是蠻不錯的,比rem和vw好不少。確實屬於第三代移動端佈局方案了
相關文章
相關標籤/搜索