移动端布局基础概念指南

前言这篇文章主要讨论了移动端页面渲染与布局的一些基本概念例如 viewport 和 css 像素, 这些内容是了解移动端页面适配的基本前提. 在这篇文章中记录了这些基本的概念, 因为我并不从事移动端开发, 文章有误, 欢迎指正. 移动端渲染表现为了简单起见, 我们不从概念讲起, 我们先来看看一个固定宽度的元素被放置到移动端浏览器中会发生什么. 有如下代码: <!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Document</title> <style> .example{ width: 900px; background-color: aqua; } </style></head><body> <div class="example"> 我的宽度是 900px </div></body></html>当然桌面浏览器渲染效果肯定在我们意料之中啦✨: 图片: 1920px下chrome渲染的效果: 而下面展示了图片展示了几个移动端浏览器渲染的结果: 图片: 移动端浏览器渲染效果: 这里我们简单的介绍一下图片中浏览器的版本, 这些浏览器在后文的截图中版本都是不会发生变化的. 浏览器版本平台chrome75windows10chrome75android9edge42android9Samsung9.3android9仔细观察我们发现:900px 宽的 div 没有占满屏幕, 也就是说屏幕的宽度比 900px 还要大. 那么屏幕宽度到底是多大呢, 我们插入一行代码来获取HTML元素的宽度: <script> document.write(`html元素的宽度是${document.documentElement.clientWidth}`);</script>不同浏览器的显示结果为: 他们都输出了同样的数据那就是 980 在某种程度上我们知道了页面的宽度, 原来页面是 980px 的宽度所以 900px 的div无法铺满一行. 对于移动浏览器它感知到自己的视口的宽度是 980px, 此时它渲染的比例就和一个 横向分辨率为 980px 的桌面显示器渲染的一样. 只不过移动终端的屏幕是一个小号的显示器而已.在没有更改页面的设置的情况下或者说默认的情况下, 移动终端的浏览器会按照 980px 的宽度来渲染页面, 然后再将页面缩放到适合屏幕的大小. ...

October 13, 2019 · 4 min · jiezi

第三代移动端布局方案

第三代移动端布局方案大家有没有发现淘宝的H5移动端没有使用任何rem和vw单位,而是和web端项目一样,使用的是px单位。虽然是px但它也很完美的将整个页面渲染了出来。那淘宝的FE是怎么实现的呢?最近在研究关于布局的设计方案,通过学习理解阿里的fusion.design的设计思想并结合手机淘宝H5版的px布局问题。逐渐有了一些想法,这里进行综合整理,也算是抛砖引玉吧。1、rem和vwrem和vw都是为了解决移动端适配问题。rem方案中最成功的就是淘宝的lib-flexible了,它是通过javascript将整个布局分割成10份,从而进行有效布局。不过有计算dpr的问题,在一些dpr比较怪异的手机上会出现脱相的问题。后来又产生了vw布局,使用了vw之后,也再无需通过javascript的帮助进行布局的切分,而是自动的将整个布局切割为等分的100份,也就是1vw = 1%的页面宽度。1.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.2、vw的问题支持程度不太好,安卓4.4以下都不支持1.3、它们共同的问题都需要计算以达到适配的目的额外引入工具,在编译阶段完成转换UI的回归测试不友好。毕竟设计稿是px,而页面是rem或vm。都是相对单位。rem 的比例是可以通过控制 html 字体大小来控制的,而vw的比例是固定的。无法和web项目共用统一套工程化方案,因为web项目不需要使用rem和vw单位。2、移动端布局的初衷可以轻松搞定任意布局通过设计稿,可以让应用在不同的设备上有完美的体验效果虽然rem和vw可以很好完成它们的初衷,不过同时它们也是有代价的(就是它们存在的问题)。那有没有一种方案可以规避掉以上rem和vw的问题又可以很好的完成初衷哪?2.1、一个新的Units单位(该小节摘自https://fusion.design/design/…)DP为UI设计中的唯一可用单位由于DP在不同设备中的叫法不同,且用于描述字号的单位有所不同(如SP,PT),但其基本计算方法和原则相同且通用,所以在设计过程中,我们考虑到严谨性,统一采用只写数字不带单位的方法书写。选用DP的原因像素密度PPI:指每英寸包含的像素(Px)个数如图同一物理尺寸(肉眼所见尺寸)下,低密度显示器的像素个数明显小于高密度显示器的像素个数,所以像素(PX)在多变的设备和分辨率下不是一个稳定可用的单位与密度无关的像素(DP):设备独立像素如图,DP与PX的对比可见,DP可以自适应屏幕的密度,不管屏幕密度怎么变化,实际显示的物理尺寸相同,DP可以保证物理尺寸的一致性,DP是目前最适合UI设计的单位,同时也是使代码语言相通的尺寸。转换公式当屏幕的PPI为160时,1DP=1PX;例:Iphone4,Iphone5,Iphone6,PPI为326,在这些屏幕之下1DP=2PXDP=(PX160)/PPIPX=DP(PPI/160)切图规则DP是与开发代码共用的语言,但一些需要置入的jpg,png等图片非矢量,依旧采用px作为单位,这个时候我们需要将图片适配到不同PPI的屏幕中去。图示,为一块banner适配到不同分辨率屏幕时的像素值:但实际场景中,无法为各种屏幕做切图适配,我么遵循大图可压缩小,小图不可变大的原则:【Mobile】选择3x图输出,适配于ios和andirod【Web】选择2x的图输出,适配普通屏幕和retina屏幕画布设置规则【Mobile】选择375667作为绘图尺寸【Andriod】 选择360640作为绘图尺寸【Web】使用1440宽作为绘图尺寸3、具体实现主要思路设计稿中统一使用dp作为像素单位,具体规则参考上面的切图规则和画布设计规则rem和vw多多少少存在各种问题,所以统一使用px作为实现单位web和wap可以使用同一套工程方案实现设计稿的dp到真实应用中px的映射关系,并且px会随着设备窗口大小的改变而改变当然,如果稿子是px的也可以手动将px转换为dp。想要实现这个整体方案,核心就在于第4条(实现设计稿的dp到真实应用中px的映射关系),并且这个过程只靠工程化的编译阶段是无法完美解决的,必须和浏览器运行时一起配合工作才能够达成我们的目标。前提业务模块的css不可以抽离为独立的css文件,必须输出在js文件中(style-loader的能力),这样才有改变css内容的基本能力。定义一个尺寸单位dp,标识这是在设计稿上的尺寸(类似于小程序中的rpx)。并不是所有的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元素在不同设备下的宽和高:设备宽度高度设计稿100dp150dpiPhone 5/SE85.33px128pxiPhone 6/7/8100px150pxiPhone 6/7/8 Plus110.4px165.60pxiPhone X100px150pxGalaxy S596px144px在实现这个功能必须先提供一个转换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.1、方式一:styled-components + css-in-js + JSXVue: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.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.3、优缺点优点:文章开头所提到的rem和vw都存在众多问题,该方案都可以完美解决。还可以和其他任何单位混合使用,这意味这使用这种方案的同时还可以使用之前的rem和vw方式。缺点:不管使用方式一还是方式二,都会对项目的工程化复杂度增加,不过和目前处理rem以及vm的px2rem以及px2viewport等工具相比,这点复杂度根本不值一提。增加了js bundle文件的体积,减少了css文件的体积。不过没有整体的体积没有增加,仅仅是将部分业务的css内容搬到了js文件里。最后借用了大部分工程化的能力,主要还是用来css in js的能力但是这样有个缺点。就是视窗大小改变的时候会出现布局错乱。不过影响不大,因为真机里是不会出现窗口大小改版的。而且这一套方案完全抹平了web和wap的差异性,在开发层面完全一直。需要缩放就用dp,不需要就用np和px作为单位即可这套方案还是蛮不错的,比rem和vw好很多。确实属于第三代移动端布局方案了 ...

April 12, 2019 · 2 min · jiezi

移动端所有浏览器页面调试方法

通常在移动端页面调试时,除了移动端chrome和safiri,几乎对其他浏览器无法调试。在测试过程中,通常解决兼容性占了大部分时间,对那些手机厂商自带浏览器和第三方浏览器深恶痛绝,为什么不使用统一标准。spy-debugger 移动端浏览器调试工具页面调试、抓包工具。远程调试任何手机浏览器页面,任何手机移动端webview(如:微信,HybridApp等)。支持HTTP/HTTPS,无需USB连接设备。1.安装需要安装node,在这不做介绍了,然后安装 spy-debbugger 依赖包到全局环境。(这里使用了npm,yarn也不错)npm install spy-debugger -g2.PC端运行打开终端运行spy-debugger当出现下图所示即表示在PC端运行成功,记住IP和node-mitmproxy启动端口,后面需要使用,上图这个样子就成功启动了,浏览器打开 http://127.0.0.1:65443,后续调试的的功能都在这个页面上操作。3. 设置手机端HTTP代理首先电脑和手机保持在一个网络下,或连接同一个wifi。然后到手机端设置所连wifi的代理。进入wlan列表,长按或者点击最右的箭头(IOS是i符号),进入当前wifi详情页手机滑到最下面有个代理,点开选择为手动然后连接电脑端的代理,就下图中的 将第2步中的IP和端口分别填入主机名和端口4.手机端下载并安装证书(首次使用安装)手机端第三方浏览器输入 http://s.xxx (不要用自带浏览器和微信浏览器),下载证书到本地。安卓端安装证书的两种方式:其一:设置——WiFi——高级设置——安装证书其二:设置——更多设置——系统安全——从存储设备安装证书IOS端安装证书的方式看下面链接就行:https://www.jianshu.com/p/d31…5.用手机浏览器访问你要调试的页面即可下图就是用手机端浏览器打开页面,targets会显示监听到打开的页面。菜单栏的 Elements 、Resources、Network、Timeline、Console和chrome的开发工具项类似。体验肯定没PC端浏览器的好,但能够满足大部分调试的需求。

February 1, 2019 · 1 min · jiezi