• 1

  • 472

移动端适配浅析

1星期前

一些基本概念

在进行介绍之前,首先得明确下面这些基本概念(术语):

物理像素(physical pixel)

物理像素又被称为设备像素,他是显示设备中一个最微小的物理部件。每个像素可以根据操作系统设置自己的颜色和亮度。正是这些设备像素的微小距离欺骗了我们肉眼看到的图像效果。

设备独立像素(density-independent pixel)

设备独立像素也称为密度无关像素,可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如说CSS像素),然后由相关系统转换为物理像素。

屏幕密度

屏幕密度是指一个设备表面上存在的像素数量,它通常以每英寸有多少像素来计算(PPI)。 PPI

设备像素比(device pixel ratio)

设备像素比简称为dpr,DPR(devicePixelRatio)是默认缩放为100%的情况下,设备像素和CSS像素的比值。它的值可以按下面的公式计算得到:

设备像素比 = 物理像素 / 设备独立像素 在JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr。而在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)。

在早先的移动设备中,并没有DPR的概念。随着技术的发展,移动设备的屏幕像素密度越来越高。从iphone4开始,苹果公司推出了所谓的retina视网膜屏幕。之所以叫做视网膜屏幕,是因为屏幕的PPI(屏幕像素密度)太高,人的视网膜无法分辨出屏幕上的像素点。iphone4的分辨率提高了一倍,但屏幕尺寸却没有变化,这意味着同样大小的屏幕上,像素多了一倍,于是DPR = 2

众所周知,iPhone6的设备宽度和高度为375pt * 667pt,可以理解为设备的独立像素;而其dpr为2,根据上面公式,我们可以很轻松得知其物理像素为750pt * 1334pt。 某元素的CSS样式: width: 2px; height: 2px; 在不同的屏幕上,CSS像素所呈现的物理尺寸是一致的,而不同的是CSS像素所对应的物理像素具数是不一致的。在普通屏幕下1个CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素对应的却是4个物理像素。

看到这里,你能感觉到,在移动端时代屏幕适配除了Layout之外,还要考虑到图片的适配,因为其直接影响到页面显示质量。

meta标签

标签有很多种,而这里要着重说的是viewport的meta标签,其主要用来告诉浏览器如何规范的渲染Web页面,而你则需要告诉它视窗有多大。在开发移动端页面,我们需要设置meta标签如下: 代码以显示网页的屏幕宽度定义了视窗宽度。网页的比例和最大比例被设置为100%。

rem原理

rem方案的原理其实就是,将每一个不同的屏幕划分成相同的份数,让同一个元素在不同的屏幕上占据相同比例的空间。1rem等于此页面html的font-size,rem可以理解为每份是多少px,比如说,我们的设计稿宽度为750px,我们规定将页面划分成10份,那么rem=75px,如果有一个div宽度为75px,就刚好为1rem。即,将750px宽的屏幕划分为10份,这个div宽度占一份。那么只需要让此div在不同宽度的屏幕下宽度都占一份,就行了。那么我们只需要做一件事,就是确定不同的屏幕下一份有多宽,即不同设备下1rem等于多少px?这很容易计算,document.body.clientWidth / 10 就能算出来。 设计稿宽度 设计稿 r e m = 屏幕宽度 ( c l i e n t W i d t h ) 屏幕 r e m \frac{设计稿宽度}{设计稿rem} = \frac{屏幕宽度(clientWidth)}{屏幕rem} 通过这个式子很清楚地看到,核心思想就是把不同的屏幕划分成相同的份数,在设计稿中占一份的,在所有设备屏幕上都应该占一份。

lib-flexible原理

flexible实际上就是能过JS来动态改写meta标签。lib-flexible会自动在html的head中添加一个<meta name="viewport">的标签,同时会自动设置html的font-size为屏幕宽度除以10,也就是1rem等于html根节点的font-size。假如设计稿的宽度是750px,此时1rem应该等于75px。假如量的某个元素的宽度是150px,那么在css里面定义这个元素的宽度就是 width: 2rem 代码类似这样:

var 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);
}
复制代码

注意:

1.检查一下html文件的head中,如果有 meta name="viewport"标签,需要将他注释掉,因为如果有这个标签的话,lib-flexible就会默认使用这个标签。而我们要使用lib-flexible自己生成的 meta name="viewport"来达到高清适配的效果。

2.如果每次从设计稿量出来的尺寸都手动去计算一下rem,就会导致效率低下,还有可能会计算错误,所以我们可以使用px2rem-loader自动将css中的px转成rem

lib-flexible 设置 dpr 又是为了什么?

为了解决“极致的1px”问题。 再次把这张图放一下:

在 Retina 屏幕上面,1个 CSS 像素实际上对应着4个物理像素(dpr=2的情况)。当我们在屏幕上画1px 的线时,Retina 屏幕实际上是2个物理像素宽。我们要实现“极致的1px”就是希望在Retina屏幕上画出一条1个物理像素宽的细线。

原理其实也很简单,对于 dpr=2的设备,设置 rem 的值为正常值的 2 倍,将需要画出“极致的线”的地方使用1px表示,再将页面缩小2倍,这样1px的线就变成了“0.5px”,实际上为1个物理像素来表示。

同理,对于 dpr=n 的设备,将 rem 值设为正常的 n 倍,在对页面缩放 n 倍。

现在的 lib-flexible 有两个版本,master 版本和 2.0 版本。在 master 版本中使用 正则来判断系统种类(IOS/Andorid),但是只对 IOS 系统做了 dpr 的适配,对于 Android 手机,统一设置 dpr =1; 实际上使用 rem 配合 dpr 缩放的方式有非常多的问题。最明显的例子就是安卓机 dpr 的混乱,例如 VIVO 的某款手机甚至出现了 dpr 为小数的情况(上文我们介绍到,1个 CSS 像素对应多少个物理像素 dpr 就是几,显然不可能出现小数位的情况),所以使用缩放来实现“极致的1px”兼容性并不是很好。

有团队的做法是将 dpr 设置有问题的机型进行上报,然后收集成一个白名单,再根据白名单在 lib-flexible 对于有问题的机型进行单独适配,然而这个工作量一般的小团队没有精力去做……

所以我们可以看到 2.0 版本已经舍弃了这种做法。

// 进行了精简
// detect 0.5px supports
var docEl = document.documentElement;
if (dpr >= 2) {
  var fakeBody = document.createElement('body')
  var testElement = document.createElement('div')
  testElement.style.border = '.5px solid transparent'
  fakeBody.appendChild(testElement)
  docEl.appendChild(fakeBody)
  if (testElement.offsetHeight === 1) {
    docEl.classList.add('hairlines')
  }
  docEl.removeChild(fakeBody)
}
复制代码

可以看到这个版本对于设备进行了一个测试,如果该设备支持 0.5px 的书写方式,那么就在 html 元素上面添加一个 hairlines 的类,我们在使用的时候只需要在header中添加.hirlines div {border-width: 0.5px}就可以了。

lib-flexible源码

rem的缺点

  • 在奇葩的dpr设备上表现效果不太好,比如 一些华为的高端机型 用rem布局会出现错乱。
  • 设置根字体大小的方式有两种,一种是媒体查询,优点:不需要额外使用js去更改html的字体,缺点:不连续,或者说并能完全实现对所有设备的布局规范统一;另一种是js动态更改html字体,优点:连续;缺点:不如直接写媒体查询的体验好;
  • 不支持css3 calc的需要大量密集的 @media hack;
  • 使用iframe引用也会出现问题;
  • 个人认为最重要的一点,它不是vw。为什么这么说?因为我们一直在使用Hack手段用rem模拟vw特性,而vw是css原生的支持。

关于vw

lib-flexible的GitHub已经宣布停止使用rem方案了,因为找到了新的更好的全局性参照单位:vw。 什么是 vw ?同样根据CSS Values and Units Module Level 4:(vw)等于初始包含块(html元素)宽度的1%。换句话说,可以认为: 1vw 就等于屏幕宽度的1% 。这个单位似乎是专门为我们适配量身打造的。首先,它本质上就是一个百分比单位,比如 3vw 就相当“屏幕宽度的百分之三”;其次,它又是全局性的与屏幕宽度直接相关的单位。那么上一节概念阐述中举的两个百分比的例子,用 vw 单位可以直接写成:75px = 75/750 = 10vw,即占页面宽度的10%。

那么移动浏览器对 vw 的支持度如何呢?根据caniuse.comcaniuse

即 iOS 8+和 Android 4.4+都支持 vw ,而目前的手机应用通常支持iOS 9+和Android 5+。所以,实践中使用 vw 单位完全可行。而这也正是手机淘宝团队19年初正式转向使用 vw 单位适配的原因。

那么,还是之前的问题,人工进行设计稿的 px 到 vw 单位的转换太麻烦。在开发中使用Webpack编译打包,那么已经有人开发了postcss的插件:postcss-px-to-viewport。配置好这个插件,你写CSS的时候可以严格按照设计稿上的像素值去写,这个插件负责将你写的 px 转换为 vw 。

参考资料:

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

ios

472

相关文章推荐

未登录头像

暂无评论