4

前言

这篇文章的内容如题目一样,主要分为两个部分,

  1. 谈谈业内主流的移动端适配解决方案

  2. 点5像素的由来和实现方法

建议在读这篇文章的时候先读下这篇文章《高清屏概念解析与检测设备像素比的方法_20161005》,因为这些文章涉及的很多概念在这篇文章中都会提到。

1.再谈移动端适配

1.1百分比解决方案的缺点

在我们的项目中大量的使用百分比来解决页面在宽度上的自适应,其实在高度上并没有很好的自适应。所以在我们的前端页面会出现一些比较奇怪的问题。比如下面这样:

还有一个比较明显的问题就是:在任何机型上我们的按钮的高度是不会变化自适应的,所以小屏手机我们的按钮看起来很臃肿。

1.2一些常用单位的概念解析

在谈我为什么在我们的项目中引入rem单位的时候,先来详细的了解几个常用的单位概念。

  1. px像素(Pixel)。相对长度单位。像素px是相对于显示器屏幕分辨率而言的(也就是说是跟物理设备有关的)。拿高清屏和普通屏来做对比就是普通屏幕的1个像素点就是1个物理像素点,而高清屏的1个像素点4个物理像素点

  2. em相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。em单位的特点:1. em的值并不是固定的;2. em会继承父级元素的字体大小。

  3. rem相对长度单位。rem是CSS3新增的一个相对单位,这个单位引起了广泛关注。这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。

任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1rem=16px。那么12px=0.75rem,10px=0.625rem。为了简化font-size的换算,需要在css中的html选择器中声明font-size=62.5%,这就使rem值变为 16px*62.5%=10px, 这样12px=1.2rem, 10px=1rem, 也就是说只需要将原来的px数值除以10,然后换上rem作为单位就行了。

注意:当我们在根节点<html>上设置了font-size基准值以后,在文档中有使用rem单位的属性值都是相对于根节点font-size的一个相对值。比如说一些元素的属性如width height margin等。也正是这个原因,现在很多网站的移动端网站都在使用rem单位作为适配工具。

在使用rem的时候比较麻烦的就是pxrem换算的问题。上面的除10的方案是比较简单的。但是根据设置基准值的不同换算方法也不一样。如果我们使用scss来写我们的样式表的话,解决方法就比较简单了,代码如下:

@function px2rem($px){
    @return ($px/10)/2 + rem;  //  相当于$px/20  +rem
}

width:px2rem(100px);   //5rem
height:px2rem(200px);  //10rem

1.3为什么引入 rem 单位

由上面的内容已经知道百分比单位在多屏幕适配上的缺点和rem单位的优点。那么rem单位能为我们的开发带来什么呢?来看一个常见的多列布局,淘宝移动端的商品列表页,如下图:

设计稿(不管是iphone6的二倍稿还是iphone5的二倍稿)中给展示商品的坑位的宽高比是固定的。为了能够使这个列表在不同的屏幕上达到最佳的显示效果,需要保持宽度和高度比一致。如果使用百分比肯定是满足不了这个需求的,因为高度上我们没有办法控制。

iphone6和iphone5的屏幕宽度和高度比不一样,当我们使用百分比做多列布局的时候就会出现下面的这种情况:

所以为了满足商品坑位在不同屏幕上的宽高比一致,淘宝使用的是rem的解决方案,也是目前最好的解决方案。

1.3为什么将计算根元素的font-size值的js放在head标签中

设置根节点font-size值的方法,第一种是使用css的Media queries,示例代码如下:

@media (min-device-width : 375px) and (max-device-width : 667px) and (-webkit-min-device-pixel-ratio : 2){
      html{font-size: 37.5px;}
}

上面的这种方式在我之前做过的项目中使用了一段时间。上面的设置方法有一个很明显的问题font-size是在一个屏幕宽度的区间上有一个基准值。像安卓手机种类的繁多,屏幕大小就更多的情况下,上面的方法很鸡肋。

第二种解决方案,就是使用JavaScript根据当前屏幕的宽度动态计算font-size值,这种方法可以保证屏幕宽度连续变化的时候,font-size基准值也是连续变化的

计算方法可以参考这篇文章《根据iPhone6设计稿动态计算rem值》

那么最后一个问题也来了:为什么将计算rem单位的js放在head标签里面?

一句话总结:在浏览器中文档流是从上往下加载渲染的。为了保证发生不必要的重绘或者是重排肯定是越早给根节点设置font-size值越好。

1.4什么情况下使用rem来布局和注意的问题

  1. 整体的布局还是使用百分比

  2. 使用rem的最佳场景是,遇到例如多列带有图片的列表,常常需要图片固定宽高比例

  3. 研究了一些网站,比如淘宝,对字体字体一般情况建议使用px

  4. 出现1px像素线的地方,仍旧使用border-width:1px;而不是border-width:.1rem;

2.点5像素的由来

在前言中推荐阅读的那篇文章中已经知道在 高清屏(Retina) 中控制显示的最小的物理单元包括4个基本的像素点,而普通屏幕1个点像素就是1个物理像素单元。所以在高清屏(Retina)出来之前,就算我们在css中写 0.5px,对于显示屏幕也是不识别的。但是在高清屏(Retina)中我们可以通过间接的方法实现0.5px的效果。

下面的这张图可能能让我们迅速回忆上篇文章的内容

来看一段简单的示例代码加深理解:

<div class="item1"></div>
<div class="item2"></div>

css代码如下:

.item1{
    width:100px;
    height:50px;
    border-top:1px solid #000;
    float: left;
    margin-top:10px;
}

.item2{
    width:100px;
    height:50px;
    margin-top:10px;
    border-top:.5px solid #de1dfb;
    float: left;
}

chrome中的显示效果如下图:

我把这张图截取下来放到 PS 中放大可以很明显的看到一些问题。就是高清屏实际上是用了两排的物理像素点来显示1px的像素线。且不做特殊处理的话1px0.5px的在Chrome浏览器中显示效果是相同的。也就是说Chrome浏览器不识别0.5px

但是相同的代码我在 safari浏览器中的效果却是下面的效果:

下面的效果是我在PS中做了放大后的效果。

所以,我们可以得出一个结论:对于0.5px不同浏览器对它的识别是存在差异的。

以下这张图是一位网友对一些常用设备是否识别0.5px做的统计:

其实从视觉的感受上来说0.5px像素的显示效果确实是比1px的显示效果要好很多。大多数情况下也更符合设计稿上的1px线的效果。那么我们有没有办法可以让1px在不同的浏览器和设备中显示真正的1像素的效果呢?

答案是肯定定的。

2.1传统的实现方法,也就是我们的项目中正在使用的方法:

伪元素 + css3的缩放巧妙地实现;

基本步骤就是:

  1. 设置目标元素定位参照

  2. 给目标元素添加一个伪元素before或者after,并设置绝对定位

  3. 给伪元素添上1px的边框

  4. 设置伪元素的宽高为目标元素的2倍

  5. 缩小0.5倍(变回目标元素的大小)

  6. 使用border-box把border包进来

先来看一个1像素0.5像素的显示效果,下面的截图是在chrome中:

实现的代码如下:

<div class="item4">测试用的边框</div>
<div class="item5">测试用的边框</div>
.item4, .item5 {
    width: 200px;
    height: 100px;
    position: relative;
}

.item4 {
    border: 1px solid #000;
}

.item5::before {
    content: '';
    position: absolute;
    width: 200%;
    height: 200%;
    border: 1px solid #000;
    transform-origin: 0 0;
    transform: scale(0.5, 0.5);
    box-sizing: border-box;
}

可以很明显的看到缩放前和缩放后的效果。在ps中把上面的截图放大很多倍以后我们可以看到显示细节。缩放以后确实是使用的最小的物理像素单元来显示边框。如下图:

注意:按照css3 transformscale的定义,理论上边框可以任意细(1/n px)。但是上图中已经是物理设备能够使用的最小的物理单元了,那么我们继续缩放会有什么现象呢?

使用一下代码

<div class="item6">测试用的边框</div>
.item6::before {
    content: '';
    position: absolute;
    width: 400%;
    height: 400%;
    border: 1px solid #000;
    transform-origin: 0 0;
    transform: scale(0.25, 0.25);
    box-sizing: border-box;
}

chrome中显示效果如下,可以很明显的看到颜色变浅了,但是是否变得更细了我们肉眼无法分辨:

在PS中放大以后的效果:

很明显可以看到线并没有变细,但是线的颜色确实是变浅了。这样的结果也符合我们的预期,就是已经到了物理设备能够显示一块颜色的最小的物理单元了。

2.2js动态设置viewport的方案

一些大厂(所谓的淘宝)的解决方案就是使用js动态获取屏幕的设备像素比,然后动态设置viewport。当然也是我认为目前最好的解决方案。

meta.setAttribute('content', 'initial-scale=' + 1/dpr + ', maximum-scale=' + 1/dpr + ', minimum-scale=' + 1/dpr + ', user-scalable=no');

我们知道,一般我们获取到的视觉稿大部分是iphone6的,所以我们看到的尺寸一般是双倍大小的,在使用rem之前,我们一般会自觉的将标注/2,其实这也并无道理,但是当我们配合rem使用时,完全可以按照视觉稿上的尺寸来设置。

  1. 设计给的稿子双倍的原因是iphone6这种屏幕属于高清屏,也即是设备像素比(device pixel ratio)dpr比较大,所以显示的像素较为清晰。

  2. 一般手机的dpr是1,iphone4,iphone5这种高清屏是2,iphone6s plus这种高清屏是3,可以通过js的window.devicePixelRatio获取到当前设备的dpr,所以iphone6给的视觉稿大小是(*2)750×1334了。

  3. 拿到了dpr之后,我们就可以在viewport meta头里,取消让浏览器自动缩放页面,而自己去设置viewport的content例如(这里之所以要设置viewport是因为我们要实现border1px的效果,在scale的影响下,高清屏中就会显示成0.5px的效果)

总结:

由以上两个部分的内容可以知道,不管是在做多终端适配还是实现点5像素的线,都是存在css和js两种解决方案的。两种方案相比来说,我都认为使用JavaScript的解决方案都胜一筹。唯一的缺点就是会在html的head标签中引入一段js代码 。

记得刚入行的时候,业内有一个叫“雅虎军规”的东西,是好多前端做页面优化参考的标准。其中有一条就是要将js文件放在body标签的底部。到现在很多年了,时代在变化,我们的网络带宽也在提升,“雅虎军规”中的一些内容,可能有些已经不适合我们现在应用开发的场景。而且我觉的做技术不应该拘泥于已有的一些规定,而应该按照我们的场景选择适合我们的技术和解决方案。

纵然有瑕疵,有些也是可以通过技术手段来解决的。比如在head标签中引入计算font-size和检测设备独立像素比的js的时候,会担心js的执行阻塞页面的渲染。而我们完全可以通过review的方式确定js代码的执行不会出现阻塞,而影响文档流的加载。

还是那句话:没有十全十美的技术方案,只有适合不合适当前业务场景的技术方案。

参考文章

[css3的字体大小单位[rem]到低好在哪里](https://www.zhihu.com/questio...
移动端高清、多屏适配方案
使用Flexible实现手淘H5页面的终端适配
lib-flexible源代码



zhiqiang21
1.4k 声望1.1k 粉丝

I'm a Vimer!