(淘宝无限适配)手机端rem布局详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(淘宝无限适配)手机端rem布局详解相关的知识,希望对你有一定的参考价值。
一、 首先我们先来看看淘宝不同分辨率下的适配页面
可以看出来,淘宝在不同的分辨率下,页面的尺寸和模块间的间距会发生变化,这是因为淘宝采用了rem,这篇文章会简单介绍淘宝的布局思路以及具体做法,不过在此之前我们先了解一些移动端的知识,以为更好的理解淘宝布局的方案,下面我们来看一些移动端的知识
二、了解一些移动端的知识
viewport的<meta>标签用法
其主要用来告诉浏览器如何规范的渲染Web页面,而你则需要告诉它视窗有多大
移动端开发中,通常我们都会采用<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">来设置viewport,那么viewport是什么样的原理控制页面适配呢?
所以在讲淘宝的具体做法之前,我们先来深入了解viewport
viewport是什么?
通俗来讲,移动端的viewport就是我们所能看到的手机端浏览器的可视窗口大小,但viewport又不仅仅局限于可视窗口的大小,一般情况下,它是比默认窗口大小要大的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动端正常显示为桌面浏览器而设计的网页,移动端的浏览器都会默认把自己的默认的viewport设为980px到1024px不等,但其后果就是会出现横向滚动条,因为移动端浏览器可视区域的大小是比默认的viewport宽度要小的。
不同的设备对1px有不一样的定义
在css中我们一般使用px作为单位,在桌面浏览器中css的1个像素往往都是对应着电脑屏幕的1个物理像素,这可能会造成我们的一个错觉,那就是css中的像素就是设备的物理像素。但实际情况却并非如此,css中的像素只是一个抽象的单位,在不同的设备或不同的环境中,css中的1px所代表的设备物理像素是不同的。在为桌面浏览器设计的网页中,我们无需对这个津津计较,但在移动设备上,必须弄明白这点。在早先的移动设备中,屏幕像素密度都比较低,如iphone3,它的分辨率为320x480,在iphone3上,一个css像素确实是等于一个屏幕物理像素的。后来随着技术的发展,移动设备的屏幕像素密度越来越高,从iphone4开始,苹果公司便推出了所谓的Retina屏,分辨率提高了一倍,变成640x960,但屏幕尺寸却没变化,这就意味着同样大小的屏幕上,像素却多了一倍,这时,一个css像素是等于两个物理像素的。
在移动端浏览器中以及某些桌面浏览器中,window对象有一个devicePixelRatio属性,它的官方的定义为:设备物理像素和设备独立像素的比例,也就是 devicePixelRatio = 物理像素 / 独立像素。css中的px就可以看做是设备的独立像素,所以通过devicePixelRatio,我们可以知道该设备上一个css像素代表多少个物理像素。例如,在Retina屏的iphone上,devicePixelRatio的值为2,也就是说1个css像素相当于2个物理像素
以iphone6为例:
设备宽高为375×667,可以理解为设备独立像素(或css像素)。
dpr为2,根据上面的计算公式,其物理像素就应该×2,为750×1334。
用一张图来表现,就是这样:
关于三个viewport的理论
ppk大神对于移动设备上的viewport有着非常多的研究(第一篇,第二篇,第三篇),有兴趣的同学可以去看一下
ppk认为,移动设备上有三个viewport。包括layout viewport、visual viewprt、ideal viewport
layout viewport: 首先,移动设备上的浏览器认为自己必须能让所有的网站都正常显示,即使是那些不是为移动设备设计的网站。但如果以浏览器的可视区域作为viewport的话,因为移动设备的屏幕都不是很宽,所以那些为桌面浏览器设计的网站放到移动设备上显示时,必然会因为移动设备的viewport太窄,而挤作一团,甚至布局什么的都会乱掉。也许有人会问,现在不是有很多手机分辨率都非常大吗,比如768x1024,或者1080x1920这样,那这样的手机用来显示为桌面浏览器设计的网站是没问题的吧?前面我们已经说了,css中的1px并不是代表屏幕上的1px,你分辨率越大,css中1px代表的物理像素就会越多,devicePixelRatio的值也越大,这很好理解,因为你分辨率增大了,但屏幕尺寸并没有变大多少,必须让css中的1px代表更多的物理像素,才能让1px的东西在屏幕上的大小与那些低分辨率的设备差不多,不然就会因为太小而看不清。所以在1080x1920这样的设备上,在默认情况下,也许你只要把一个div的宽度设为300多px(视devicePixelRatio的值而定),就是满屏的宽度了。回到正题上来,如果把移动设备上浏览器的可视区域设为viewport的话,某些网站就会因为viewport太窄而显示错乱,所以这些浏览器就决定默认情况下把viewport设为一个较宽的值,比如980px,这样的话即使是那些为桌面设计的网站也能在移动浏览器上正常显示了。ppk把这个浏览器默认的viewport叫做 layout viewport。这个layout viewport的宽度可以通过document.documentElement.clientWidth 来获取。
然而,layout viewport 的宽度是大于浏览器可视区域的宽度的,所以我们还需要一个viewport来代表 浏览器可视区域的大小,ppk把这个viewport叫做 visual viewport。visual viewport的宽度可以通过window.innerWidth 来获取
如下所示便是layout viewport 和visual viewport的区别,简单明了吧
现在我们已经有两个viewport了:layout viewport 和 visual viewport。但浏览器觉得还不够,因为现在越来越多的网站都会为移动设备进行单独的设计,所以必须还要有一个能完美适配移动设备的viewport。所谓的完美适配指的是,首先不需要用户缩放和横向滚动条就能正常的查看网站的所有内容;第二,显示的文字的大小是合适,比如一段14px大小的文字,不会因为在一个高密度像素的屏幕里显示得太小而无法看清,理想的情况是这段14px的文字无论是在何种密度屏幕,何种分辨率下,显示出来的大小都是差不多的。当然,不只是文字,其他元素像图片什么的也是这个道理。ppk把这个viewport叫做 ideal viewport,也就是第三个viewport——移动设备的理想viewport。
ideal viewport并没有一个固定的尺寸,不同的设备拥有有不同的ideal viewport。所有的iphone的ideal viewport宽度都是320px,无论它的屏幕宽度是320还是640,也就是说,在iphone中,css中的320px就代表iphone屏幕的宽度。
但是安卓设备就比较复杂了,有320px的,有360px的,有384px的等等,关于不同的设备ideal viewport的宽度都为多少,可以到http://viewportsizes.com去查看一下,里面收集了众多设备的理想宽度。
再总结一下:ppk把移动设备上的viewport分为layout viewport、visual viewport和ideal viewport三类,其中的ideal viewport是最适合移动设备的viewport,ideal viewport的宽度等于移动设备的屏幕宽度,只要在css中把某一元素的宽度设为ideal viewport的宽度(单位用px),那么这个元素的宽度就是设备屏幕的宽度了,也就是宽度为100%的效果。ideal viewport 的意义在于,无论在何种分辨率的屏幕下,那些针对ideal viewport 而设计的网站,不需要用户手动缩放,也不需要出现横向滚动条,都可以完美的呈现给用户。
利用meta标签对viewport进行控制
移动设备默认的viewport是layout viewport,也就是那个比屏幕要宽的viewport,但在进行移动设备网站的开发时,我们需要的是ideal viewport。那么怎么才能得到ideal viewport呢?这就该轮到meta标签出场了。
在文章一开始我们就说,进行移动端开发时,我们一般都会加上这么一句:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,user-scalable=no">
该meta标签的作用是让当前viewport的宽度等于设备的宽度,同时不允许用户手动缩放。也许允不允许用户缩放不同的网站有不同的要求,但让viewport的宽度等于设备的宽度,这个应该是大家都想要的效果,如果你不这样的设定的话,那就会使用那个比屏幕宽的默认viewport,也就是说会出现横向滚动条。
这个name为viewport的meta标签到底有哪些东西呢,又都有什么作用呢?
meta viewport 标签首先是由苹果公司在其safari浏览器中引入的,目的就是解决移动设备的viewport问题。后来安卓以及各大浏览器厂商也都纷纷效仿,引入对meta viewport的支持,事实也证明这个东西还是非常有用的。
在苹果的规范中,meta viewport 有6个属性(暂且把content中的那些东西称为一个个属性和值),如下:
width | 设置layout viewport 的宽度,为一个正整数,或字符串"device-width" |
initial-scale | 设置页面的初始缩放值,为一个数字,可以带小数 |
minimum-scale | 允许用户的最小缩放值,为一个数字,可以带小数 |
maximum-scale | 允许用户的最大缩放值,为一个数字,可以带小数 |
height | 设置layout viewport 的高度,这个属性对我们并不重要,很少使用 |
user-scalable | 是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes代表允许 |
把当前的viewport宽度设置为 ideal viewport 的宽度
要得到ideal viewport就必须把默认的layout viewport的宽度设为移动设备的屏幕宽度。因为meta viewport中的width能控制layout viewport的宽度,所以我们只需要把width设为width-device这个特殊的值就行了
<meta name="viewport" content="width=device-width">
通过width=device-width,所有浏览器都能把当前的viewport宽度变成ideal viewport的宽度
这样的代码看起来确实简单明了,不管你懂不懂viewport,都可以用这句进行移动端开发,可是很少有人知道
<meta name="viewport" content="initial-scale=1">
这句代码能达到和
<meta name="viewport" content="width=device-width">
一模一样的效果,也可以把当前的的viewport变为 ideal viewpor。觉得不太信的,你可以试试,不过这说明了什么呢?
从理论上来讲,这句代码的作用只是不对当前的页面进行缩放,也就是页面本该是多大就是多大。那为什么会有 width=device-width 的效果呢?
要想清楚这件事情,首先你得弄明白这个缩放是相对于什么来缩放的,因为这里的缩放值是1,也就是没缩放,但却达到了 ideal viewport 的效果,所以,那答案就只有一个了,缩放是相对于 ideal viewport来进行缩放的,当对ideal viewport进行100%的缩放,也就是缩放值为1的时候,不就得到了 ideal viewport嘛
但如果width 和 initial-scale=1同时出现,并且还出现了冲突呢?比如:
<meta name="viewport" content="width=400, initial-scale=1">
width=400表示把当前viewport的宽度设为400px,initial-scale=1则表示把当前viewport的宽度设为ideal viewport的宽度,那么浏览器到底该服从哪个命令呢?是书写顺序在后面的那个吗?不是。当遇到这种情况时,浏览器会取它们两个中较大的那个值。例如,当width=400,ideal viewport的宽度为320时,取的是400;当width=400, ideal viewport的宽度为480时,取的是ideal viewport的宽度。
最后,总结一下,要把当前的viewport宽度设为ideal viewport的宽度,既可以设置 width=device-width,也可以设置 initial-scale=1,但这两者各有一个小缺陷,就是iphone、ipad以及IE 会横竖屏不分,通通以竖屏的ideal viewport宽度为准。所以,最完美的写法应该是,两者都写上去,这样就 initial-scale=1 解决了 iphone、ipad的毛病,width=device-width则解决了IE的毛病:
<meta name="viewport" content="width=device-width, initial-scale=1">
三、淘宝的布局方案解析
有了上边的viewport的知识了解后,我们来剖析淘宝的viewport。淘宝触屏版布局的前提就是viewport的scale根据devicePixelRatio动态设置:
Galaxy S5:
iphone 5:
iphone 6 plus:
上图都是明晃晃的证据啊。我截取了dpr分别为1,2,3时的缩放比,不一样吧
这么做目的当然是为了保证页面的大小与设计稿保持一致了,比如设计稿如果是750的横向分辨率,那么实际页面的device-width,以iphone6来说,也等于750,这样的话设计稿上标注的尺寸只要除以某一个值就能够转换为rem了。
通过js设置viewport的方法如下:
-
12var scale = 1 / devicePixelRatio;document.querySelector(‘meta[name="viewport"]‘).setAttribute(‘content‘,‘initial-scale=‘ + scale + ‘,maximum-scale=‘ + scale + ‘, minimum-scale=‘ + scale + ‘, user-scalable=no‘);
淘宝布局的第二个要点,就是html元素的font-size的计算公式,font-size = deviceWidth / 10:
-
1234567//flexible中定义<html>font-sizevar width = docEl.getBoundingClientRect().width;if (width / dpr > 540) {width = 540 * dpr;}var rem = width / 10;docEl.style.fontSize = rem + ‘px‘;
注:整个网页在设备内显示时的页面宽度就会等于设备逻辑像素大小,也就是device-width。这个device-width的计算公式为:设备的物理分辨率/(devicePixelRatio * scale),在scale为1的情况下,device-width = 设备的物理分辨率/devicePixelRatio ,物理像素=逻辑像素* devicePixelRatio*scale
接下来要解决的问题是,元素的尺寸该如何计算,比如说设计稿上某一个元素的宽为150px,换算成rem应该怎么算呢?这个值等于设计稿标注尺寸/该设计稿对应的html的font-size。拿淘宝来说的,他们用的设计稿是750的,所以html的font-size就是75,如果某个元素是150px的宽,换算成rem就是150 / 75 = 2rem。
总结下淘宝的这些做法:
(1)动态设置viewport的scale
-
12var scale = 1 / devicePixelRatio;document.querySelector(‘meta[name="viewport"]‘).setAttribute(‘content‘,‘initial-scale=‘ + scale + ‘, maximum-scale=‘ + scale + ‘, minimum-scale=‘ + scale + ‘, user-scalable=no‘);
(2)动态设置html的font-size
(3)布局的时候,各元素的css尺寸=设计稿标注尺寸/设计稿横向分辨率/10
(4)font-size可能需要额外的媒介查询,并且font-size不使用rem
这些都是淘宝的布局方案,只是讲了一些布局思路,接下来我们具体了解flexible库以及看看它是怎么实现这些思路的
四. 淘宝移动端自适应方案—lib.flexible库解析
lib.flexible库是淘宝用来解决移动端页面终端适配的
这是github上flexible库的源码,需要就去clone吧 https://github.com/amfe/lib-flexible
它的主要js文件有三个,包括flexiblecss.js、flexible.js、makegrid.js
flexible.js—布局的核心js
flexiblecss.js—注入统一的css样式,比如去掉所有元素的内外边距,去掉默认边框等等
makegrid.js—栅格系统
怎么用lib.flexible库? 引入flexible_css.js,flexible.js文件
<script src="build/flexible_css.debug.js"></script>
<script src="build/flexible.debug.js"></script>
更简单方便的,你可以直接使用阿里CDN:
<script src="http://g.tbcdn.cn/mtb/lib-flexible/{{version}}/??flexible_css.js,flexible.js"></script>
PS:{{version}}换成相应的版本号,如{{0.3.2}}
建议对于js做内联处理,在所有资源加载之前执行这个js。执行这个js后,会在(也就是document.documentElement)上增加一个data-dpr属性和font-size样式。之后页面中的元素,都可以用rem单位来设置。html上的font-size就是rem的基准像素。JS会根据不同的设备添加不同的data-dpr值,比如说2或者3,同时会给html加上对应的font-size的值,比如说75px。如此一来,页面中的元素,都可以通过rem单位来设置。他们会根据html元素的font-size值做相应的计算,从而实现屏幕的适配效果。 除此之外,在引入lib-flexible需要执行的JS之前,可以手动设置meta来控制dpr值,如:
<meta name="flexible" content="initial-dpr=2" />
其中initial-dpr会把dpr强制设置为给定的值。如果手动设置了dpr之后,不管设备是多少的dpr,都会强制认为其dpr是你设置的值。在此不建议手动强制设置dpr,因为在Flexible中,只对ios设备进行dpr的判断,对于android系列,始终认为其dpr为1。
-
12345678910111213141516171819if (!dpr && !scale) {var isAndroid = win.navigator.appVersion.match(/android/gi);var isIPhone = win.navigator.appVersion.match(/iphone/gi);var devicePixelRatio = win.devicePixelRatio;if (isIPhone) {// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {dpr = 3;} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){dpr = 2;} else {dpr = 1;}} else {// 其他设备下,仍旧使用1倍的方案dpr = 1;}scale = 1 / dpr;}
flexible的实质
flexible实际上就是能过JS来动态改写meta标签,代码类似这样:
-
1234567891011var metaEl = doc.createElement(‘meta‘);var scale = isRetina ? 0.5:1;metaEl.setAttribute(‘name‘, ‘viewport‘);metaEl.setAttribute(‘content‘, ‘initial-scale=‘ + scale + ‘, maximum-scale=‘ + scale + ‘, minimum-scale=‘ + scale + ‘, user-scalable=no‘);if (docEl.firstElementChild) {document.documentElement.firstElementChild.appendChild(metaEl);} else {var wrap = doc.createElement(‘div‘);wrap.appendChild(metaEl);documen.write(wrap.innerHTML);}
事实上他做了这几样事情:
•动态改写标签
•给元素添加data-dpr属性,并且动态改写data-dpr的值
•给元素添加font-size属性,并且动态改写font-size的值
把视觉稿中的px转换成rem
目前Flexible会将视觉稿分成100份,而每一份被称为一个单位a。同时1rem单位被认定为10a。针对我们这份视觉稿可以计算出:
1a = 7.5px
1rem = 75px
那么我们的视觉稿就分成了10a,也就是整个宽度为10rem,对应的font-size为75px;
这样一来,对于视觉稿上的元素尺寸换算,只需要原始的px值除以rem基准值即可。例如一个div尺寸为220px*100px,则其转化为rem的尺寸为2.9333rem * 1.33333rem。
推荐CSSREM——px转化rem小工具
在实际开发中,如果每一次计算px转换rem,或许会觉得非常麻烦,或许直接影响大家平时的开发效率。这里有一个px转换rem的小工具——CSSREM;
CSSREM是一个CSS的px值转rem值的Sublime Text3自动完成插件。我已经安装使用体验,只需要输入正常的px值,会自动换算成相应的rem值;如下图所示:
查看CSSREM如何安装,请参考:https://github.com/flashlizi/cssrem
文本字号应该用rem吗?
显然,我们在iPhone3G和iPhone4的Retina屏下面,希望看到的文本字号是相同的。也就是说,我们不希望文本在Retina屏幕下变小,另外,我们希望在大屏手机上看到更多文本,以及,现在绝大多数的字体文件都自带一些点阵尺寸,通常是16px和24px,所以我们不希望出现13px和15px这样的奇葩尺寸。
如此一来, rem并不适合用到段落文本上。所以在Flexible整个适配方案中,考虑文本还是使用px作为单位。只不过使用[data-dpr]属性来区分不同dpr下的文本字号大小。
-
1234567891011div {width: 1rem;height: 0.4rem;font-size: 12px; // 默认写上dpr为1的fontSize}[data-dpr="2"] div {font-size: 24px;}[data-dpr="3"] div {font-size: 36px;}
总结下吧,字体慎用rem,误差太大了,因为不能满足任何屏幕下字体大小相同,所以建议标题类用rem,要求字体大小相同的部分还是用px
栅格系统—makegrid.js
栅格系统用于实现移动端列表型的需求,比如淘宝首页的分类模块:
首先引入makegrid.js
<script src="build/makegrid.js"></script>
使用方法
lib.flexible.makeGrid(params)
• [Object params]
-designWidth - 设计稿宽度
- designUnit - 设计稿最小单位a(以px为单位)
-columnCount - 栅格列数
-columnXUnit - 栅格列宽(以a为单位)
-gutterXUnit - 栅格间距(以a为单位)
-edgeXUnit - 页面左右边距(以a为单位)
-className - 栅格样式的名称(可省略,默认为grid)
通过传入视觉的栅格规范定义,可以输出对应的css样式。 lib.flexible.makeGridMode(modeName) 方案还预置了几个默认的栅格规范,分别是750-12,750-6,640-12,640-6。
-
12345678910var gridMode = {‘750-12‘:{ designWidth:750,designUnit:6,columnCount:12,columnXUnit:7,gutterXUnit:3,edgeXUnit:4},‘750-6‘: { designWidth:750,designUnit:6,columnCount:6,columnXUnit:17,gutterXUnit:3,edgeXUnit:4},‘640-12‘: { designWidth:640,designUnit:4,columnCount:12,columnXUnit:11,gutterXUnit:2,edgeXUnit:3},&nb
以上是关于(淘宝无限适配)手机端rem布局详解的主要内容,如果未能解决你的问题,请参考以下文章