你们有没有发现淘宝的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);