两个 viewport 的故事(二)

译者序

A tale of two viewports — part twoJeremyWei 翻译,译文版权"署名-非商用",意见反馈

原文写于 2010 年 6 月,译文做了一些更新。

正文

在这个迷你系列里我将解释 viewport ,以及多个重要元素的宽度是如何计算的,比如 <html> 元素,window,screen。

这篇文章我们来聊聊移动浏览器。如果你对移动开发完全是一个新手的话,我建议你先读一下第一篇关于桌面浏览器的文章,先在熟悉的环境中做好准备。

移动浏览器的问题

当我们比较移动浏览器和桌面浏览器的时候,它们最显而易见的不同就是屏幕尺寸。为桌面浏览器所设计的网站在移动浏览器中显示的内容明显要少于在桌面浏览器中显示的;不管是对其进行缩放直到文字小得无法阅读,还是在屏幕中以合适的尺寸只显示站点中的一小部分内容。

移动设备的屏幕比桌面屏幕要小得多;想想其最大有 400px 宽,有时候会小很多。(一些手机声称拥有更大的宽度,但是它在撒谎-或者也可以说它给了我们没用的信息。)

处于中间的平板,比如像 iPad 或者传说中 HP 基于 webOS 所研发的设备,会缩小桌面环境和移动环境的差异,但是这并没有改变根本问题。站点必须也能工作在移动设备上,我们必须让它们在小尺寸的屏幕上正常显示。

最重要的问题在 CSS 上,特别是 viewport 的尺寸。如果我们照搬桌面环境的模式,那么我们的 CSS 就会出错。

让我们看下之前 sidebar 为 width: 10% 的例子。如果移动浏览器跟桌面浏览器一样,它们将最多为 sidebar 设置 40px 的宽度,但是这太窄了。你的流式布局将被挤得特别扁。

解决这个问题的一个方法是为移动浏览器建立一个特别的站点。先不说你是否有必要这么做,实际上只有很少的网站会特地的支持移动设备。

移动浏览器厂商想给它们的客户提供最好的体验,眼下即「尽可能的跟桌面一样」。因此需要一些技巧。

两个 viewport

viewport 太窄了,不能作为 CSS 布局的基础。显然的解决方案是让 viewport 变宽一些。不过要把它分成两种:visual viewport 和 layout viewport 。

George Cummins 在 Stack Overflow 上对这个基本概念解释得最好:

把 layout viewport 想像成为一张不会改变大小或者形状的大图。现在想像你有一个小方框,你通过它来看这张大图。这个小方框的周围是不透明的材料,挡住你的视线。你通过这个小方框所能看到的大图的部分就是 visual viewport。你可以拿着方框走远点(缩小)以看到整个大图,或者走近点(放大)只看局部。你也可以改变方框的方向,但是大图(layout viewport)的大小和形状永远不会变。

也可以看一下 Chris 的解释

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

但是 CSS 布局,尤其是百分比宽度,是基于 layout viewport 计算的。 通常认为 layout viewport 比 visual viewport 宽。

因而 <html> 元素以 layout viewport 的宽度作为它的宽度的初始值,而且 CSS 的布局,好像屏幕比手机的屏幕大得多。这使得你的站点布局跟它在桌面浏览器上的一样。

layout viewport 有多宽?各个浏览器不一样。Safari iPhone 为 980px,Opera 为 850px,Android WebKit 为 800px,IE 为 974px。

一些浏览器有特殊的行为:

缩放

很显然两个 viewport 都是以 CSS 像素度量的。但是当进行缩放的时候, visual viewport 的尺寸会发生变化(如果放大,屏幕的 CSS 像素会变少),layout viewport 的尺寸仍然不变。如果不这样,你的页面会经常重新回流 reflow, 就像百分比宽度重新计算一样。

理解 layout viewport

为了理解 layout viewport 的尺寸,我们来看一下当页面被完全缩小后会发生什么。许多移动浏览器会在初始情况下以完全缩小的模式来展示页面。

重点是:浏览器已经为自己的 layout viewport 选择了尺寸,这样的话在完全缩小模式的情况下它完整的覆盖了屏幕(因而等于 visual viewport )。

所以 layout viewport 的尺寸等于在最大限度缩小的模式下屏幕上所能显示的任何内容的尺寸。当用户放大的时候这些尺寸保持不变。

layout viewport 宽度保持不变。如果你旋转你的手机,visual viewport 会变,但是浏览器对此的调整措施是,稍稍放大,所以 layout viewport 又和 visual viewport 一样宽了。

这会影响到 layout viewport 的高度,现在的高度比竖屏模式要小。但是 web 开发者不在乎高度,只在乎宽度。

度量 layout viewport

我们现在有两个需要度量的 viewport。很幸运的是浏览器战争给我们提供了两个属性对。

document.documentElement.clientWidth-Height 包含了 layout viewport 的尺寸。

document.documentElement.clientWidth/Height

屏幕方向会对高度产生影响,但对宽度不会产生影响。

度量 visual viewport

对于 visual viewport ,它是通过 window.innerWidth/Height 来进行度量的。很明显当用户缩小或者放大的时候,度量的尺寸会发生变化,因为屏幕上的CSS像素会增加或者减少。

window.innerWidth/Height

不幸的是这是浏览器不兼容问题中的一部分;许多浏览器仍然不得不增加对 layout viewport 度量尺寸的支持。但是没有浏览器把这个度量尺寸存放任何其他的属性对中,所以我猜 window.innerWidth/Height 是标准,尽管它被支持的很糟。

屏幕

像桌面环境一样,screen.width/height 提供了以设备像素为单位的屏幕尺寸。像在桌面环境上一样,做为一个开发者你永远不需要这个信息。你对屏幕的物理尺寸不感兴趣,而是对屏幕上当前有多少 CSS 像素感兴趣。

screen.width and screen.height

缩放比例

直接得到缩放比例是不可能的,但是你可以这样获得: screen.width 除以 window.innerWidth。当然这只有在两个属性都被完全支持的情况下才有用。

幸运的是缩放比例并不太重要。你需要知道的是当前屏幕上有多少个 CSS 像素。你可以通过 window.innerWidth 来获取这个信息,如果它被正确支持的话。

滚动距离

你还需知道的是 visual viewport 的当前位置相对于 layout viewport 的距离。这是滚动距离,并且就像在桌面一样,它被存储在 window.pageX/YOffset 之中。

window.pageX/YOffset

元素

就像在桌面上一样, document.documentElement.offsetWidth/Height 提供了以 CSS 像素为单位的 <html> 元素的尺寸。

document.documentElement.offsetWidth/Height

媒体查询 Media queries

媒体查询和其在桌面环境上的工作方式一样。 width/height 使用 layout viewport 做为参照物,并且以 CSS 像素进行度量, device-width/height 使用设备屏幕,并且以设备像素进行度量。

换句话说,width/heightdocument.documentElement.clientWidth/Height 值的镜像,同时 device-width/heightscreen.width/height 值的镜像。(它们在所有浏览器中实际上就是这么做的,即使这个镜像的值不正确。)

媒体查询

现在哪个度量的尺寸对 web 开发者更有用?我的观点是,不知道。

我开始认为 device-width 是最重要的那一个,因为它给我们提供了关于我们可能会使用的设备的一些信息。比如,你可以根据设备的宽度来更改你的布局的宽度。不过,你也可以使用 <meta viewport> 来做这件事情;使用 device-width 媒体查询并不是绝对必要的。

那么 width 究竟是不是更重要的媒体查询呢?可能是;它提供了某些线索,这些线索是关于浏览器厂商认为在这个设备上网站应该有的正确宽度。但是这有些模糊不清,并且 width 媒体查询实际上不提供任何其他信息。

所以我不做选择。目前我认为媒体查询在分辨你是否在使用桌面电脑,平板,或者移动设备方面很重要,但是对于区分各种平板或者移动设备并没有什么用。

或者还有其他用处。

事件坐标

这里的事件坐标与其在桌面环境上的工作方式差不多。不幸的是,在十二个测试过的浏览器中只有 Symbian WebKit 和 Iris 这两个浏览器能获取到三个完全正确的值。其它所有浏览器都或多或少有些严重的问题。

pageX/Y 仍然是相对于页面,以 CSS 像素为单位,并且它是目前为止三个属性对中最有用的,就像它在桌面环境上的那样。

Event coordinates

clientX/Y 是相对于 layout viewport 来计算,以CSS像素为单位的。这有道理的,即使我还不能完全指出这么做的好处。

screenX/Y 是相对于屏幕来计算,以设备像素为单位。当然,这和 clientX/Y 用的参照系是一样的,并且设备像素在这没有用处。所以我们不需要担心 screenX/Y;跟在桌面环境上一样没有用处。

Meta viewport

Meta viewport

最后,让我们讨论一下 <meta name="viewport" content="width=320"> ;起初它是苹果做的一个扩展,但是现在被许多浏览器借鉴。它的意思是调整 layout viewport 的大小。为了理解为什么这么做是必要的,让我们后退一步。

假设你创建了一个简单的页面,并且没有给你的元素设置「宽度」。那么现在它们会被拉伸来填满 layout viewport 宽度的 100%。大部分浏览器会进行缩放从而在屏幕上展示整个 layout viewport,产生下面这样的效果:

所有用户将会立刻进行放大操作,这样有用,但是大多数浏览器不改变元素的宽度,这使得文字很难阅读。

(值得注意的例外是 Android WebKit,它实际上会减小文字所在元素的大小,所以文字就能适配屏幕。这简直太有才了,我觉得所有其他浏览器应该借鉴这个行为。后面我将会完整的写一下这个议题。)

现在你尝试设置 html {width: 320px} 。现在 <html> 元素收缩了,其他元素现在使用的是 320px 的 100%。这在用户进行放大操作的时候有用,但是在初始状态是没用的,当用户面对一个缩小了的页面这几乎不包含任何内容。

为了规避这个问题苹果发明了 meta viewport 标签。当你设置 <meta name="viewport" content="width=320"> 的时候,你就设置了 layout viewport 的宽度为 320px。现在页面的初始状态也是正确的。

你可以把 layout viewport 的宽度设置为任何你想要的尺寸,包括 device-widthdevice-width 使用 screen.width (以设备像素为单位)的值,并相应的重置 layout viewport 的尺寸。

但这里有一个问题。有时候标准的 screen.width 意义不大,因为像素的数量太高了。比如,Nexus One 的标准宽度是 480px,但是 Google 的工程师们觉得当使用 device-width 的时候,layout viewport 的宽度为 480px,这有些太大了。他们把宽度缩小为三分之二,所以 device-width 会返回给你一个 320px 的宽度,就像在iPhone 上一样。

如果,像传闻那样,新的 iPhone 将会出现一个更高的像素数量(并不意味着一个更大的屏幕),如果苹果借鉴了这个主意我将不会感到惊讶。也许最终 device-width 就意味着320px。

译注:传闻已成真,即现在的 retina 屏幕。同时也产生第三种 viewport

相关研究

一些相关的话题需要进一步的研究: