移动端上的设计和适配

如果看完了还不过瘾?想和作者深聊相关话题,可以点击这里向作者提问!

特别声明: 文章中图片源于互联网,有些方案也源于互联网,如有觉得侵权,可以告之在下,我会删除!谢谢!

面对于不同移动设备,特别是针对于屏幕高度的设计和适配,一直是困扰着视觉和前端。特别是设计师和前端开发人员之间的协调,沟通的成本也随着增加。那么这篇文档,主要是用于移动端设计和适配(针对屏幕高度方面),希望通过文档的形式能减少设计师和开发人员之间的一些沟通成本,同时让前端开发人员用最低的时间成本来完成终端设备的UI适配工作。

了解一些概念

不管是设计师或者开发人员,对于一些概念都应该要有一定的了解,这样相互沟通的时候就不会因为一些术语感到困惑。接下来简单的了解一些概念:

什么是DPI、PPI?

DPI(Dots Per Inch)是测量空间点密度的单位,最初应用于打印技术中,它表示每英寸能打印上的墨滴数量。较小的DPI会产生不清晰的图片。

后来DPI的概念也被应用到了计算机屏幕上,计算机屏幕一般采用PPI(Pixels Per Inch)来表示一英寸屏幕上显示的像素点的数量,现在DPI也被引入。

个应用实例: 27寸Mac影院显示屏的PPI是109,这表示在每英寸的屏幕上显示了109个像素点。斜角长是25.7英寸(65cm),实际屏幕的宽度大概是23.5英寸,23.5109约等于2560,因此原始屏幕分辨率就是2560 x 1440px

屏幕分辨率(原始分辨率)

屏幕分辨率对用户如何理解设计有很大的影响。幸运的是,自从LCD显示器代替了CRT,现在的用户更趋向于使用原始分辨率,它保证了好的屏幕尺寸或者说PPI比例。

分辨率定义了屏幕上显示的像素数量(比如:27寸的显示器分辨率是2560 x 1440px2560px是宽,1440px是高)。在了解了PPI之后,我们就知道它不是一个测量物理大小的单位。你可以有一个2560 x 1440屏幕,它能跟墙一般大,也可以跟脑袋一般小。

一个27寸的影院显示屏,原始分辨率为2560 x 1440px,PPI为109。如果减小分辨率,元素将会显示得更大,因为有23.5英寸的水平宽度需要数量远远不够的像素点来填满。

如例子所示,屏幕的原始分辨率是2560 x 1440px。如果分辨率减小,像素点还是被展示在PPI为109的屏幕上。你的操作系统会自动拉伸所有元素来填补间隙,使得整个屏幕被填满。GPU/CPU会捕获所有像素点并且使用新的比例重新计算他们。

如果想要设置27寸屏幕分辨率为1280 x 720(之前宽的一半,高的一半),GPU会让一个像素点变成原来的2倍大来填充屏幕,那么结果就是会变得模糊。在分辨率为原来一半的时候,因为有简单分频器的存在可能看着还算可以。但是如果使用原来的1/3或者3/4,最终会以小数点结束,就不能等分一个像素点了。我们来看下面的例子:

思考后面的例子:在原始分辨率的屏幕上画一条1px的线,然后设置分辨率为50%。为了填满屏幕,CPU需要制造150%的视觉效果,所有像素点都要乘以1.51 x 1.5 = 1.5,但是因为不能有半个像素点,这就使得填充周围的像素点的颜色只有一部分,便产生了模糊。

注意,我们通常所说的显示器分辨率,其实是指桌面设定的分辨率,而不是显示器的物理分辨率。只不过现在液晶显示器成为主流,由于液晶的显示原理与CRT不同,只有在桌面分辨率与物理分辨率一致的情况下,显示效果最佳,所以现在我们的桌面分辨率几乎总是与显示器的物理分辨率一致了。

什么是视网膜显示屏?

“Retina(视网膜)显示屏”是Apple公司在发布iPhone 4时引入的。之所以叫做Retina是因为设备的PPI非常高以至于人的视网膜也不能在屏幕上分辨出像素点来。

这个说法在现在的设备的屏幕范围内是正确的,但是随着屏幕越来越好,我们的眼睛也会被训练得足够感知像素点,特别是圆形的UI元素。

从技术的角度来讲,他们做的就是在完全相同的物理大小上展示比原来高和宽多一倍的像素点。

iPhone 3G/S是3.5英寸的斜角,分辨率为480 x 320px,PPI为163。 iPhone 4/S是3.5英寸的斜角,分辨率为960 x 640px,PPI为326

事实证明正好是两倍的关系,同样的物理大小,屏幕上的元素却有两倍的清晰度,因为他们有两倍的像素点。1个标准的像素=4个Retina像素,像素的四倍。

思考下面的例子,在复杂设计中如何直接应用:

什么是像素比?

当你的设计需要在不同PPI下转换时,像素比就是你的救星。当你知道像素比后,就不需要再考虑设备的详细规格了。

以iPhone 3G和4为例,相同物理大小上iPhone4的像素点是3G两倍,因此像素比就是2,这表示只需要用你的资源乘以2,就可以兼容4G的分辨率了。

让我们先创建一个44 x 44px的iOS上被推荐的touch按钮(我后面会介绍),定义为典型按钮“Jim”。 为了让Jim在iPhone 4上看起来更好,需要创建一个它两倍大小的版本。下面就是我们做的。

什么是DP、PT、SP?

设备像素(物理像素),顾名思义,显示屏是由一个个物理像素点组成的,通过控制每个像素点的颜色,使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了,单位pt

DP或PT是测量单位,你可以用来规范你的各种设备和多DPI的app模型。 DP(Dip)表示独立于设备的像素点,PT表示点。DP用在Android上,PT用在Apple上,但是他们本质上是相同的。

简而言之,它能定义独立于设备的像素比的大小,这会包含在不同角色(如设计师和工程师)之间的讨论规则中。

继续说前面“Jim”按钮的例子。 Jim在标准的非Retina屏幕上宽度为44px,在Retina屏幕上是88px。从技术上给Jim添加20pxpadding,在Retina上padding40px。但是,当你基于非Retina屏幕设计时计算Retina的像素值并没什么意义。

因此我们需要做的就是以标准的100%非Retina比例作为一切设计的基础。

在这种情况下,Jim的大小就是44 x 44DP(PT),padding20DP(PT)。你可以在任何PPI上执行你的规范,Jim仍然是44 x 44dp/pt

Android和iOS会调整自身大小适应屏幕并且使用正确的像素比来进行换算,这就是为什么我发现使用屏幕的原始的PPI设计会更简单。

物理像素(physical pixel)

一个物理像素是显示器(手机屏幕)上最小的物理显示单元,在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。

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

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

所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比。

设备像素比(device pixel ratio )

设备像素比(简称dpr)定义了物理像素和设备独立像素的对应关系,它的值可以按如下的公式的得到:

设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向

设备像素比(dpr) 是指在移动开发中1个CSS像素占用多少设备像素,如2代表1个CSS像素用2 x 2个设备像素来绘制。

设备像素比(dpr),公式为1px = (dpr)^2 * 1dp,可以理解为1px由多少个设备像素组成;

位图像素

一个位图像素是栅格图像(如:png, jpg, gif等)最小的数据单元。每一个位图像素都包含着一些自身的显示信息(如:显示位置,颜色值,透明度等)。

CSS像素

虚拟像素,可以理解为“直觉”像素,CSS和JavaScript使用的抽象单位,浏览器内的一切长度都是以CSS像素为单位的,CSS像素的单位是px

在CSS规范中,长度单位可以分为两类,绝对(absolute)单位以及相对(relative)单位。px是一个相对单位,相对的是设备像素(Device Pixel)。

在同样一个设备上,每1个CSS像素所代表的物理像素是可以变化的(即CSS像素的第一方面的相对性); 在不同的设备之间,每1个CSS像素所代表的物理像素是可以变化的(即CSS像素的第二方面的相对性)。

根据 维基百科的解释:

它是图像显示的基本单元,既不是一个确定的物理量,也不是一个点或者小方块,而是一个抽象概念。所以在谈论像素时一定要清楚它的上下文!一定要清楚它的上下文!一定要清楚它的上下文!

不同的设备,图像基本采样单元是不同的,显示器上的物理像素等于显示器的点距,而打印机的物理像素等于打印机的墨点。而衡量点距大小和打印机墨点大小的单位分别称为PPI和DPI。

由于不同的物理设备的物理像素的大小是不一样的,所以CSS认为浏览器应该对CSS中的像素进行调节,使得浏览器中 1个CSS像素的大小在不同物理设备上看上去大小总是差不多 ,目的是为了保证阅读体验一致。为了达到这一点浏览器可以直接按照设备的物理像素大小进行换算,而CSS规范中使用"参考像素"来进行换算。

1个参考像素即为从一臂之遥看解析度为96DPI的设备输出(即1英寸96点)时,1点(即1/96英寸)的视角。它并不是1/96英寸长度,而是从一臂之遥的距离处看解析度为96DPI的设备输出一单位(即1/96英寸)时视线与水平线的夹角。通常认为常人臂长为28英寸,所以它的视角是:

(1/96)in / (28in * 2 * PI / 360deg) = 0.0213度。

由于CSS像素是一个视角单位,所以在真正实现时,为了方便基本都是根据设备像素换算的。浏览器根据硬件设备能够直接获取CSS像素。

简单介绍一下一臂之遥:我们在使用不同设备输出时,眼睛与设备输出的典型距离是不同的。比如电脑显示器,通常是一臂之距,而看书和纸张时(对应于打印机的设备输出),则通常会更近一些。看电视时则会更远,比如一般建议是电视机屏幕对角线的2.53倍长 —— 如果你是个42'彩电,那就差不多是3米远。

Viewport

@ppk把移动设备上的viewport分为 Layout viewportVisual viewportIdeal viewport 三类

其中的Ideal viewport是最适合移动设备的viewport,Ideal viewport的宽度等于移动设备的屏幕宽度,只要在CSS中把某一元素的宽度设为Ideal viewport的宽度(单位用px),那么这个元素的宽度就是设备屏幕的宽度了,也就是宽度为100%的效果。Ideal viewport 的意义在于,Ideal viewport并没有一个固定的尺寸,不同的设备拥有有不同的Ideal viewport。无论在何种分辨率的屏幕下,那些针对Ideal viewport而设计的网站,不需要用户手动缩放,也不需要出现横向滚动条,都可以完美的呈现给用户。

设计中px、pt、PPI、DPI、dp、sp之间的关系

通过前面的对概念的了解,简单归纳一下:

  • px:Pixel,像素,电子屏幕上组成一幅图画或照片的最基本单元
  • pt:Point,点,印刷行业常用单位,等于1/72英寸
  • PPI:Pixel per inch,每英寸像素数,该值越高,则屏幕越细腻
  • DPI:Dot per inch,每英寸多少点,该值越高,则图片越细腻
  • dp:Dip,Density-independent pixel, 是安卓开发用的长度单位,1dp表示在屏幕像素点密度为160ppi1px长度
  • sp:Scale-independent pixel,安卓开发用的字体大小单位

接下来看看他们之间存在的关系。

pt和px

1pt = (DPI / 72) px

当Photoshop中新建画布的分辨率为72ppi( 即 72dpi时 ), 1pt=1px; 当新建画布分辨率为72 x 2 = 144ppi时,1pt=2px

PPI和DPI

dpi = ppi

DPI最初用于衡量打印物上每英寸的点数密度。DPI值越小图片越不精细。当DPI的概念用在计算机屏幕上时,就应称之为PPI。同理: PPI就是计算机屏幕上每英寸可以显示的像素点的数量。因此,在电子屏幕显示中提到的PPI和DPI是一样的。

PPI计算方法

PPI是指屏幕上的像素密度,其计算方法为:

ppi= 屏幕对角线上的像素点数 / 对角线长度 = √ (屏幕横向像素点^2 + 屏幕纵向像素点^2) / 对角线长度

px和dp

1dp = (屏幕ppi / 160)px

dp为安卓开发时的长度单位,根据不同的屏幕分辨率,与px有不同的对应关系。

安卓端屏幕大小各不相同,根据其像素密度,分为以下几种规格:

密度 MDPI HDPI XXHDPI XXXHDPI
密度值 160 240 320 480
分辨率 320 x 480 480 x 800 720 x 1280 1080 x 1920

1dp定义为屏幕密度值为160ppi时的1px,即,在mdpi时,1dp = 1px。 以MDPI为标准,这些屏幕的密度值比为:1dpi : mdpi : hdpi : xhdpi : xxhdpi = 0.75 : 1 : 1.5 : 2 : 3;即,在XHDPI的密度下,1dp = 2px;在HDPI情况下,1dp = 1.5px。其他类推。

dp和sp

dpsp都是安卓的开发单位,dp是长度单位,sp是字体单位。spdp类似,但是可以根据用户的字体大小首选项进行缩放。Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等)。

设备安全区域

由于移动终端设备种类繁多,所以不管是设计师还是前端工程师都需要面对众多屏幕的适配处理。在screensiz.es整理了市面上众多设备的尺寸参数。

iOS 11 为屏幕适配引入了一个十分重要的概念:Safe Area。安全区域,一个熟悉又陌生的词语。

熟悉是因为在平面设计中,由于印刷裁切过程中的误差,设计师需要给设计稿预留出「出血」 位置,确保设计内容在安全区域中;陌生又是因为在互联网设计中已极少被提及。

这里指的安全区域不仅仅针对于iOS的设备,只不过以iOS设备为例。所以这里所指的设备安全区域指的是屏幕内适合放置控件的安全区域

在没有状态栏和其他东西的 iPhone 8 里,Safe Area 是指整个屏幕。

当加入状态栏后,Safe Area 便向下减少了 20pt。当我们加入 Navigation 的时候,Safe Area 又减少了 44pt。同理,我们再加入 Tabbar 的时候,Safe Area 又减少了 44pt(PS:此处更正, Tabbar 高度应该是 49pt)。

在 iPhone X 里,当我们没有使用状态栏时,Safe Area 依然和上下边有一定的距离。按照我的测量,此时距离底部应该是 43pt,距离顶部应该是 44pt

同理,加入不同 Bar 之后,iPhone X 的 Safe Area 都会有相应的变化。

遵守 Safe Area (安全区域) 的界定

拿苹果官网针对于iPhone X的安全区域来举例。

状态栏。遵守安全区域的界定,在状态栏下面留出适当的空间。避免为状态栏高度预设值,这可能会导致您的内容被状态栏遮挡或形成错位。

圆弧展示角和传感器槽。您的 App 的内容元素和控制按键应避开屏幕角落和传感器槽,让其在填满屏幕的同时不被角落切割。

主屏幕指示器。为使 App 的内容和控件始终保持清晰可见且便于点按,请确保您的 App 不会干扰主屏幕指示器。

屏幕边缘手势。iPhone X 显示屏利用屏幕边缘手势来访问主屏幕,App 切换器,通知和控制中心。请避免对这些手势造成干扰。您可将控件移到安全区域并调整用户界面。极少数情况下,您可以考虑使用边缘保护:用户的首个滑动手势将被视为 App 内的特定手势,第二次滑动才会被视为系统手势。

确保您的代码能适应不同的屏幕宽高比。许多 App 会根据特定的宽度,高度或宽高比来定位其内容。请检查您的内容是否已正确缩放并定位。

调整视频的缩放度。iPhone X 上的视频内容应填满屏幕。但是,如果这导致顶部或底部被切割,或侧面裁剪太多,则应将视频拉伸或缩小以配合屏幕。当 AVPlayerViewController 自动管理时,基于AVPlayerLayer 的自定义视频播放器需要选择适当的初始视频重力设置,并允许用户根据自己的喜好在 aspect (固定宽高比) 和 aspectFill (固定宽高比且全屏) 观看模式之间进行切换。

安全区域布局

设计布局要填充整个屏幕,特别是在iPhone X设计应当填充整个屏幕:

这样设计也更能符合苹果官方所提的设计理念。设计布局要填充整个屏幕,这里有两块区域需要额外考虑:

屏幕顶部,即StatusBar部分

这条状态栏本来并没有可发挥的空间,但是iPhone的StatusBar与NavigationBar(以下简称NavBar)背景是可以通栏的,以达到一种完全沉浸式体验的设计。

大部分的APP应该也是没有影响的(主流NavBar都采用纯色背景,StatusBar背景沿用NavBar的背景),但是对于那些做了NavBar视觉效果的设计师就要考虑了,你的渐变色背景、或者带底纹的背景、还包括电商平台商品图是通栏展示的商品图,多少会对实际效果产生一些影响。

比如,NavigationBar是渐变色背景的,由于iPhoneX的Status+Nav高度增加,我们1242 x 192(@3X)的背景图会被等比例拉伸至这两块区域并且剪辑多余部分。

屏幕底部

针对于iPhoneX设备,屏幕底部的虚拟区,替代了Home键,高度为34pt

指示灯区域是一个带着系统功能的内容显示区域,这就意味着它可以展示内容;同时如果你的底部是TabBar,那么指示灯区域背景会来自于TabBar背景的延伸;如果我们是一个feed流的页面,底部则会展示次屏feed流的局部。

鉴于圆角、传感器、指示灯区域的影响,iPhone X给出了设计布局的安全区意见:

再考虑必要的NavBar、TabBar,主题内容显示的安全区需要根据设计需求进行考虑。根据实际需要,我们添加的所有控件都应当在安全区内,如各类型的Button、Edit Menu、Pickers、Sliders等等。

布局适配

对于布局,常见的主要有:

顶部通栏

针对 iPhone X 新的安全区域,特别像具备通顶效果的页面可能面临到的内容被遮挡。我们可能会首先就想到一个解决方案——通过添加一个适配条,把页面内容挪一个位置,甚至我们可以把适配条定义为我们的产品品牌色,这样似乎也会满足用户的心理诉求。

这种简单粗暴的方法并不符合 Apple 想要传达的全面屏设计理念,官方也提出了不推荐了以这样的方式来实现适配。

因为 iPhone X 的安全区域,页面内容会被限制在安全区域内,横屏情况下更为明显,在安全区域外都是白色。

Apple 也意识到了这种尴尬的显示方式,所以引入了一个 meta 标签的 viewpoint 扩展属性——viewpoint-fit,在 iOS 11 中 viewpoint-fit 也官方添加到 CSS Round Display 规范中了。

有关于这部分稍后在前端处理适配一节中会详细概述。

普通 iPhone 有 128px 的工具栏区域,而 iPhone X 有 176px 的工具栏区域,以及 Android 有 136px 的工具栏区域。 建议设计师在设计通顶效果页面时,顶部最少预留 136px 的纯色或者无主要内容区域

底部虚拟指示条

Apple 的设计规范中提到,如果页面为一个长内容可滚动的页面,那么我们可以放心地把页面内容铺开整个屏幕。会发现页面内容可能会跟虚拟指示条重叠在一起,但没关系,Apple 自身的页面也是如此,只需在页面就底部留好安全的空白设置就可以了。

但很多时候,有吸底 Bar 的页面,可以参考 iPhone X 自身的带有底部导航栏的 App 效果,发现吸底 Bar 实际一样是始终悬浮固定在屏幕底部,同时为虚拟指示条按设计规范留足了空间。

全屏模式

对于全屏图,我们会有很多场景会用到,比如闪屏、整屏轮播 H5 等。而在不同尺寸比例的手机屏幕上显示,全屏图被裁切是不可避免,对它的适配,我们的目标是保证图片主体内容的完整显示、图片信息的有效传达。

所以,也有了两种适配方式:基于宽度适配,或基于高度适配。

全屏的设计主要是避免主题素材上边缘切边,通过基于高度进行适配,视觉稿输出背景宽度 860px,但主体内容安全区域限定在 750px 以内。拿QQ音乐的**百变播放器 **为例:

在设计的时候,就需要考虑组件元素与其他元素之间的分离:

Web页面横向适配

对于Web布局一直是行内众多同学在探讨和研究的一个技术点。时至今日,Web的布局已经经历过下图这样的一个过程:

但对于Web页面的适配,一直以来大家探讨的都是Web页面以横向的比例来进行适配。到目前为止,业内使用到的方案大多数是手淘的Flexible布局方案。当然随着Viewport单位更趋向于稳定性,开始有团队在使用vw来对页面进行横向布局。除此之外,也有同学使用vwrem相结合的适配布局。有关于Web页面横向的适配布局相关的探索,建议阅读:

Web页面的纵向适配

到今天为止,设备终端的屏幕已经是多样化,常见的有PC机显示器、iPad、Mobile,而在Mobile设备上屏幕又有很多种,这些设备除了width尺寸不同之外,还有height尺寸也不同。那么问题来了,对面设计一个页面,如何考虑多种纵向高度适配是一个值得探讨和解决的问题。对于前端开发人员而言,也将面临一个这样的问题。特别是对于互动团队而言,很多时候的页面都是一个全屏的页面。那么怎么来解决纵向的适配呢?就我自己而言,我一直还在探讨,时至今日我还没有找到一个较为满意的解决方案。如果您在这方面有相关经验,欢迎在下面的评论中与我一起分享。

1px的解决方案

对于1px而言,是一个不可避免的现象,业内对于这方面的探计已经有很多经验,也沉淀了很多技术方案,不管哪一种方案都有自己的优劣,对于Web开发人员,应该根据自己的业务场景选择最为适合的方案。有关于1px的相关解决方案,请移步阅读《再谈Retina下1px的解决方案》一文。

iPhone X前端适配处理

接下来主要是前端在代码层面怎么去处理iPhone X的适配问题。

第一步: 添加meta标签

首先要做的是你的HTML文件中</head>标签前添加下面的代码:

<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> 

特别注意: 上面的代码非常的熟悉,只是末尾添加了viewport-fit="cover"

由于iPhone X的出现,有安全区域一说:

iOS11与早期的版本有个不同的地方,Webview内容将会尊重所谓的安全区域。这意味着,如果你有一个标题栏固定在顶部(position:fixed;top:0)。它将会在屏幕顶部下面的20px开始渲染。当你向下滚动时,它会移动到状态栏的后面。当你向上滚动时,它会再次下降到状态栏下面(在20px的间隙中,内容会透出,这是一个很尴尬的间隙。无法让人接受)。

在iPhone X下,页面全屏时,默认的viewport区域是不包括刘海(Notch)的,如下图就是<body>区域的大小:

并且页面下滑后,刘海区域是透明的,会透出页面:

通过修改meta标签中的viewport-fit,可以让iOS 11默认的viewport。对于viewport-fit来说,苹果已经把它增加到了CSS Round Display规范中

通过viewport-fit可以设置可视视窗的大小,也就是可以控制剪切区域。viewport-fit接受三个值:

  • auto:这个值不影响初始布局视窗,整个Web页面是可视的。在视窗之外的UA绘制的是未定义的,它可能是画布的背景色,或者是UA认为合适的其他东西
  • contain:最初的布局视窗和视觉布局视窗被设置为最大的矩形。在Viewport之外的UA绘制的是未定义的,它可能是画布的背景色,或者UA认为合适的其他东西
  • cover:初始布局视窗和视觉布局视窗被设置为设备物理屏幕的限定矩形

第二步:页面内容限定在安全区域内

通过修改viewportcover,可以让Viewport全屏。但在iPhone X会出现内容被刘海遮挡:

现在iPhone X的形状不规则,其状态栏的高不再是20px,而且在摄像头和扬声器的设置下,你的标题栏内容将会完全无法访问到。需要注意的是,这也适用于固定在屏幕底部的页脚,它将被麦克风阻塞。

此时,需要在<body>中添加安全区域独有的函数constant()

  • constant(safe-area-inset-top):在Viewport顶部的安全区域内设置量(CSS像素)
  • constant(safe-area-inset-bottom):在Viewport底部的安全区域内设置量(CSS像素)
  • constant(safe-area-inset-left):在Viewport左边的安全区域内设置量(CSS像素)
  • constant(safe-area-inset-right):在Viewport右边的安全区域内设置量(CSS像素)

Webkit在iOS11中新增CSS Functions:env()替代constant(),文档中推荐使用env(),而constant()从Safari Techology Preview 41 和iOS11.2 Beta开始会被弃用。

env()用法如同var(),在不支持env()的浏览器中,会自动忽略这一样式规则,不影响网页正常的渲染:

body {
    /* iOS 11 */
    padding-bottom: constant(safe-area-inset-bottom);
    padding-top: constant(safe-area-inset-top);

    /* iOS 11.2+*/
    padding-bottom: env(safe-area-inset-bottom);
    padding-top: env(safe-area-inset-top);
}

iPhone X下constant(safe-area-inset-bottom)对应34pxconstant(safe-area-inset-top)对应44px,而刘海区域的实际高度是32px,也就是安全高度比刘海高了12px,如下图所示:

例如,处理Head Bar的时候(如果你的Head Bar是固定定位),那你可以这样处理:

#header{
    /* iOS < 11 */
    padding-top: 20px;
    /* iOS 11 */
    padding-top: constant(safe-area-inset-top);
    /* iOS 11.2+ */
    padding-top: env(safe-area-inset-top);
}

第三步:iPhone X个性化样式设置

如果需要对iPhone X个性化样式设置的话,可以通过@media查询来为其添加:

@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
    /* iPhone X 独有样式写在这里*/
}

配合CSS自定义属性

随着浏览器对CSS自定义属性的支持力度越来越强,或者配合PostCSS插件的csssnext,我们可以在:root使用CSS自定义属性:

:root{
    --safe-area-inset-top: 44px;
    --safe-area-inset-bottom: 34px;
    --safe-area-inset-left: 0px;
    --safe-area-inset-right: 0px;
}

如此一来就可以配合var()函数去做安全区域的处理。有了这样的CSS变量后,我们就可以在CSS里拿到对应的安全区域来做适配了,利用CSS变量的覆盖我们可以实现一段兼容代码:

/* 默认的安全区域是0 */
:root{
    --origin-safe-area-inset-top: 0;
    --origin-safe-area-inset-bottom: 0;
}
/* iPhoneX等支持constant的适配 */
@supports (width: constant(safe-area-inset-top)){
    :root{
        --origin-safe-area-inset-top: constant(safe-area-inset-top);
        --origin-safe-area-inset-bottom: constant(safe-area-inset-bottom);
    }
}

/* 后续使用 */
#header{
    padding-top: var(--safe-area-inset-top);
}
#tabbar{
    padding-bottom: var(--safe-area-inset-bottom);
}

简单的总结一下:

苹果对于iPhone X上H5页面的适配,提供了特殊属性支持,包括meta标签的viewport属性值中加入viewport-fit和加入constant(safe-area-inset-*)env(safe-area-inset-*) ,这些属性是与iOS11以上的所有iPhone机型(不仅仅包括iPhone X)都相关的,故以iOS版本为区别具体分析一下全屏下的H5页面:

  • 针对iOS11.0以下系统:将不识别H5页面meta标签下的viewport-fitconstant(safe-area-inset-*)/env(safe-area-inset-*)属性。
  • 针对于iOS11.0-iOS11.1的系统:当设置了viewport-fit="cover",H5页面会覆盖页面安全区域全屏展示,但是这样会带来页面元素会被“刘海儿”和底部Home Indicator遮挡问题,所以苹果提供在CSS中设置constant(safe-area-inset-*)距离来规避遮挡问题。另外,页面不加viewport-fit="cover"默认viewport-fit="contain/auto",也就是我们看到的页面不能覆盖安全区域的情况,此时constant(safe-area-inset-*)的值都为0。所以在meta标签的viewpoint中加viewport-fit="cover"时iOS10和iOS11下constant(safe-area-inset-*)值的表现是不一样的。
  • 针对iOS11.2及iOS11.2以上的系统:constant()改成了env()。另外,iOS11.2新增了CSS function: min()max()。例如:padding-left: max(12px, env(safe-area-inset-left));。在env(safe-area-inset-left)值因为Webview变化时值也可以做出相应变化,取12pxenv(safe-area-inset-left)的较大值。

总结如下图:

注意一些细节

设置了viewport-fit=cover后,height: 100%不能撑满整个视口

如下图蓝色区域所示,当设置了设置了viewport-fit=cover时,给<html><body>设置了height:100%,高度并不等于视口高度,而是留出了constant(safe-area-inset-top)的高度,实际上就是整个页面被往上提了constant(safe-area-inset-top)的高度:

默认状态 设置viewport-fit=cover

并且这时fixed定位(底部红色元素)的元素设置bottom: 0以后也会距离底部constant(safe-area-inset-top)的高度,通过给<body>设置height: 100vh可以解决:

默认状态 设置height: 100vh

当然,<body>高度大于100vh时,bottom: 0fixed元素也可以正常吸底,这就很奇怪了,例如某个页面的高度是由内容撑起来的,并且又有底部导航,那么一旦页面高度小于100vh时底部导航就无法吸底,而一旦高度变高了,又会突然吸底,如下图所示:

页面内容高度小于100vh 页面内容高度大于100vh

所以如果页面有吸底的fixed元素,最好给页面设置一个100vh的最小高度:

body{
    min-height: 100vh;
}

<body>的背景色不受<body>高度的限制

虽然当页面本身的高度小于100vh时,<body>的高度没有扩展到底部,但是<html>或者<body>的背景是可以延伸到底部的,如下图所示,蓝色区域是<body>的高度,绿色是<body>的背景色:

当页面高度小于100vh时,fixed元素超出<body>区域的部分不可见

按前文说的,当页面本身的高度小于100vhfixed元素的bottom属性的计算参考不是整个视口,而是有constant(safe-area-inset-top)的偏差,这时如果我们设置一个负的bottom值,则这部分超出的区域将不可见,如下图,其中的绿色边框为<body>的区域,蓝色区域为fixed元素,可以看到超出<body>的区域部分已经不见了(实际高度是底部红色元素的2倍),即使这个区域还在视口内:

实际上这和fixed元素的特性是符合的,负值部分本来就是超出视口的部分,正常情况下就是不可见的,只是body的背景色又可以溢出显示,这就有点矛盾了,如下图中灰色部分就是<body>的背景,可以溢出显示,而fixed元素被截去了一部分:

fixed元素的一些细节

fixed 完全吸底元素(bottom = 0),比如下图这两种情况:

可以通过加内边距 padding 扩展高度:

.footbar {
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
}

或者通过计算函数 calc() 覆盖原来高度:

.footbar {
    height: calc(60px(假设值) + constant(safe-area-inset-bottom));
    height: calc(60px(假设值) + env(safe-area-inset-bottom));
}

注意,这个方案需要吸底条必须是有背景色的,因为扩展的部分背景是跟随外容器的,否则出现镂空情况。

还有一种方案就是,可以通过新增一个新的元素(空的颜色块,主要用于小黑条高度的占位),然后吸底元素可以不改变高度只需要调整位置,像这样:

.newEle{
    margin-bottom: constant(safe-area-inset-bottom);
    margin-bottom: env(safe-area-inset-bottom);
}

空的颜色块:

position: fixed;
bottom: 0;
width: 100%;
height: constant(safe-area-inset-bottom);
height: env(safe-area-inset-bottom);
background-color: #fff;

fixed 非完全吸底元素(bottom ≠ 0),比如 “返回顶部”、“侧边广告” 等。像这种只是位置需要对应向上调整,可以仅通过外边距 margin 来处理:

.fixedEle {
    margin-bottom: constant(safe-area-inset-bottom);
    margin-bottom: env(safe-area-inset-bottom);
}

或者,你也可以通过计算函数 calc() 覆盖原来 bottom 值:

.fixedEle {
    bottom: calc(50px(假设值) + constant(safe-area-inset-bottom));
    bottom: calc(50px(假设值) + env(safe-area-inset-bottom));
}

这仅是初级版本,文章中有些细节还需要通过真机验证,目前仅供参考!如果您在项目中碰到iPhone X适配的坑,或者有更好的建议,欢迎在下面的评论中进行讨论。

持续...

有关于视觉设计师和前端而言,他们是相爱相恨的。最近或者将来很多的一段时间都会花时间在探计和学习视觉、交互体验、用户体验和还原上。希望能深淀出一些值得大学思考和学习的东西。各位路过的前辈,欢迎多多指点和拍正。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.w3cplus.com/mobile/mobile-design-and-adapter.html

如需转载,烦请注明出处:https://www.w3cplus.com/mobile/mobile-design-and-adapter.html

如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!

赏杯咖啡,鼓励他创作更多优质内容!
返回顶部