Skip to content

要面对的问题

响应式设计的主要工作就是要让网页适配当下种类繁多的设备,使页面在不同设备上仍然看上去友好并且可用。
但是细想,当在设法让一个页面同时适配三星Galaxy S6和iPhone 6时,我们究竟是在适配什么?
Galaxy S6和iPhone 6究竟存在哪些影响页面展现的差异因素?
以上这些问题都可以归纳为:当谈论设备的时候我们究竟在谈论什么?

不同设备间的差异有很多种,我们不关心设备的制造厂商,不关心CPU功耗,不关心生产工艺,只关心会影响页面在屏幕上展现的设备因素。
如果用户在来自两台不同厂商设备上浏览页面时的效果是一致的,那么从前端的角度讲,就可以认为在某种意义上这两台设备并无差异。

像素密度

上示的截图来自苹果中文网站对 iPhone 6 的一段技术规格描述

PPI 这个概念的复杂之处在于,它的意义会随着上下文的改变而变得大相径庭,
例如,它可以用于描述图片文件的某些属性,可以作为打印时的可配置参数
在这里我们只谈论它作为设备屏幕特征的情况

PPI(Pixel Per Inch)直译为 "像素每英寸"。
这样翻译其实有些晦涩,如果考虑到它实际想表达的意思,可以把他译为像素密度(在维基百科中,PPI 这个名词也是归属于 Pixel density)。
和常常谈论的人口密度、建筑密度类似,表达的是某个量在指定面积内的密集情况
下图很直观地描述了这个测量单位

图中从左至右同样 3 个 1 平方英寸单位面积的正方形面积中,谁的像素越多谁的像素密度就越高

但是,你是否想过上面一直在谈论的像素究竟指的是什么?“呃,像素不就是在书写样式时使用的单位px吗?”其实不尽然。
我们姑且把这一类像素称为CSS像素。
在谈论它们之前,我们先看看另一类像素——设备像素。

设备像素在英文中对应为device pixel或physical pixel,所以也可译为物理像素。
无论是早期的CRT显示器还是如今的LCD显示器,现实的原理都是通过将一系列的矩形小点排列成一个大的矩形,让不同的小点呈现不同的颜色,最终来组成一幅完整的图像。

例如,下图所示就是 LCD 显示器上一个 4 x 4 个设备像素排列成的矩阵

图中的每一个 "点"(dot) 就是设备像素。
在 LCD 显示器中,每一个设备像素又是由 3 个分别显示红绿蓝的子像素(subpixel)组成。
LCD 显示器的显示功能是通过调整每一个设备像素的子像素明暗来实现的,具体原理如下图所示

像素密度中所指的像素是设备像素,鉴于设备像素亦可称为物理点,所以PPI也可以称为DPI(dots per inch,每英寸点数)。
但请注意这样的等价只有在描述显示设备的特征时才成立。
在其他行业的上下文中两者含义并不同。

设备像素密度的计算方式正如它英文单词定义的一样所见即所得:使用对角线上的设备像素值,除以对角线的英寸长度,即为像素密度。

下图为 iPhone 5 对应的计算像素密度的图解

我们当然希望像素密度越高越好(手机厂商也的确在往这个方向努力),因为像素密度越高意味着在有限的手机屏幕面积上能容纳的设备像素越多,能够展现更多的画面细节。
同时因为肉眼几乎无法分辨物理像素点,设备看上去更加自然和平滑,原理如下图

但高像素密度同样也带来了副作用:单位面积内容纳的设备像素越多,也就意味着单个设备像素面积越小,如下图所示

可以预见的一种情况是,一个 4×4 像素组成的图片素材在标准像素密度(以下简称为标清)的设备(如普通的桌面显示器)上看上去有硬币大小,但是到了高像素密度(以下简称为高清)的设备上却只有指甲盖大小,如下图所示。

反过来我们可以推论,如果想让高清设备与标清设备上的图片看上去同样大小,那么高清设备上的图片素材应该具有更多的像素,如下图所示。

以一台23英寸的显示器为例,它的横向和纵向分别排列着 1920×1080 个设备像素,那么它的最高分辨率就可以达到 1920×1080,我们称这个分辨率为原生分辨率(native resolution)或者物理分辨率(physics resolution)。

而屏幕只有5英寸的三星Galaxy S4的屏幕同样是由 1920×1080 个设备像素组成的。
根据刚刚的结论,因为单个设备像素的面积过小,在普通显示器上可见的图片素材有可能此时在 S4 上几乎是很难分辨的。

手机厂商不可能没有留意到这个问题。
为了设备的可用性,即图标和文字可以被正确识别和准确点击,在高清设备上的各类素材视觉上必须保证与标清设备同样大小。
他们的解决方法很简单:如果素材在高清设备上显示过小,就把所有尺寸都放大一倍就好了(准确来说,Galaxy S4放大了9倍)。
原来图片上的一个像素单位由一个设备像素单位显示,现在则由9个设备像素(3×3)单位显示,效果就是将图片做拉伸处理。
如果网站提供的图片像素不够高,则会出现模糊情况,如下图所示。

用iPhone 3GS和iPhone 4是最佳的对照实验,两者拥有相同的屏幕尺寸,但是iPhone 4的像素密度几乎是iPhone 3GS的2倍,像素是后者的4倍。
但是两者屏幕上应用图标视觉上大小却是一模一样的,因为后者系统将所有的元素进行了4倍的放大(长2倍×宽2倍)。
不过按照常识来说,将位图放大4倍务必会造成图片模糊。
例如,下面这个例子为同一张图片在高清(左,模糊)和标清(右,清晰)设备上的对比,如下图所示。

但感官上iPhone 4画面(如首屏图标)不仅没有模糊素材,反而看上去更细腻,是因为iPhone 4素材包含的像素数量是前者的4倍,尺寸也是前者的4倍,而设备像素足够小,能将细节全部展现出来。
这同样也是Retina工作的原理。

在高清设备中,为了解决设备像素过小的问题,系统分辨率下每个像素会等于多个设备像素,而这个比值称为设备像素比(Device Pixel Ratio,DPR)。

从另一个方面来说,iPhone 3GS和iPhone 4都保持了相同的系统分辨率——480×320,但是iPhone 4的设备像素达到960×640,每一个系统分辨率下的像素由2个设备像素组成。
这样就能容纳更多的细节。

请再次注意,放大素材的前提是被放大的素材最好有足够的尺寸和像素,否则多余的像素只能由系统计算出来而导致看上去模糊。
这也是高清设备常常被诟病的地方。

CSS 像素

在我们编写样式代码时,常会用到另一个像素单位px。
为了和设备像素区分开,我们把它称为CSS像素。
如果说设备像素给我们的印象是机械的、固定的、物理的,那么CSS像素将会是灵活的、虚拟的、相对的。

为什么说它是相对的?

假设我在页面上画一个 300 px 宽度的块级元素。
一般情况下,块级元素只相当于页面的部分宽度。
如果使用浏览器的页面放大功能,10倍地放大页面,很快块级元素就会充满整个页面。
但吊诡的是,此时我们既没有改变浏览器的宽度,也没有改变容器的样式宽度,那么浏览器为我们做了什么呢?
它把每一个CSS像素的面积放大了,如下图所示。

CSS像素默认与系统分辨率下像素大小相等。那么,在标清设备中,一个CSS像素应该是与一个设备像素大小相等。
但是,在高清设备或者用户缩放的过程中,一个CSS像素也可以大于或等于多个设备像素,如下图所示。

有关高清设备被诟病的问题,还有一个问题未被提及:假设在原生应用(注意,不是Web)的开发中,如果必须以设备像素为单位进行开发,那会是非常痛苦的一件事。
以iPhone 3GS为参照,在3GS中,一个系统分辨率像素等于1个设备像素;
在iPhone 4中,一个系统分辨率像素等于2个设备像素;
在Galaxy S4中,一个系统分辨率像素等于3个设备像素;
在iPhone 6 Plus中,一个系统分辨率像素等于2.46个设备像素。
那么,如果一个按钮在iPhone 3GS中大小为 100×100 设备像素,在iPhone 4中大小就应该是 200×200 设备像素,在Galaxy S4中就应该是 300×300 设备像素,在iPhone 6 Plus中就应该是246×246 设备像素。
但是,我们实在无法为每一台设备根据它的设备像素准备如此多的素材。

我们希望有这么一种抽象的单位,只需告知手机它这个按钮或者素材占用几个这样的“抽象单位”,在显示时它就能自动缩放至合适的具体设备像素值。
例如,iOS系统中的PT就是这样一个单位,当我们告诉它按钮占用的宽度是 100×100 PT时,在iPhone 3GS中,它就意味着占用 100×100 的设备像素;
在iPhone 4中,它就占用 200×200 设备像素。
也就是说,系统会根据给出的PT值,再根据系统分辨率像素与设备像素的比值,换算出目标应该占用的大小。

上面所说的这种抽象单位称为与设备无关像素(device independent pixel)。

同理,CSS像素也是与设备无关像素。
我们不用关心在不同设备上一个CSS像素会匹配多少个设备像素,浏览器会根据DPR为我们适配,在CSS基础上根据DPR做适当放大或者拉伸。
但问题是,因为字体是矢量的关系,被放大后仍然足够清晰。
而身为标量的图片则会变得模糊,因为拉伸状态下一个CSS像素需要横跨多个设备设备像素,每张图片上的单个像素信息仍然要被多个设备像素瓜分显示,自然就变得模糊起来。
上图中对比的是同样CSS尺寸在高清和标清下的效果,很明显左侧的高清设备会显得更模糊。

为了解决高清设备中图片素材会变模糊的问题,需要为高清设备提供更大尺寸、细节更丰富的图片。
此外,高清图片可以向下兼容,我们可以用CSS像素控制高清图片在标清设备上的大小,这样一张图片就走遍天下了,解决了响应式图片中的适配问题。
可新的问题又出现了。

(1) 如何区分出高清设备和标清设备呢?如何为不同的设备提供不同的样式呢?

(2)如果用户在网络信息较差的手机上访问网站我们仍然提供高清图片,这是否有失偏颇?能否做到为不同的设备,甚至为不同的网络环境,提供不同的图片素材?

视口

桌面浏览器的视口

在桌面浏览器中,假设某个页面的 <html> 宽度设置为自适应的 100%: html {width: 100%;},这意味着html宽度始终与浏览器宽度保持一致。

同时,浏览器宽度也等价于浏览器可视区域宽度,所以在桌面浏览器中,浏览器可视区域大小决定了页面的布局。
所见即所得,浏览器窗口多大,就会以多大的尺寸影响页面布局。
我们称这里的可视区域大小为布局视口(layout viewport),或者简称为视口(viewport),如下图所示。

在后面的内容中,如果没有特别说明,所称视口皆为此概念。
当然,如果页面布局为固定宽度布局,页面布局就不会受到视口大小影响,但是在视口之外的内容,需要通过控制浏览器视口的滚动栏才能看见。 桌面端视口的特点是,浏览器区域大小受限于显示器屏幕大小,这也意味着页面的宽度不会超过浏览器屏幕宽度

假设在100%宽度的body内还有一些占用宽度为10%的元素,如果以桌面视口的定义,10%宽度的元素实际宽度最大只能是系统分辨率的10%,则在1920×1080的显示器上浏览器最大化的情况下,该元素最大宽度为192 px。
若在iPhone 4上浏览这个页面,如果iPhone 4仍然继承的是桌面视口的设定,那么用户看到只是一个32 px的元素,这是根本是无法识别的,这样的容器也不可用。

所以,移动设备的视口定义与桌面并不相同,但视口同样是用于控制页面布局渲染的。

移动设备浏览器的视口

对于移动设备上的浏览器来说,仍然需要一个区域用于控制页面的布局渲染。
只不过这个区域不再以屏幕尺寸作为限制。

以iPhone 4为例,Safari渲染页面布局的默认宽度为980 px(CSS像素)。
但是,用户可能觉得并非如此,因为当用户用Safari打开一个网站桌面版后,他看到的页面宽度刚好是与屏幕宽度相等的,如下图所示。
但是iPhone 4的系统宽度分辨率不是320 px吗?

其实之所以这样是因为浏览器做了两件事,如下图所示:
(1) 用980 px像素宽度渲染页面;
(2)将页面缩放至宽度与系统宽度一致。

用户使用手势缩放页面也是同样的原理。浏览器和用户在这里并没有改变页面(准确来说是视口)的大小(size),只是改变了视口的缩放比例(scale)。

但是,浏览器使用默认的980 px去渲染页面并非是万能的。
例如,当页面比较窄时(如只有320 px宽),页面效果会非常糟糕,如下图所示。

设计人员希望以页面的宽度来渲染页面,并能自然自适应到系统宽度。于是手机厂商(最早是在Safari中)提供了一个名为viewport的 <meta> 标签设置视口大小。例如,在上一个用例中,当我们想以320 CSS像素渲染页面时,可以在 <head> 标签中加入meta标签,并设置如下:

1
<meta name="viewport" content="width=320" />

通过在content属性中设置width参数,即可以手动调整宽度,也可以添加initial-scale参数来控制渲染时缩放视口的比例。
如果未添加该参数,浏览器自动会将页面缩放至与浏览器宽度一致。

1
<meta name="viewport" content="width=320, initial-scale=1.0" />

以下图中320 px宽的图片为例,通过混合配置width和scale参数,我们能随意控制缩放比例页面的大小。

设备宽度

大部分情况下我们都希望以系统分辨率的宽度来渲染页面,以尽可能地避免缩放,以及正确地响应设备(例如,页面布局是在320 px宽度的限制下进行设计的,我们当然希望设备以320 px宽度来渲染布局,而不是用980 px渲染后再进行缩放)。
问题是不同设备的系统分辨率是不一致的,即使在同一种设备上,横竖两种持握方式也会让渲染方式不同。

但是,我们可以不用关心具体的数值,只要告诉浏览器:“无论什么设备,什么样的持握方式,请按照系统分辨率宽度渲染。”

于是我们可以将width的值设置为device-width。例如:

1
<meta content="width=device-width, initial-scale=1.0" />

那么我们就把获取具体系统分辨率宽度这个任务交给浏览器了,就由浏览器具体情况具体执行,如下图所示。