首页 > 前端 > 移动端高清屏适配方案

移动端高清屏适配方案

移动设备各种规格高清屏的出现,使得PC端常规的px适配方案变得捉襟见肘,在这种情况之下,有针对移动端独立的一套适配方案就显得非常地重要。

第一印象

重度依赖meta标签的viewport和html标签的font-size,配合使用dpr\rem\px

一些概念

1、Retina

“Retina”是“视网膜”的意思,指将更多的像素点缩至同样大小的屏幕里,显示屏的分辨率极高,超越了肉眼分辨单点像素的极限。 Retina技术一开始由苹果公司应用在iPhone4上,由于该技术带来的细腻屏幕显示体验,其它智能手机厂商纷纷仿效,由此引发了一股从普通屏幕到高清屏幕升级的潮流。如今市面上大部分智能手机都搭载了高清显示屏。

2、物理像素(physical pixel)

物理像素也叫设备像素(device pixel),指显示屏中最小的一个物理部件,是屏幕的实际像素

3、逻辑像素(logical pixel)

逻辑像素是一个抽象的单位,是计算机显示系统的一个虚拟像素,可以认为是应用程序所使用的计算机坐标系统上的一个点,对应于网页开发中的CSS像素,下图描述了CSS像素(逻辑像素)和设备像素(物理像素)的关系: 所以说,css中的1px并不一定等于设备的1px。

4、设备像素比(device pixel ratio)

设备像素比dpr是描述物理像素和设备独立像素关系的一个比值,计算公式为:

设备像素比 = 物理像素 / 逻辑像素

拿iPhone7为例,三者有如下关系:

也就是说一般情况下,浏览器会以375×667的分辨率来把页面渲染显示在实际上有750×1334分辨率的屏幕上.

5、屏幕密度

屏幕密度是指一个设备表面上存在的像素数量,它通常用PPI(pixels per inch)来表示,即每英寸包含的像素数量,计算公式为:

例如iPhone7的4.7英寸,像素分辨率为1334*750的Retina HD显示屏,计算得出其ppi值为326.

6、viewport

弄明白上面几个概念后,viewport也就不难理解了. viewport字面意思是’视图窗口’,同样是为了解决移动设备高清屏浏览问题而出现的新概念.通过类似下面的基础写法,它允许我们给移动浏览器设定一个虚拟的宽度,从而实现更友好的页面浏览体验:

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">

viewport是个很大的课题,这里大概了解即可,ppk大师写了很多相关文章,补充作为深入研究.

7、rem——移动端适配利器

先看看W3C给rem的定义:

‘rem’ — font size of the root element.

也就是说,rem是根据根元素的font-size来设置大小基准,一般的浏览器默认根元素的font-size是16px即 html { font-size:16px; //1rem=16px }

在这基础上,如果我们想给一个P标签设置12px的字体大小那么用rem来写就是

p {
    font-size: 0.75rem; //12÷16=0.75(rem)
}

rem的这个特性给我们提供了一个适配的思路,即我们可以人为动态地设置html的font-size的值并以此为参照,让rem和px的转换比例适应不同dpr的屏幕,从而能在css大小布局中得以重用.事实上,手淘和网易正是基于该特性去实现移动web的适配.

iPhone手机和安卓手机

iPhone的dpr有三个,分别是1,2,3.下面这张图来自paintcodeapp,它较为全面地描述了历代iPhone设备上各种参数的关系: 从上图我们可以看出,无论是iPhone3g的320物理宽度还是iPhone4的640物理宽度,css中的320就代表着屏幕的宽度,其它以此类推.

而安卓的dpr值则较为复杂,有2.6的Google Pixel,也有4.0的Samsung Galaxy S7: 非常的多,五花八门,于是乎手淘的flexible干脆不考虑安卓,全部dpr设为1.

更多移动设备的屏幕参数,可以到mydevice上查阅.

设计师与前端

目前设计师通常选择iPhone6作为设计基准,交付给前端的视觉稿是750pxx1334px的尺寸,然后前端开发人员再通过一套适配规则自动适配到其他的尺寸,下面一张图是手淘的协作模式:

关于图片

各种手机不同dpr的现状给开发带来了一定的障碍,比如说我们要设置一张 CSS分辨率 为30×50的图片,对于一个 dpr=2 的手机,为了不让图片看起来模糊,我们需要使用一张 实际分辨率 为60×100的图片;但是对于一个 dpr=1 的手机,我们又没有必要使用一张 实际分辨率 为60×100的图片,因为这样会造成带宽浪费.

所以,为了有效节省带宽,同时保持图片的浏览体验,可以给不同dpr的设备准备不同尺寸的图片,例如:

/1/a.jpg
/1.5/a.jpg
/2/a.jpg

至于如何自动加载哪个版本的图片,Reda Lemeden在Towards A Retina Web一文中提到了一些方案,下面这两种做法值得一提:

1、使用CSS媒体查询

通过css的device-pixel-ratio属性,我们可以对不同dpr进行media query,从而使高清屏加载高清图片:

.icon {
    background-image: url(example.png);
    background-size: 200px 300px;
    height: 300px;
    width: 200px;
}

//如果是dpr大于1.5的情况
@media only screen and (-Webkit-min-device-pixel-ratio: 1.5),
only screen and (-moz-min-device-pixel-ratio: 1.5),
only screen and (-o-min-device-pixel-ratio: 3/2),
only screen and (min-device-pixel-ratio: 1.5) {
    .icon {
        //启用两倍像素尺寸图片
        background-image: url(example@2x.png);
    }
}

这种方法的好处在于只需要下载匹配到的图片资源,但是使用场景有很大的局限性——只能用在css控制的背景图片background-image属性,无法用在img标签或其它场景,同时不利于大规模项目的开发管理。

2、使用JavaScript

通过js的window.devicePixelRatio属性同样可以做到设备像素比dpr的检测,目前各个浏览器厂商对其的支持已经,非常好可以放心地使用:

用法:

$(document).ready(function(){
  if (window.devicePixelRatio > 1) {
    var lowresImages = $('img');

    images.each(function(i) {
      var lowres = $(this).attr('src');
      var highres = lowres.replace(".", "@2x.");
      $(this).attr('src', highres);
    });
  }
});

js的做法适用范围比css做法广,可以用在页面上的所有图片,虽然在非Retina屏幕下不用加载高清图片,但是在Retina屏幕下却需要加载标清和高清两种图片,而且会引起浏览器进行页面重绘(repaints)操作,所以在实际应用场景中还是得权衡好利弊。

手淘的解决方案–flexible.js

谈到移动Web开发,不得不提手淘的flexible,毕竟它经历了时间和实战场合的多重考验,我们现在看到的手淘页面也还在使用着这个方案。 以目前较为流行的750px宽度设计稿为例,手淘的做法就是将750的宽度分为10份,每份是75,整个宽度就是10rem,并将<html>font-size设置为75px,这样我们就得到了1rem=75px的换算关系。

下面是flexible.js中对rem的处理部分:

function refreshRem(){
        var width = docEl.getBoundingClientRect().width;
        if (width / dpr > 540) {
            width = 540 * dpr;
        }
        var rem = width / 10; //分为十份
        docEl.style.fontSize = rem + 'px'; //设置font-size
        flexible.rem = win.rem = rem;
}

如此一来,页面的元素我们就可以用rem来进行愉快的布局了。

但是这样还没完,flexible.js的源码中还有这样一段非常重要:

if (!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;
}

这段代码对ios设备做了特殊处理,也就是检测到有搭载Retina屏幕的iPhone,会设置不同的dpr,然后对于安卓设备,一律设置dpr=1,至于为什么要这样做,我有两种猜测:

  • 手淘的工程师是骨灰果粉,唯ios马首是瞻:)
  • 偷懒,面对类型繁杂的安卓设备,选择放弃治疗:)

这样的做法对安卓高清屏有什么影响? 答:无法通过dpr去进行media query,从而实现字体的适配(具体原因见下面)

为什么布局用的是rem,而字体用的却还是px?

布局对rem转换成px的值的格式要求不高,比如允许图片有.33rem=24.75px的宽度. 但是字体对px格式有要求,比如不希望出现字体有24.75px这样的大小. 现在主流的字体大小都是类似12px、16px等,如果我们用rem做为字体单位,那么转成px的时候,势必会出现奇数或者小数的情况. 因此,为了避免这种情况,我们还是要用px做为字体的单位.但是又该怎么做呢?

手淘的解决方案就是默认写个dpr为1时的字体大小,然后使用css的选择器,根据dpr的不同来设置字体大小:

div {
    width: 1rem; 
    height: 0.4rem;
    font-size: 12px; // 默认写上dpr为1的fontSize
}

[data-dpr="2"] div { //dpr为2时
    font-size: 24px;
}

[data-dpr="3"] div { //dpr为3时
    font-size: 36px;
}

1px像素问题

假设这样一个需求:你需要在任何屏幕上这条边框都是1物理像素. 我们当然不能直接一个border-width:1px,因为在retina屏下css的1px映射到物理像素就可能会有2px或者3px.解决方案有多种,这里以手淘的flexible为例: * 在dpr = 1 时, 输出是1:1的还原,因此不必顾虑; * 在dpr = 2 时,设置initial-scale=0.5; * 在dpr = 3 时,设置initial-scale=0.3333333333333333. 这时我们方可直接的写1px,然后交由浏览器去进行缩放.至此,经典的1px问题得以解决.

总结

最近项目里做一个手机H5,赶紧趁机会恶补了移动Web开发的知识,花了几天总算渐渐理清知识点。移动Web开发是一个很大的课题,本文主要总结了高清屏的适配方案,还有例如meta、字体、事件等方面还需深入了解,一边学习一边做下笔记,好的笔记会继续转成文章发布出来。 最后盗用@南宮瑞揚的一张图作为收尾:

扩展阅读


本文标题:移动端高清屏适配方案
转载请注明出处,欢迎分享