移动互联网快速发展的今天,手机的种类和尺寸愈来愈多,做为前端的小伙伴们可能会愈来愈头疼,但又不得不去适配一款又一款的新机型。对于移动端适配,不一样的公司、不一样的团队有不一样的解决方案。我在项目中也用了一部分解决方案,也看到了一些解决方案,对比下,总结一些本身的理解,但愿对各位有帮助,找到最适合大家项目的适配方案。javascript
屏幕的物理像素,又被称为设备像素,他是显示设备中一个最微小的物理部件。任何设备屏幕的物理像素出厂时就肯定了,且固定不变的。css
设备独立像素也称为密度无关像素,能够认为是计算机坐标系统中的一个点,这个点表明一个能够由程序使用的虚拟像素(好比说CSS像素),而后由相关系统转换为物理像素。html
设备像素比简称为dpr,其定义了物理像素和设备独立像素的对应关系前端
设备像素比 = 物理像素 / 设备独立像素 以iphone6为例: iphone6的设备宽和高为375pt * 667pt,能够理解为设备的独立像素,而其设备像素比为2.固有设备像素为750pt * 1334pt
经过:window.devicePixelRatio得到。html5
设备像素比是区别是不是高清屏的标准,dpr大于1时就为高清屏,通常状况下dpr为整数,可是android有些奇葩机型不为整数。java
在CSS、JS中使用的一个长度单位。单位pxandroid
注:在pc端1物理像素等于1px,可是移动端1物理像素不必定等于1px,1物理像素与px的关系与如下因素有关。(有些视口概念,能够把下面视口看完了再来看)ios
一、屏幕布局视口大小(下面会讲到) 二、屏幕的分辨率(物理像素)
对于一块屏幕,其物理像素是肯定的。视觉视口尺寸是继承的布局视口的,而视觉视口里宽度便是css的px数。故在一块屏上物理像素与px的关系就是物理像素与布局视口的px数的关系。web
好比iphone6,期物理像素为750,若是没有设置布局视口时,viewport为980px
此时:1物理像素长度等于980/750px = 1.3067px的长度
因为像素都是点阵的,故1物理像素至关于1.3067px * 1.3067px方格。
当在meta中设置了以下配置时
<meta name="viewport" content="width=device-width"> 至关于把布局视口设置为设备的宽度(即上面讲到的设备独立像素), 对于iphone6就是375px。 此时1物理像素长度等于375/750px = 0.5px的长度,故1物理像素至关于0.5px * 0.5px的方格。
在html中通常在meta中的name为viewport字段就是控制的布局视口。布局视口通常都是浏览器厂商给的一个值。在手机互联网没有普及前,网络上绝大部分页面都是为电脑端浏览而作的,根本没有作移动端的适配。随着移动端的发展,在手机上看电脑端的页面已成为很是普及现象。而电脑端页面宽度较大,移动端宽度有限,要想看到整个网页,会有很长的滚动条,看起来很是麻烦。因而浏览器厂商为了让用户在小屏幕下网页也可以显示地很好,因此把布局视口设置的很大,通常在768px ~ 1024px 之间,最经常使用的宽度就是 980。这样用户就能看到绝大部份内容,并根据具体内容选择缩放。浏览器
故布局视口是看不见的,浏览器厂商设置的一个固定值,如980px,并将980px的内容缩放到手机屏内。
布局视口能够经过:
document.documentElement.clientWidth(clientHeight) // 布局视口的尺寸。
浏览器可视区域的大小,即用户看到的网页的区域。(其宽度继承的布局视口宽度)
window.innerWidth(innerHeight) // 视觉视口尺寸
布局视口虽然解决了移动端查看pc端网页的问题,可是彻底忽略了手机自己的尺寸。因此苹果引入了理想视口,它对设备来讲是最理想的布局视口,用户不须要对页面进行缩放就能完美的显示整个页面。最简单的作法就是使布局视口宽度改为屏幕的宽度。
能够经过window.screen.width获取。
<meta name="viewport" content="width=device-width">
移动端到底怎么适配不一样的屏幕呢?最简单的方法是设置以下视口:
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
当使用以上方案定义布局视口时,即布局视口等于理想视口(屏幕宽度),屏幕没有滚动条,不存在高清屏下,字体较小的问题。可是在不一样屏幕上,其视觉宽度是不一样的,不能简单的将全部的尺寸都设置为px,可能会出现滚动条。小尺寸的能够用px,大尺寸的只能用百分比和弹性布局。
对于上面的设置,再不一样的屏幕上,css像素对应的物理像素具数是不一致的。
在普通屏幕下,dpr=1时,
1个css像素长度对应1个物理像素长度,1个css像素对应1个物理像素。
而在Retina屏幕下,若是dpr=2,
1个css像素长度对应2个物理像素长度,1css像素对应4个物理像素。
此时若是css中写
border: 1px solid red; // 此时1px 对应的宽度是2物理像素的宽度。
而通常如今移动端设计稿都是基于iphone设计的,稿子通常为750px或640px,这正好是iphone6和iphone5的物理像素。在设计稿中,通常有些边框效果,这时边框的线宽为1px,对应的就是1物理像素。而对于iphone5和iphone6,当width=device-width时,css的1px显示出来的是2个物理像素,因此看起来线就比较粗。怎么解决呢?1px边框效果其实有不少hack方法,其中一种就是经过缩放viewport。
initial-scale是将布局视口进行缩放,initial-scale是相对于理想视口的,即initial-scale=1与width=device-width是同样的效果。initial-scale=0.5等效于width= 2倍的device-width,因此设置initial-scale和width均可以改变布局视口的大小。
<meta name="viewport" content="width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
对于iphone6当添加如上设置后,initial-scale=0.5,即将页面缩小2倍后等于屏幕宽度。
布局视口width: width / 2 = 375px; width = 750px;
因此此时布局视口为750px,此时1px等于1物理像素。
上面讲了一些基础概念,下面讲具体适配。
对于ui设计师给的一张设计稿,怎么将其还原到页面上?对于不一样手机屏幕,其dpr不一样,屏幕尺寸也不一样,考虑到各类状况,有不少适配方案,因此不一样的适配方案,实现方法不一样,处理复杂度也不一样,还原程度也不一样。
方案一:
固定高度,宽度自适应。
这种方案是目前使用较多的方案,也是相对较简单的实现方案:
该方法使用了理想视口:
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
垂直方向使用固定的值,水平方向使用弹性布局,元素采用定值、百分比、flex布局等。这种方案相对简单,还原度也很是低。
方案二:
固定布局视口宽度,使用viewport进行缩放
荔枝的代码:
if(/Android (\d+\.\d+)/.test(navigator.userAgent)){ var version = parseFloat(RegExp.$1); if(version>2.3){ var phoneScale = parseInt(window.screen.width)/640; if(/MZ-M571C/.test(navigator.userAgent)){ document.write('<meta name="viewport" content="width=640, minimum-scale = 0.5, maximum-scale= 0.5">'); }else if(/M571C/.test(navigator.userAgent)&&/LizhiFM/.test(navigator.userAgent)){ document.write('<meta name="viewport" content="width=640, minimum-scale = 0.5, maximum-scale= 0.5">'); }else{ document.write('<meta name="viewport" content="width=640, minimum-scale = '+ phoneScale +', maximum-scale = '+ phoneScale +', target-densitydpi=device-dpi">'); } }else{ document.write('<meta name="viewport" content="width=640, target-densitydpi=device-dpi">'); } }else{ document.write('<meta name="viewport" content="width=640, user-scalable=no, target-densitydpi=device-dpi">'); }
网易应用:
var win = window,
width = 640, iw = win.innerWidth || width, ow = win.outerHeight || iw, sw = win.screen.width || iw, saw = win.screen.availWidth || iw, ih = win.innerHeight || width, oh = win.outerHeight || ih, ish = win.screen.height || ih, sah = win.screen.availHeight || ih, w = Math.min(iw, ow, sw, saw, ih, oh, ish, sah), ratio = w / width, dpr = win.devicePixelRatio; if (ratio = Math.min(ratio, dpr), 1 > ratio) { var ctt = ",initial-scale=" + ratio + ",maximum-scale=" + ratio, metas = document.getElementsByTagName("meta");ctt += ""; for (var i = 0, meta; i < metas.length; i++) meta = metas[i], "viewport" == meta.name && (meta.content += ctt) }
固定布局视口,宽度设置固定的值,总宽度为640px,根据屏幕宽度动态生成viewport。(设计稿应该是640px的)
<meta name="viewport" content="width=640, minimum-scale = 0.5625, maximum-scale = 0.5625, target-densitydpi=device-dpi">
这种方式布局如荔枝FM的网页宽度始终为640px。缩放比例scale为:
var scale = window.screen.width / 640
设计稿为640px时,正好能够1:1以px来写样式。可是1px所对应的物理像素就不必定是1了。
(window.screen.width * dpr) / 640 // 1px对应的物理像素
方案三:
根据不一样屏幕动态写入font-size,以rem做为宽度单位,固定布局视口。
如网易新闻:
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
以640px设计稿和750px的视觉稿,网易这样处理的:
var width = document.documentElement.clientWidth; // 屏幕的布局视口宽度 var rem = width / 7.5; // 750px设计稿将布局视口分为7.5份 var rem = width / 6.4; // 640px设计稿将布局视口分为6.4份
这样不论是750px设计稿仍是640px设计稿,1rem 等于设计稿上的100px。故px转换rem时:
rem = px * 0.01;
在750px设计稿上:
75px 对应 0.75rem, 距离占设计稿的10%; 在ipone6上: width = document.documentElement.clientWidth = 375px; rem = 375px / 7.5 = 50px; 0.75rem = 37.5px; (37.5/375=10%;占屏幕10%) 在ipone5上: width = document.documentElement.clientWidth = 320px; rem = 320px / 7.5 = 42.667px; 0.75rem = 32px; (32/320=10%;占屏幕10%)
故对于设计稿上任何一个尺寸换成rem后,在任何屏下对应的尺寸占屏幕宽度的百分比相同。故这种布局能够百分比还原设计图。
方案四:
以rem做为宽度单位,动态写入viewport和font-size进行缩放。
根据设置的dpr设置font-size。如:
document.documentElement.style.fontSize = 50 * dpr; // dpr 为设置的设备像素比。(注意不是设备自身的设备像素比,而是认为设置的dpr)
这种状况下,dpr = 1时,1rem = 50px;
dpr = 2时, 1rem = 100px;
当设计以iphone6为标准,出750px的设计稿时,此时dpr=2,故1rem 等于100px,将图上的尺寸转换为rem很是方便,除以100就行。代码以下:
var scale = 1.0; var dpr = 1; var isAndroid = window.navigator.appVersion.match(/android/gi); var isIPhone = window.navigator.appVersion.match(/iphone/gi); var devicePixelRatio = window.devicePixelRatio; // 此处只简单对ios作了伸缩处理,安卓没有作伸缩处理,统一dpr = 1 if ( isIPhone ) { scale /= devicePixelRatio; dpr *= devicePixelRatio; } var viewport = document.getElementById('viewport'); var content = 'initial-scale=' + scale + ', maximum-scale=' + scale + ',minimum-scale=' + scale + ', width=device-width, user-scalable=no'; viewport.setAttribute( 'content', content ); document.documentElement.style.fontSize = 50 * dpr + 'px'; document.documentElement.setAttribute('data-dpr', dpr);
对于该方案,
假设肉眼看到的宽度(视觉宽度):visualWidth,令dpr=1时,其1rem对应的宽度为50. dpr = 1 时, 1rem = 50px, initial-scale=1, 缩放为1。 visualWidth = 50 * 1 = 50; dpr = 2 时, 1rem = 100px, initial-scale=0.5, 缩放为0.5。 visualWidth = 100 * 0.5 = 50; dpr = 3 时, 1rem = 150px, initial-scale=0.3333, 缩放为0.3333。 visualWidth = 150 * 0.3333 = 50;
因此该方案,1rem在全部屏幕上对应的肉眼距离相同,故不一样屏幕下,总的rem数不一样,大屏下总的rem数大于小屏下,如iphone6下,总宽度为7.5rem,iphone5下,总宽度为6.4rem。故此方案不能百分比还原设计稿,故写样式时,对于大块元素应该用百分比,flex等布局,不能直接用rem。
关于这个方案的详细教程请参考这篇文章传送门
方案五:
根据不一样屏幕动态写入font-size和viewport,以rem做为宽度单位
将屏幕分为固定的块数10:
var width = document.documentElement.clientWidth; // 屏幕的布局视口宽度 var rem = width / 10; // 将布局视口分为10份
这样在任何屏幕下,总长度都为10rem。1rem对应的值也不固定,与屏幕的布局视口宽度有关。
对于动态生成viewport,他们原理差很少,根据dpr来设置缩放。看看淘宝的:
var devicePixelRatio = window.devicePixelRatio; var isIPhone = window.navigator.appVersion.match(/iphone/gi); var dpr,scale; if (isIPhone) { if (devicePixelRatio >=3) { dpr = 3; } else if (devicePixelRatio >=2) { dpr = 2; } else { dpr = 1; } } else { dpr = 1; } scale = 1 / dpr;
淘宝只对iphone作了缩放处理,对于android全部dpr=1,scale=1即没有缩放处理。
此方案与方案三类似,只是作了viewport缩放,能百分比还原设计稿。
适配中要解决的问题 :
移动端适配最主要的是使在不一样屏幕下不用缩放页面就能正常显示整个页面。以上方案都完成了这一需求。其次有几个需求:
一、解决高清屏下1px的问题,其实有不少hack方法,这里只讲了缩放视口。先将布局视口设置为高清屏的物理像素。这样css中1px就是1个物理像素,这样看到的线条才是真正的1px。可是此时视口宽度大于设备的宽度,就会出现滚动条。故对视口进行缩放,使视口宽度缩放到设备宽度。
淘宝团队在处理安卓端的缩放存在不少问题,因此dpr都作1处理,因此安卓端就没有解决1px的问题。
二、在大屏手机中一行看到的段落文字应该比小屏手机的多。
因为淘宝和网易新闻rem都是百分比,故若是用rem一行显示的文字个数应该是相同的。故对于段落文本不能用rem做为单位,应该用px处理,对于不一样的dpr下设置不一样的字体。
.selector { color: red; font-size: 14px; } [data-dpr="2"] .selector { font-size: 28px; // 14 * 2 } [data-dpr="3"] .selector { font-size: 42px; // 14 * 3 }
对于方案四,无论什么状况下,1rem对应的视觉上的宽度都是同样的,而对应的大屏、小屏手机其视觉宽度固然不一样,故字体设置为rem单位时,也能知足大屏手机一行显示的字体较多这个需求。
五种方案对比:
上面四种方案对设计稿还原程度是有差异的。
除了方案一和方案四之外,其余方案都是百分比还原设计稿,大屏下元素的尺寸就大。
方案一还原设计稿程度较低,这里不作说明。
方案二作了百分比适配,部分1px适配,没有字体适配。
方案三作了百分比适配,没有1px适配,有字体大小适配。
方案四没有作百分百适配,布局要用百分百和flex布局,作了1px的适配,而且对于段落文字直接能够用rem作单位,不须要作适配。
方案五作了百分比适配,有1px适配,有字体大小适配。
项目中遇到的问题:
在咱们项目中方案四和方案五都用过。
方案五在使用中没有遇到什么问题,就是刚开始没有作字体适配都是用的rem,后面加入了字体适配,这种方案设计师相对轻松些,不用考虑在大小屏幕下的布局效果。
方案四时没有跟ui设计师沟通清楚,致使设计师在设计图上一行排了不少交互元素,在小屏下放不下去,又不能简单放百分比(元素里的文字放不下)。因此仍是要作动态判断大小屏,作出相应适配。这个方案可能设计师须要考虑的多些,尽可能减小一行内的交互元素,当一行交互元素多时要考虑小屏手机怎么适配。
其实对于1px的适配在苹果端很好,在android端各个厂商手机差异太大,适配有不少问题。这是为何绝大多数方案里都放弃了android端1px适配。不过最近看到不少网站都用了densitydpi=device-dpi这个安卓的私有属性来兼容部分安卓机型,这个属性在新的webkit已经被移除了,使用它主要为了兼容低版本的android系统。
这里大漠老师针对flexible方案进行了改版,兼容了更多的android机型的1px效果。文章传送门
他给了个压缩版的方案,我看了下源码,把它写了一遍,不知道有没有问题,效果是同样的。
var dpr, scale, timer, rem; var style = document.createElement('style'); dpr = window.devicePixelRatio || 1; scale = 1 / dpr; document.documentElement.setAttribute('data-dpr', dpr); var metaEl = document.createElement('meta'); metaEl.setAttribute('name', 'viewport'); metaEl.setAttribute('content', 'target-densitydpi=device-dpi, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); document.documentElement.firstElementChild.appendChild(metaEl); document.documentElement.firstElementChild.appendChild(style); if (980 === document.documentElement.clientWidth) { metaEl.setAttribute('content', 'target-densitydpi=device-dpi,width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1'); } function refreshRem () { var c = '}'; var width = document.documentElement.clientWidth; var isPhone = window.navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i); if (!isPhone && width > 1024) { width = 640; c = 'max-width:' + width + 'px;margin-right:auto!important;margin-left:auto!important;}'; } window.rem = rem = width / 16; style.innerHTML = 'html{font-size:' + rem + 'px!important;}body{font-size:' + parseInt(12 * (width / 320)) + 'px;' + c;; } refreshRem(); window.addEventListener('resize', function () { clearTimeout(timer); timer = setTimeout(refreshRem, 300); }, false); window.addEventListener('pageshow', function (e) { if (e.persisted) { clearTimeout(timer); timer = setTimeout(refreshRem, 300); } }, false);
这些方案只是针对绝大部分机型,项目中可能有些特殊机型有特殊问题,须要特殊对待。好比在这篇文章中做者使用flexible在小米max和荣耀8中有问题,须要特殊hack。传送门,我没有这种手机,也没有对此作验证。
对于上面的五种方案,方案五看似是适配最好的,可是当项目中引入第三方插件时可能要一一适配,好比:引入一个富文本,里面设置字体大小的通常都是px,你须要将其一一转换成rem。而对于方案二,能够直接用px作单位来百分百还原设计稿,引入的插件时也不用适配。因此说,具体项目中用哪一个方案,其实不光是前端的选择,还要跟设计师讨论下,知足设计需求,选择最适合项目的方案。
以上是我的对于移动端适配的一些理解,若有不对欢迎指正。
参考文章: