你必须要知道的移动端开发知识

移动开发不同与PC端开发,可能会经历各种意想不到的问题,尤其是移动端应用刚起步的几年;随着移动互联网的快速发展,有些问题已经得到了很好的支持,如1像素边界的问题。当然,要更好地解决这些移动端的问题,就需有移动端领域相关的知识,下面就来说说。

dpr设备像素比

首先说一下,这个dpr不仅仅是移动端才有的,pc端也有,但是对一些移动端的问题产生的原因及解决显得比较重要,比如1像素的问题。先来看几个概念:

  1. 物理像素(physical pixel)

    一个物理像素就是显示设备上最小的物理显示单元,每个物理像素都有自己的颜色值和亮度值。例如iphone6手机屏幕有750*1334个物理像素

  2. 设备独立像素(density-independent

    设备独立像素又叫密度无关像素,也可以叫逻辑像素,程序使用的虚拟像素如css像素,可以理解为显示设备坐标系统中的一个点;

  3. 设备像素比dpr(device pixel ratio)

    设备像素比,简称dpr,定义了物理像素与设备独立像素之间的对应关系,具体的对应关系是一个计算公式如下:

    dpr = 物理像素 / 设备独立像素

    上面计算的dpr是指某一个方向上如x或者y方向,二者dpr值相同;程序中获取dpr方式如下:

    • js获取dpr使用window.devicePixelRatio

    • css获取dpr使用-webkit-device-pixel-radio

    例如iphone6,设备宽高375 * 667,可以理解为设备独立像素(也即css像素);其dpr为2,那么对应的物理像素宽高均 * 2,即 750 * 1334;也就是说一个逻辑像素,在x轴和y轴都需要2个物理像素来显示,一图胜千言,如图:
    你必须要知道的移动端开发知识
    由上线描述可以知道,css中的1px并不等于显示设备的物理1px,这就导致移动开发中设计师设计的是1px的物理像素,而转换为css的值为1/dpr,其可能为小数值,这在低版本android(<=4.4)和ios(<=8)中被会系统自动转换为0,这就是移动端常见的1px像素的问题;下面会给出具体的解决方案。

viewport

做过移动端开发的同学可能对下面html中的meta标签比较熟悉:

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

这个是用来控制移动端viewport区域是怎么展现的,很有必要对其理解。

viewport理解

在PC的浏览器中,viewport其实就是浏览器可视区域,但是在移动设备上问题就比较复杂,viewport并不局限于浏览器可视区域大小,可能比浏览器可视区域要大,也可能比浏览器可视区域要小;但是因为移动端屏幕相较pc端太窄,为在移动端正常显示为PC端设计的网站,默认情况下移动端设备上的viewport都是要大于浏览器可视区域的,一般值为980px也可能有其他值,根据不同设备来定;因为viewport比浏览器可视区域大,那么浏览器就会出现横向滚动条。

需要注意两点:

  1. 页面的html标签的宽度就是相对于viewport的大小。

  2. PC端viewport就是浏览器可视区域大小;移动端默认viewport值为980px,也可以根据meta标签自定义设置。

有关viewport理论,国外有一个人ppk对此有做过比较深入的研究,具体可以参考其写的三篇文章 。其将viewport分成三个层面来理解:

  • layout viewport

    布局窗口,网页真正的布局视口,它的宽度可以大于也可以小于浏览器可视区域的宽度,对于大于浏览器可视区域(比如默认980px的viewport或者自定义设置viewport)的viewport,只能通过滚动浏览器滚动条来展现其内容。

  • visual viewport

    可视窗口,移动设备浏览器可视区域的大小,其宽度并一定为移动显示设备的屏幕宽度,在initial-scale缩放为1的情况下才相等。

  • ideal viewport

    理想化的窗口,它没有一个固定的尺寸,其宽度为移动设备屏幕宽度,它是最适合移动设备的viewport。设置理想化的窗口的网页不论何种分辨率的屏幕下,其用户不需要缩放和横向滚动条就能正常查看网站所有内容,保证网页的文字、图片等等其大小完美的呈现给用户。

一图胜千言,借用网上的几张图来说明具体的区别:

你必须要知道的移动端开发知识

你必须要知道的移动端开发知识

你必须要知道的移动端开发知识你必须要知道的移动端开发知识

上面两幅图很好理解,下面两幅对比图,说明ideal viewport对于网站用户体验的重要性,用户不用缩放或者滚动就能达到极佳的体验效果。

用meta标签控制viewport

<meta name="viewport" content="xxx"> 标签来控制viewport大小首先是由苹果公司在其safari浏览器中引入的,目的就是解决移动设备的viewport问题。后来安卓以及各大浏览器厂商也都纷纷效仿,引入它对viewport的支持。

viewport是通过meta标签来控制页面的viewport大小,具体形式如下:

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

其中width=device-width顾名思义是设置viewport的宽为设备的宽,其还可以设置具体的逻辑像素值,如980。

meta标签的viewport相关配置content值有6个属性,它们可以同时使用,也可单独使用,还可以混合使用,具体如下:

width 设置layout viewport的宽度,为一正整数,也可为device-width
height 设置layout viewport的宽度,为一正整数,很少使用
initial-scale 设置页面的初始缩放值,为一数值,可带小数
minimum-scale 设置页面的最小缩放值,为一数值,可带小数
maximum-scale 设置页面的最大缩放值,为一数值,可带小数
user-scalable 页面是否允许缩放,值为"no"或"yes", no 不允许,yes允许

需要注意两点:

  1. user-scalable=no禁止缩放在ios>=10系统的safari下有兼容问题,具体可以看禁止页面缩放meta标签兼容性问题这部分

  2. meta属性中initial-scale的缩放是相对于ideal viewport来进行的

这样通过meta标签很容易设置页面layout viewport为移动设备屏幕宽度(它也是ideal layout)即:

<meta name="viewport" content="width=device-width">

上面这种方式是最直接也是最轻易想到的设法,但是还可以使用initial-scale来达到同样的效果,即:

<meta name="viewport" content="initial-scale=1">

简单解释下,initial-scale表示页面初始缩放值,其缩放是相对于ideal viewport的来说的,为1表示不缩放,那么其layout viewport的宽度就是ideal viewport的宽度,也就是移动设备屏幕的宽度。

既然二者都可以设置layout viewport的宽度,那么二者同时设置且值不相等会怎样呢?答案是:

浏览器会以二者中值较大的那个为准

可能读者会有新的疑惑,在设置layout viewport为屏幕宽度时,经常看到的是二者都写上,为什么呢?答案是:

一个是兼容性的考虑,另一方面解决某些设备横竖屏不分导致通通以ideal viewport的宽度为准的问题

viewport的缩放

上面提到,meta中的initial-scale是相对于ideal viewport进行缩放的,该属性的作用:

initial-scale用来确定visual viewport即浏览器可视区域宽度大小

阿里早期的iphone/ipad下的自适应布局解决方案flexible就是利用initial-scale来解决的。

有人可能会有疑问,移动端浏览器可视区域宽度不就是移动设备屏幕的宽度么?其实我们这里所说的可视区域宽度是逻辑意义上的宽度,而非实际真实的宽度,例如iphone4的320px屏幕宽度,initial-scale放大2倍,那么可视区域的逻辑大小变成了160px,可以通过查看页面html元素的宽度测试。

其实visual viewport与ideal viewport的关系如下:

visual viewport = ideal viewport / initial-scale

在iphone/ipad下,在没有指定initial-scale的情况下,无论你怎么设置layout viewport宽度,它会根据上面的计算关系,自动计算当前页面的inital-scale值以保证layout viewport宽度在缩放后就是浏览器可视区域的的宽度,即inital-scale = ideal viewport宽度 / visual viewport宽度(等于layout viewport宽度)

例如,iphone6情况下默认的layout viewport为980px,那么当前缩放值inital-scale=375 / 980。

需要说明一下的是:在设置了initial-scale的情况下,这个自动计算的值就不起作用了。

移动适配方案

移动端设备不同,其屏幕大小也不尽相同,那么针对特定移动设备的页面设计ui怎么在不同移动设备上因设备不同而自适应屏幕展示呢。一般常见的解决方案有rem和vw/vh。下面就来说说。

rem适配

rem是一个相对单位,相对于<html>font-size来说的;那么参与页面布局的单位使用rem而不是px,这样我们只需控制不同设备下网页<html>font-size即可做到页面的自适应。

可以像flexible一样将移动屏幕宽度100等份,每份为a,同时1rem认定为10a;例如750px的设计稿来说,这样的话:

1a = 750px / 100 = 7.5px
1rem = 10a = 75px

实现代码如下:

(function (doc, win) {
var docEl =doc.documentElement,
    resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
    recalc = function () {
      var width = docEl.getBoundingClientRect().width;
      if (!width) return;
      docEl.style.fontSize = (width / 100)*10 + 'px';
    };

  if (!doc.addEventListener) return;
  win.addEventListener(resizeEvt, recalc, false);
  recalc();
  // hack兼容某些特殊机型
  doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);

另外,我们也可以基于特定的设计稿尺寸来计算其他移动设备下的font-size,例如基于750px的设计稿,我们假设其font-size为40px,那么其他设备下font-size的计算关系式:

\[ 750/40 = 设备屏幕宽/fontsize \]

对应的核心转换代码即: docEl.style.fontSize = (width / 750)*40 + ‘px‘

VW适配

首先要知道vw和vh的概念代表什么,它也是相对单位,相对于屏幕的宽高而言的。

100vw = ideal viewport 宽

100vh = ideal viewport 高

跟rem类似,我们可以使用vw单位作为css的唯一单位,这样所有元素基于vw来布局;基于特定设计稿的尺寸来转换vw单位,我们使用stylus预编译函数来进行转换:

$vw_base = 375
vw(px) {
   (px/$vw_base) * 100vw
}

然后无论是文本还是布局高宽、间距等都使用 vw 作为 CSS 单位,如

.container
  padding: vw(15) vw(10) vw(10)
  height: vw(40)

vw+rem结合适配

单纯的vw适配在视口缩放时尤其是缩小时有些小瑕疵,因为vw是利用视口单位实现的布局,依赖视口的大小而自动缩放,也就失去了最大最小的宽度限制。一种比较好的解决方法是使用vw与rem配合来进行适配,即:

页面需要适配的元素使用rem为单位,而

的font-size值是根据vw来设定的,但是该font-size值需要限制最大最小值。

具体的stylus代码如下,也可以参考这个demo猛戳

$fontsize = 75 // 将屏幕分成10份
$base = 750 // iphone6 750作为基数
rem(px) {
    (px/$fontsize) * 1rem
}

html 
  font-size: ($fontsize / $base) * 100vw //font-size以vw为单位
  // 通过medai query限制根元素的最大值最小值
  @media screen and (max-width: 320px) {// 页面宽度<=320时生效
      font-size: 64px
  }
  @medai screen and (min-width: 540px) { // 页面宽度>=540时生效
      font-size: 108px
  }

媒体查询media query适配

通过类似如下形式来实现适配:

/* 大于1200px */
@media screen and (min-width:1200px){}
/* 大于等于960px,小于1200px */
@media screen and (min-width: 960px) and (max-width: 1199px){}
/* 大于等于768px,小于960px */
@media screen and (min-width: 768px) and (max-width: 959px){}
/* 大于等于480px,小于768px */
@media only screen and (min-width: 480px) and (max-width: 767px){}
/* 小于479px */
@media only screen and (max-width: 479px){}

该方式比较简单,成本低,但是代码量大,比较臃肿,维护不方便,不推荐该方式。

阿里flexible适配

该方案随着viewport单位 得到众多浏览器的兼容支持已逐渐不推荐使用了,它主要是为了解决iphone系列适配问题;虽然官方已不推荐使用,但是其思想还是值得借鉴学习的,主要表现下面三个方面:

  • 根据dpr的值来修改<meta>viewport的值实现移动端1px的问题

  • 根据dpr的值来修改<html>font-size值,使用以rem为单位值来等比例缩放

  • 使用hack手段用rem模拟vw特性

对应第一点,它通过hack手段来根据设备的dpr值相应改变<meta>标签中的viewport的值:

<!-- dpr = 1-->
<meta name="viewport" content="initial-scale=scale,maximum-scale=scale,minimum-scale=scale,user-scalable=no">
 <!-- dpr = 2-->
<meta name="viewport" content="initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">
 <!-- dpr = 3-->
<meta name="viewport" content="initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333,user-scalable=no">

这样,iphone系统的不同设备下页面达到的效果是使css 1px像素与物理1px像素相同;然后,flexible使用rem作为布局单位实现适应布局。关键基本代码如下:

// 设置meta的viewport内容进行缩放
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;

// 确定html的font-size值
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
????width = 540 * dpr;
}
var rem = width / 10; // 屏幕均分100份,每份为a,1rem为10a
docEl.style.fontSize = rem + 'px';

1px边框问题及解决方案

产生1px边框的问题其实归结三点:

  • 1px的css逻辑像素不等于1px的物理像素

  • ios<8以及Android<=4.4以下的浏览器处理0.5px时会转化为0

对于1px边框问题的解决,flexible能完美的解决,思路见上面分析的;除此之外还有什么方式,其实网上有很多方法,但是还是列一下思路:

  1. 用border-image来实现

    使用一张图片来充当border,图片形式是3x3,如下:

    你必须要知道的移动端开发知识

    .border {
        border-width: 1px;
        border-image: url(border.png) 2 repeat;
    }

    缺点:改边框颜色时要修改图片,不灵活

  2. 用背景渐变来实现

    设置1px的渐变背景,50%有颜色,50%透明

    .border {
        background-image: linear-gradient(180deg, green, green 50%, transparent 0);
      background-size: 100% 1px; /* 背景宽度100%,高度1px */
      background-repeat: no-repeat;
      background-position: bottom;
    }

    缺点:维护过多代码,圆角没法实现

  3. 用box-shadow模拟边框来实现

    .border {
        border: none;
        height: 100px;
        width: 100%;
        box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5);
    }

    缺点:颜色不好处理,有阴影出现

  4. 用伪类+transform来实现

    比较推荐的方法,原理是把原先元素的border去掉,然后利用:before或者:after重做border,并把transformscale缩小一半,原先元素相对定位,伪元素模拟的border采用绝对定位。

    .border {
        position: relative;
        border: none;
    }
    .border:after {
        content: ' ';
        position: absolute;
        left: 0;
        background: #666;
        width: 100%;
        height: 1px;
        tranform: scaleY(0.5);
        transform-origin: 0 0;
    }

参考文献

你必须要知道的移动端开发知识

上一篇:org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext之解决办法


下一篇:在Azure DevOps Server (TFS)的流水线中编译和测试Xcode移动应用(iPhone)