zoukankan      html  css  js  c++  java
  • 移动开发屏幕适配分析

    我在开发前端的时候曾经会有几个疑惑:

    1)拿到的设计搞的宽度是640px或750px的,在页面不同尺寸屏幕布局的时候怎么换算。

    2)移动端布局是用%、px、rem伸缩盒 Flexible Box Layout还是多列Multi-column

    3)用px设置了字体大小,但是在ipad上面显示的却很小,字体大小是用rem比较好还是px+媒体查询比较好。

    4)媒体查询@media分几个尺寸的范围能覆盖比较多的尺寸情况。

    在分析适配问题之前,要先了解一些基础概念,Retina与HI-DPI、DPI与PPI、DP与PT、viewport等。

    一、Retina显示屏

    Retina Display是苹果注册的命名方式,其他厂商只能使用HI-DPI或者其他的命名方式,不过意思都是一样的,就是屏幕的PPI非常高。

    iPhone 3GS(非Retina屏幕) iPhone 4(Retina屏幕)

    右边的图片明显要比左边的清晰,这是因为PPI要高,何为PPI。

    1)PPI与DPI

    PPI和DPI这两个是密度单位,不是度量单位。

    1. PPI(pixels per inch):图像分辨率 (在图像中,每英寸所包含的像素数目)【1英寸(in)=2.54厘米(cm)】

    2. DPI(dots per inch): 打印分辨率 (每英寸所能打印的点数,即打印精度)

    PPI的计算方式如下,其中长度像素数和宽度像素数取的是物理像素(Physical Pixel)即设备像素

    PPI = √(长度像素数² + 宽度像素数²) / 屏幕对角线英寸数

    这个物理像素不是说屏幕的大小,例如iPhone4的屏幕是320*480(这是DP/PT设备独立像素),但物理像素是640*960,屏幕是3.5寸。自己按下计算机算出来≈326。

    知乎上看到,还有一种比较复杂的PPI计算方法,考虑了观察者的距离,下图是一个极简版本:

    上面又出现了几个概念,物理像素、DP、PT又是什么鬼?

    2)物理像素(Physical Pixel)

    物理像素又被称为设备像素,他是显示设备中一个最微小的物理部件。下图摘自《The Ultimate Guide To iPhone Resolutions》,原文是洋文写的,对应的中文可以看这里

    上图中iPhone 3GS的物理元素就是320*480,而4/4S是640*960。其中4/4S的320*480其实就是PT,而render中的2x就是dpr(设备像素比)

    3)DP、PT与SP

    DP或PT是测量单位,DP(Density-independent pixels,也会叫DiP)表示独立于设备的像素点,PT(Point)表示点。

    DP用在Android上,PT用在Apple上,但是他们本质上是相同的。

    SP(Scale-independent pixel)和DP、PT从用途上来讲是不同的,但是工作方式相同。SP表示与比例无关的像素,通常用来定义字体大小。

    这三个都是抽象像素,不会因为PPI的变化而变化,在相同物理尺寸和不同PPI下,他们呈现的高度大小是相同。也就是说更接近你手机看上去的大小,而px则不行。

    上图摘自google的《Device metrics》,要浏览google的页面你懂得。表格中Density(密度)的基础单位,在Android上是mdpi,而在IOS中是1x。

    4)CSS像素

    CSS像素是Web编程的概念,指的是CSS样式代码中使用的逻辑像素。例如300px跟font-size:14px,这类css语句中的px。

    CSS像素和物理像素(Physical Pixel)是容纳的关系。

    例如iPhone4/4S中,1个CSS像素=4个物理像素(也叫设备像素):

    做个简单的例子,通过改变viewport,来手动放大屏幕。简单设置一个ul的CSS。

    ul{
        padding-left:20px;
        font-size: .5rem;
    }

    1. 页面宽度等于屏幕DP或PT值

    2. 页面宽度等于物理像素(Physical Pixel)

    从两张图片中可以看到,同样是20px,看起来却有大小差别,上图比下图宽了一倍,与第一张图正好对应起来。

    5)设备像素比(device pixel ratio)

    设备像素比简称为dpr,其定义了物理像素(Physical Pixel)和DP/DiP(设备独立像素)【IOS中是PT(点)】的对应关系。

    设备像素比 = 物理像素(Physical Pixel) / 设备独立像素(DP/DiP或PT)

    DP/DiP或PT与屏幕密度有关(屏幕密度需通过PPI计算出来)。

    在JavaScript中,可以通过 window.devicePixelRatio 获取到当前设备的dpr。

    而在CSS中,可以通过 -webkit-device-pixel-ratio , -webkit-min-device-pixel-ratio 和 -webkit-max-device-pixel-ratio 进行媒体查询。

    二、viewport

    在桌面浏览器中,viewport就是浏览器窗口的宽度高度。

    但移动设备的屏幕比桌面屏幕要小得多,为了要让网页在小尺寸的屏幕上显示正确,就需要对viewport做些处理。

    需要把viewport分成两部分:visual viewport和layout viewport。

    George Cummins在Stack Overflow上对这两个概念做了分析。大致意思如下:

    把layout viewport想像成为一张大图。现在用一个比较小的框,通过它来看这张大图。在框内看到的部分就是visual viewport。框中的度量单位是CSS像素。
    可以把这个框靠近一些(放大看局部)或靠远一些(缩小看整体)。也可以改变框的方向,但是大图layout viewport的大小和形状永远不会变。

    1)visual viewport

    visual viewport是页面当前显示在屏幕上的部分。用户可以通过滚动来改变他所看到的页面的部分,或者通过缩放来改变visual viewport的大小。

    结合上面的比喻,再想象一下大图和框的操作。

    图片来自于文章《A tale of two viewports》,是用洋文写的,对应的中文可以看这里

    2)layout viewport

    layout viewport就是页面原来的大小。

    但是我们用在手机用浏览器打开PC的网页的时候,会看到网页被浏览器自动缩小了,变的太小会导致无法浏览内容。

    3)viewport meta标签

    为了不让浏览器自动缩小,引入了viewport元标签。通过这个元标签控制layout viewport的宽度。

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

    上面这段HTML经常会在移动端页面看到。

    1. width:设置 layout viewport 的宽。

    2. initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例,上面是变成设备的宽度,也就是layout viewport= DP或PT。

    3. maximum-scale:允许用户缩放到的最大比例。

    4. minimum-scale:允许用户缩放到的最小比例。

    5. user-scalable:用户是否可以手动缩放。

     
    浏览器自动缩放 添加了viewport

    注意下图片中的红色框,第二张图片内容超了出来,应该是给文字设置了宽度,并且这个宽度大于layout viewport导致了出来。但其实在Android和IOS中会有不一样的表现。

    在文章《A tale of two viewports》中指出:

    1. 通过 document.documentElement.clientWidth 获取 layout viewport 的宽度

    2. 通过 window.innerWidth 获取 visual viewport 的宽度

    我用iPhone6和红米 note在自己本地做了个获取这两个值的测试,设置了个宽度,撑开页面。

    <div style=" 700px">
      //文字内容
    </div>

    1. 红米中的UC、微信浏览器、自带的浏览器还有移动端的Chrome,显示两个值都一样,是360px,只是UC和微信浏览器不会出现左右滚动条。

    2. iPhone6中的UC浏览器和Safari显示两个值不一样,layout viewport 的宽度是375px,而visual viewport 的宽度是708px,不会出现滚动条。

     

    4)ideal viewport

    这个viewport的尺寸,我理解相当于DP/DiP(设备独立像素)或PT的尺寸。

    在文章《Meta viewport》有详细的说明,如果洋文看起来吃力,可以选择浏览中文版的。

    visual viewport宽度 = ideal viewport宽度/ zoom factor

    zoom factor其实就是meta中的“initial-scale”。

    设置initial-scale后,就开始相对于ideal viewport进行计算产生visual viewport,用visual viewport的宽度值去设置layout viewport的宽度值。

    注意,刚刚用iPhone6测试获取到layout viewport与visual viewport不同,一开始应该是相同的,只是后面渲染的时候style=" 700px"把visual viewport给撑大了。

    如果你拿到的设计稿是640px宽度的,在你用iPhone5布局的时候,其实你可以initial-scale=0.5,将layout viewport放大到640px,接下布局可以通过rem来设置。

    但是手机屏幕的尺寸各不相同,那么initial-scale的值也应该是不同的,这个值是可以通过JavaScript计算出来的,可以参考flexible.js(移动自适应方案)中的代码。

    三、flexible.js

    打开手机淘宝页面,可以看到里面内联了这个库的代码,这个flexible.js库就是用来解决H5页面终端适配的,具体的使用方法可以参考这里

    1)rem

    rem 就是相对于根元素 <html> 的 font-size 来做计算。flexible.js会给html赋font-size的值。下面的代码是在iPhone5中模拟。

    使用方法中并不推荐字体用rem单位,但我觉得可以因地制宜,具体情况具体分析,比如要在iPad中适配,这个时候用rem体验会比较好。

    2)计算initial-scale

    注意下面的Android设备一律dpr=1,我后面做了个简单的测试。

     1 var devicePixelRatio = win.devicePixelRatio;
     2 if (isIPhone) {
     3   // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
     4   if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
     5     dpr = 3;
     6   } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
     7     dpr = 2;
     8   } else {
     9     dpr = 1;
    10   }
    11 } else {
    12   // 其他设备下,仍旧使用1倍的方案
    13   dpr = 1;
    14 }
    15 scale = 1 / dpr;

    将第2行修改下代码,通过正则匹配来判断dpr的值。

    var isRegularDpr = devicePixelRatio.toString().match(/^[1-9]d*$/g);
    if (isRegularDpr) {
     //...逻辑与上面相同   
    }

    在IOS中表现正常,可是在Android中的UC、Chrome浏览器中会放大,微信浏览器显示正常。顺便说下,我的手机是红米note,物理像素是720px,DP(设备独立像素)是360px。

    修改前 修改后  添加target-densitydpi=device-dpi
    CSS像素是360px CSS像素是720px CSS像素是720px

    第二和第三都是720px,宽度都变大了,只是一个是通过“initial-scale=0.5”,另一个是通过“target-densitydpi=device-dpi”。但字体一张变大一张变小了。

    造成两种展现不一样的原因,我的理解是,第一种1个CSS像素=4个物理像素,而第二种是1个CSS像素=1个物理像素。

    Android的viewport中属性“target-densitydpi”,这个“DPI”就相当于“PPI”,我的红米note的“PPI”是267。

    可以的取值如下:

    device-dpi: 使用设备原本的 dpi 作为目标 dp。 不会发生默认缩放。
    high-dpi: 使用hdpi 作为目标 dpi。 中等像素密度和低像素密度设备相应缩小。
    medium-dpi: 使用mdpi作为目标 dpi。 高像素密度设备相应放大, 像素密度设备相应缩小。 这是默认的target density.
    low-dpi: 使用mdpi作为目标 dpi。中等像素密度和高像素密度设备相应放大。
    <value>: 指定一个具体的dpi 值作为target dpi. 这个值的范围必须在70–400之间。

    有一篇文章《Viewport target-densitydpi support》曾经讲到,这个属性将被废弃。

    3)viewport元标签

    如果在页面中已经设置了元标签,那么就读取元标签中的内容,如果没有设置,就动态添加meta标签到head中。

    4)refreshRem函数

    此函数就是在设置html中font-size,以及flexible.rem与win.rem的值,flexible.rem会在后面的两个辅助方法refreshRempx2rem内用到。

    docEl.getBoundingClientRect().width是为了获得 visual viewport 的宽度。

    这个540其实是个经验值,参考Issues#12,目前主流手机最大的css像素尺寸,是540(比如devicePixelRatio为2,分辨率是1080x1920的手机)。

    width / dpr 是因为前面添加了viewport后,width会根据dpr(设备像素比)改变,而540定义的是一个DP/DiP(设备独立像素)或PT值。

    width / 10 主要为了以后能更好的兼容 vh 和 vw。

    function refreshRem() {
      var width = docEl.getBoundingClientRect().width;
      if (width / dpr > 540) {
        width = 540 * dpr;
      }
      var rem = width / 10;
      docEl.style.fontSize = rem + 'px';
      flexible.rem = win.rem = rem;
    }

    5)pageshow与DOMContentLoaded

    pageshow事件类似于 onload 事件,onload 事件在页面第一次加载时触发, onpageshow 事件在每次加载页面时触发,即 onload 事件在页面从浏览器缓存中读取时不触发。

    win.addEventListener('pageshow', function(e) {
      if (e.persisted) {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 300);
      }
    }, false);

    页面文档完全加载并解析完毕之后,会触发DOMContentLoaded事件,HTML文档不会等待样式文件,图片文件,子框架页面的加载。

    jQuery中的ready方法就是基于这个事件编写的,查看ready源码

    现在我自己来解答一下自己的疑惑:

    1)通过修改initial-scale的值,然后配合rem来做换算,可以参考flexible.js中计算方式。

    2)这几种布局应该是配合使用,因地制宜,我前面的一篇文章中就记录使用了多列与伸缩盒的使用场景。

    3)还是一样,要结合媒体查询与rem或px,因地制宜。

    4)这个范围,目前我还不能确定,还没有做过详细的实验。

    参考资料:

    移动端前端开发中需要知道的一些屏幕知识
    设计师DPI指南(翻译)
    理解flexible.js所需的viewport知识
    使用Flexible实现手淘H5页面的终端适配
    dp、sp、px傻傻分不清楚
    移动端H5页面高清多屏适配方案
    A tale of two viewports
    关于移动端 rem 布局的一些总结
    移动端页面布局及字体大小该如何设置
    web app变革之rem
    苹果的 Retina 屏幕一定需要突破 326 PPI 吗?还是跟距离相关?
    Retina显示屏
    (翻译)第三种viewport-ideal viewport

    关于meta viewport中target-densitydpi属性

  • 相关阅读:
    Maximum Depth of Binary Tree
    Single Number
    Merge Two Sorted Lists
    Remove Nth Node From End of List
    Remove Element
    Remove Duplicates from Sorted List
    Add Two Numbers
    编译视频直播点播平台EasyDSS数据排序使用Go 语言 slice 类型排序的实现介绍
    RTMP协议视频直播点播平台EasyDSS在Linux系统中以服务启动报错can’t evaluate field RootPath in type*struct排查
    【解决方案】5G时代RTMP推流服务器/互联网直播点播平台EasyDSS实现360°全景摄像机VR直播
  • 原文地址:https://www.cnblogs.com/strick/p/5197521.html
Copyright © 2011-2022 走看看