@Lenciel

How To Define Viewport For Mobile

概念:设备像素和CSS像素

首先要明白 CSS 像素和设备像素之间的区别。

设备像素是定义了我们使用的设备的分辨率,一般来说可以通过screen.width/height来得到。

如果我们在浏览器里面创建一个width:128px的元素,而我们的屏幕是 1024px 宽,那么在浏览器最大化的时候,浏览器的宽度应该是这个元素的八倍(大概八倍,暂时忽略那些 tricky 的 bits)。

如果用户使用滚轮放大或者缩小页面(Zooming),那么这个关系就会变化。一般来说,现在的浏览器对 Zomming 的实现都是通过伸缩像素,也就是当用户 Zoom 到 200%的时候,你的128px宽的元素宽度并不会变成256px,而是每个像素的宽度翻倍了:这个元素还是128px的宽度,但是占据了 256 个设备像素。

也就是说,zoom 到 200%其实会让一个 CSS 像素从一个设备像素变成四个设备像素的大小(长宽各翻一倍,面积就变成了 4 倍)。

         原始(zoom 100%)                 开始放大(zoom in)              开始缩小(zoom out)

csspixels_100  csspixels_in  csspixels_out

从这里可以看出,我们在使用 css 像素值在 css 文件里面定义宽度时,不需要考虑设备像素,因为在 zoom 的时候 css 像素值是不变的。用户在缩放的时候,浏览器负责放大缩小那些元素,但是整个 layout 是不变的。

屏幕大小

screen.widthscreen.height可以获取用户屏幕的尺寸。获得的尺寸值的单位是设备像素,换句话说,它们是不变的(可以看成是显示器的硬件指标,而不随浏览器窗口缩放而变化)。

desktop_screen

一般来说,用户的屏幕大小对我们是无用的信息。很少有人使用它(除开统计站点访问信息时)。

窗口大小

####

窗口大小表示了当前你的 CSS layout 可以占用的空间大小。可以通过window.innerWidthwindow.innerHeight来获取它们。

desktop_inner

很显然,窗口大小的单位是 CSS 像素值。当用户用滚轮放大缩小自己的窗口大小的时候,window.innerWidth/Height会相应的变化,这样你就知道自己的 layout 有多大空间可用了。

注意:Opera 是一个例外,它的window.innerWidth/Height不会因为缩放变化,而是取的设备像素值。这点对于桌面操作系统上的应用很讨厌,但是就像后面会说到的,对于手机来说很关键。

Scrolling offset

window.pageXOffset/pageYOffset代表了当前的 document 在横向和纵向上的偏移量。这样你就知道用户 scroll 了多少内容。

desktop_page

这些值也是 CSS 像素,理论上,当用户做缩放的时候,window.pageXOffset/pageYOffset也会变化。但是实际上,浏览器一般会在用户缩放的时候保证置顶的元素不变,所以在缩放的时候,一般来说这两个值基本是不变的。

概念:Viewport

Viewport的主要作用是限定<html>元素,也就是整个页面的最高级元素的大小。这样说比较含糊,我们来看一个详尽的例子:假设你有一个页面,侧栏的宽度是 10%。在你缩放浏览器窗口大小的时候,这个侧边栏也会自动缩放。这是如果实现的?

技术上讲,按照设置,侧边栏会自动占据自己的父元素(这里我们可以假设是<body>)宽度的 10%。那么<body>的宽度呢?又会取它的父元素<html>的宽度的 100%——因为理论上所有的 HTML 的 block-level 元素都会自动取父元素宽度的 100%(有些例外情况,我们这里忽略它们)。

那么<html>的宽度是怎么来的呢?所有的网页开发者都默认它就是浏览器的宽度。的确如此——在桌面浏览器上就是这样。实际上,就像我们已经提到过的,<html>的宽度是 viewport 限定的。在桌面上它就是整个浏览器窗口的宽度,而在手机上则要复杂得多。

手机浏览器

手机一个显而易见的特点就是屏幕小。如果我们原封不动的使用为桌面版浏览器开发的网站的文件,就会发现它们在手机浏览器上面目全非。但是大多数网站开发者是没有精力给手机用户专门维护另外一个网站的。因此手机浏览器制造商尽可能的通过技术手段让网站在手机上和在桌面浏览器里看起来「差不多」。

两个viewports

既然手机浏览器的 viewport 比较小,没法呈现所有 css 定义的元素,那么解决这个问题的办法就很明显了:扩大手机上的 viewport。最终,手机浏览器通过visual viewportlayout viewport的合作来解决问题。

George 在 Stack Overflow 上解释说:

layout viewport 就像一个不改变大小和形状的大的图片。想象一下你透过一个小窗口去看这个大的图片,因为窗框的遮挡,你只能看到大图片的一部分。这部分就是 visual viewport。你可以改变自己和这个窗口的位置(其实就是缩放),你也可以把窗口横放或者竖放,但是大图片本身(layout viewport)是不会改变的。

mobile_visualviewport

要注意,CSS layout,特别是用百分比定义的 layout,都是用 layout viewport 来算的,一般来说比 visual viewport 要宽很多。

因此,起始阶段<html>会占据整个 layout viewport 的宽度,你用 CSS 定义的元素的尺寸会比手机屏幕的尺寸要宽,这主要是要模拟网站在手机浏览器上的表现。

那么 layout viewport 究竟有多宽?这个要看具体的手机浏览器。Safari 的 iPhone 版为 980px,Opera 是 850px,Android 的 Webkit 是 800px,而 IE 是 974px。还有一些其他的浏览器除开特别的宽度之外有特别的行为,比如 Symbian 上的 Webkit 会尽量保持 layout viewport 和 visual viewport 相同。当 layout viewport 超过了 850px 的时候,两者才会变得不同。

缩放

很明显,visual 和 layout 两种 viewport 都使用了 CSS 像素,但是 layout viewport 是不会因为缩放操作变化的,而 visual viewport 会。很多的移动设备上的浏览器,在默认情况下都是以完全 zoom-out 的尺寸来显示页面的——结合前面的对两种 viewport 的简介,你可以认为这种时候你是把头伸到窗口(visual viewport)外在看窗外那副大图片(layout viewport),这种情况下两个 viewport 也是相等的(没有被窗框遮住的部分)。

因此,layout viewport 的宽和高等于在完全 zoom-out 的情况下 screen 上最大可显示的 CSS 像素值大小。因此在随后用户如果 zoom-in 了这个值是不会变化的。

下面的图表示了这个变化:在刚开始打开一个页面的时候,处于完全 zoom-out 的状态,layout viewport 的值等于 visual viewport 的值。随后,当用户 zoom-in(放大页面)时,每个 CSS 像素变成数倍于设备像素,而整个 layout viewport 的值不变(这样才保证你为页面定义的 CSS 长宽有意义),于是 layout viewport 的物理范围扩大了不少。

mobile_viewportzoomedout                              mobile_layoutviewport

另外要注意的就是 layout viewport 的宽度是保持不变的。也就是说在默认情况下,竖屏转横屏的时候,虽然 visual viewport 变化了,但是浏览器会自动的 zoom-in 一些来使得 layout viewport 和 visual viewport 仍然相等。这种情况下,layout viewport 的高度会变小,但是,对于 web 开发者而言,最重要的是宽度能保持不变。

mobile_viewportzoomedout            mobile_layoutviewport_la

测量layout viewport

document.documentElement.clientWidth/Height得到的是 layout viewport 的数值。并且,如前所述,横竖屏转换时,只会影响 Height 不会影响 Width 取值。

document.documentElement.clientWidht/Height

  • Layout viewport
  • 使用CSS像素值为单位
  • 支持Opera、iPhone、Android、Symbian、Bolt、MicroB、Skyfire和Obigo
  • 有一些问题:
  1. Samsung的Webkit的viewport只有当显式指定了的时候才有效,否则返回的是元素的长宽 </div>
  2. Firefox返回的是以设备像素值为单位的结果
  3. IE返回1024×768,正确的信息要通过document.body.clientWidth/Height获取

测量visual viewport

window.innerHeight/Width返回的是 visual viewport。很显然,当用户缩放的时候,这两个值会变化,因为有更多或者更少的 CSS 像素适配到屏幕可见的部分中。

window.innerHeight/Width

  • visual viewport
  • 使用CSS像素值为单位
  • 支持iPhone、Symbian、BlackBerry
  • 有一些问题:
  1. Samsung的Webkit返回的是layout viewport的取值只有当显式指定了的时候才有效,否则返回的是元素的长宽 </div>
  2. Firefox和Opera返回的是以设备像素值为单位的结果
  3. IE不支持,正确的信息要通过document.documentElement.offsetWidth/Height获取
  4. 其他的如Iris、Skyfire或者Obigo是有返回值到乱来的

手机屏幕大小

前面提到过,在桌面系统,screen.width/hieght 返回屏幕的大小,但是和桌面系统一样,开发者都不关心这个值:因为物理上屏幕多大并不重要,我们更关心多少个 CSS 像素能弄进这个屏幕。

<html>元素

通过document.documentElement.offsetWidth/Height可以获取<html>元素的 CSS 像素单位的尺寸。

缩放比例

当 screen.width 和 window.innerWidth 在你工作的平台都得到了正确实现的时候,两者相除就可以得到缩放的比例大小。

Media query

Media query 的思想就是定义只在页面大于,等于或者是小于一定尺寸的时候,才被执行的 CSS 规则。比如:

div.sidebar {
    width: 300px;
}

@media all and (max-width: 400px) {
    // styles assigned when width is smaller than 400px;
    div.sidebar {
        width: 100px;
    }

}

规则指定了当宽度小于 400px 的时候 sidebar 是 100px,其余时候是 300px。

需要注意的是在定义 media query 的时候可以用两套长宽:css 里的 width/height 表示的是 documentElement. clientWidth/Height 取值,也就是 viewport,单位是 css 像素。css 里的 device-width/height 表示的是 screen.width/height,单位是物理像素。

那么使用哪种值来定义 media query 甚至用不用它都是让人疑惑的问题。比如你用 device-width 的话 ,其实效果和使用来定义是类似的。但是用 width,仅仅是一个模糊的像素宽度,更加缺乏在各种设备上具体效果的准确预期。目前来看,用 media query 应该能比较正确的区分运行环境是桌面,手机还是平板,但是并不太能细致到是那款平板或者是什么手机。

Meta viewport

终于可以来讨论了。这个最早是 Apple 的一个扩展,但是现在已经被各种浏览器抄去了。它主要的作用是用来重定义 layout viewport 的大小。

我们通过实例来解释:假设你开发了一个网站,没有写 css,因此所有的元素没有 width 属性。打开页面的时候,它们会默认 zoom-out 到最大,看起来像这样:

mq_none

用户肯定会 zoom-in,这时因为很多浏览器会保持每个元素的宽度仍然是 100%的 layout viewport,因此,很多文字就撑到 visual viewport 外面去了变得不可见了(Android 原生的 Webkit 在这种情况下会自动调整 text-containing 元素的宽度来适配屏幕宽度)。

mq_none_zoomed

为了避免这种情况,你可能会加上 html {width:320px}的 css 规则。这样<html>的宽度,也就是大多数未定义宽度的元素继承到的宽度,是 320px。这样的规则可以解决用户 zoom-in 放大页面之后溢出的问题,但是当用户刚打开页面的时候,大概又会看的下图的效果(因为 300 个 CSS 像素值在完全 zoom-out 的情况下是很窄的):

mq_html300

为了解决这种初始和用户缩放之后的不和谐,Apple 定义了一个新的 meta 值。你可以用

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

来确定layout viewport的宽度。这样在初始的时候,整个页面看起来仍然是非常正确的:

mq_yes

meta 中定义的 layout viewport 宽度甚至可以是 device-width,也就是以设备像素值标定的screen.width取值。但是这里有一个陷阱,就是有时候正式的screen.width的取值并不是真的有实际意义,因为像素值可能太高。比如 Nexus One 的屏幕宽度是 480px,但是 Google 的工程师觉得用device-width来定义viewport就让layout viewport取值 480px 太宽了,于是它们让 device-width 的返回值打了个 2/3 的折扣,只有 320px,和 iphone 一致。这样一来,device-width的取值又有点儿 CSS 像素值的意思了。这种 resolution 标称的像素值和device-width的返回值之间的关系被称为CSS pixel density

</table>

Device resolution (px) device-width/ device-height (px)
iPhone 320 x 480 320 x 480, portrait/landscape mode
iPhone 4 640 x 960

320 x 480, in both portrait and landscape mode

CSS pixel density  = 2 </td> </tr>

iPad 1 and 2 768 x 1024 768 x 1024, portrait/landscape mode
new iPad 1536 x 2048

768 x 1024, portrait/landscape mode

CSS pixel density = 2 </td> </tr>

Samsung Galaxy S I and II 480 x 800

320 x 533, portrait mode

CSS pixel density = 1.5 </td> </tr>

Samsung Galaxy S III 720 x 1280 360? x 640?, portrait mode
HTC Evo 3D 540 x 960

360 x 640, portrait mode

CSS pixel density = 1.5 </td> </tr>

Amazon Kindle Fire 1024 x 600 1024 x 600, landscape mode

欢迎留言