关于视口即 viewport 的论述,本文章主要翻译自 A tale of two viewports

在这个系列中将会阐述视口和各种重要元素比如 html 元素的宽度的显示机制,当然还包括 window 和 screen 元素。

本篇文章主要讲述的是桌面浏览器,主要是为理解移动浏览器所作的铺垫。大多数开发人员可能对大多数桌面浏览器的概念会比较了解些。
在移动端中我们将会发现同样但更加复杂的概念,而事先基于大家都理解的概念进行讨论将会更好地帮助你理解移动端浏览器。

概念:设备像素和 CSS 像素

CSS 像素和设备像素是不同的概念。

设备像素指的是那种可以直观地看到的正确的大小。这种像素确定了你所在的设备的准确的分辨率,可以使用 screen.widthscreen.height 来取值。

如果设定元素的宽度为 width: 128px,而你的显示器是 1024px 宽,当你最大化你的浏览器的时候,元素会放大8倍以适配到你的显示器。

如果用户使用放大,计算将会改变。如果放大 2 倍, 宽度为 128px 的元素在 1024px 的显示器上只会适配到 4 倍大小。

放大在现代浏览器中的展示方式即为拉伸了像素的大小。意思即是并不是元素的宽度由 128 变为 256 像素;相反是像素点放大了 2 倍大小。实际上元素还是 128px 宽度,即使他占用了 256px 的空间。

换句话说,放大到 2 倍让一个 CSS 像素变为设备像素的 4 倍(宽度的 2 倍,高度的 2 倍得到 4 倍)。

以下图片可以说明这种概念。4 像素在 100% 缩放。CSS 像素完全和设备像素重叠。

现在当缩小。CSS 像素开始收缩,意味着一个设备像素会和几个 CSS 像素重叠。

当你放大的时候,会发生相反的情况。CSS 像素开始增大,现在一个 CSS 像素是几个设备像素重叠。

这里想指明的是你只需关注 CSS 像素,这是你的样式表所渲染出来的。

对于你和用户来说,设备像素基本上是毫无意义的。用户会缩放到自己认为适合阅读的样子。然而,缩放的水平和你没有半点干系。浏览器会自动让你的 CSS 样式放大或缩小。

100% 缩放

100% 缩放即 1 CSS 像素等于 1 设备像素。

100% 缩放对于接下来的阐述很有用,但是一般情况下你无需注意它。在桌面端即使用户放大或者缩小 CSS 像素会保证你的布局等比缩放。

屏幕大小

指的是用户显示器的大小而不是浏览器的。可以用 screen.widthscreen.height 取值。这些像素是设备像素是不可能改变的,这是显示器而不是浏览器的功能。

这些一般情况无用,只是对于网页统计网站有用。

窗口大小

如果你想要知道浏览器容器的内部大小。这些是你的CSS布局可使用的。可以使用 window.innerWidthwindow.innerHeight 求出。

很明显,浏览器窗口的内部宽度是用 CSS 像素来表示的。你需要知道你可以在浏览器窗口如何写布局,并且会随着用户的缩小而递减。当用户缩小你得到更少的可用的浏览器窗口空间,window.innerWidthwindow.innerHeight会随之减少。

(唯一的例外是 Opera, 当用户缩小的时候 window.innerWidth/innerHeight 并没有减少,而是用设备像素来计算)在桌面端会非常恼人,对于移动端是致命的。

高度和宽度都包含滚动条的高度和宽度。

滚动位移

可以通过 window.pageXOffsetwindow.pageYOffset 来计算窗口的滚动距离。这两个属性在不同的浏览器表现会有不同可用如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
function getScrollOffset() {
let supportPageOffset = window.pageXOffset !== undefined
let isCSS1Compat = ((document.compatMode || "") === "CSS1Compat")
let scrollLeft = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft;
let scrollTop = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;
return {
scrollTop: scrollTop,
scrollLeft: scrollLeft
}
}

这些属性都是以 CSS 像素计算的。你想要知道文档滚动了多少距离,处于什么的缩放状态。

理论上,当用户往上滚动并且放大,window.pageX/YOffset 将会改变。然而,当用户缩放的时候,浏览器试图保持相同的元素在可视区域的顶部。这个并不是像所期望的那样完美,但是这意味着实际上 window.pageX/YOffset 并不会真正改变:滚动的出浏览器窗口的距离会保持不变。

视口概念

在介绍更多的 JavaScript 属性之前介绍一个概念:视口。

视口是为了限制你最外层的元素 html。

或者有一点模糊。举个例子。当你有一个流式布局然后其中一个侧边栏的宽度是 10%,当你调整浏览器窗口大小的时候侧边栏的大小也会跟着改变。其中的原理是什么呢?

侧边是其父元素的宽度的 10%。这里是 <body> 元素,所有的块级元素都是其父元素的 100% 的宽度。<body> 元素的父元素是 <html>

<html> 的宽度为浏览器的宽度。这就是为什么侧边栏是浏览器窗口的 10% 宽度的缘故。所有的开发者都可以很直观地明白。
你所不知道的是,理论上,<html> 元素宽度是由视口所决定的。<html> 是 100% 的视口的宽度。
视口被定义为浏览器窗口。但并不是一个 HTML 的结构的一部分,所以你无法用 CSS 来影响它。桌面端它拥有浏览器窗口的宽度和调试。在移动端则会更加复杂。

视口的影响

有些奇怪的问题。可以缩放 该页面。滚动到顶,然后缩小些浏览器窗口直到浏览器内容超出浏览器窗口。

然后水平滚动到右边就会发现顶部的条没有正确地显示,空出一一块白色的区域,没有填满整个浏览器窗口。

这个行为是视口如何被定义造成的。定义了头部栏宽度为 html 元素的 100%,即浏览器窗口的 100%。

问题在于:当是 100% 缩放的时候显示是正常的,但是当你缩小视口,而网站的内容超出了视口的宽度的时候。这其实并没有什么问题,内容现在溢出了 <html> 元素,但是元素有一个 overflow: visible 属性,意味着溢出的内容在任何情况下都会显示。

蓝色的背景条没有溢出。因为我我为它赋值 width: 100% 然后,浏览器会给他视口的宽度。浏览器并不会关心宽度是否过小。

文档宽度?

我们需要知道页面所有内容的宽度,包括那些溢出的。据我所知,不可能找得到(除非你为页面上的每个元素单独计算其宽度和外边距,但是是含蓄地说来,这是极易出错的)。

我觉得需要一个 JavaScript 的属性来计算出文档的宽度。

如果我们想要更激进点,为什么不把这个值暴露到 CSS 上呢?我想要让我的蓝色的头部的背景是 document 宽度的 100% 而不是 <html> 元素的宽度。(这个一定会非常的难办的,并且如果它是难以实现地话,我并不会感到奇怪)。

你们觉得浏览器生产商会办到吗?

测量视口的宽度和高度

使用 document.documentElement.clientWidth/Height 以获得视口的分辨率。

如果你了解 DOM,你知道实际上 document.documentElement 实际上是 <html> 元素:HTML 文档的根元素。然而视口是比 html 更高一级的元素,它包含 html 元素。如果你给 html 元素赋值宽度可能会导致一些问题(虽然可以设置宽度)。

在这样的情况下 document.documentElement.clientWidth/Height 是提供的视口的宽度而不是 html 元素的。(这是这个元素的特殊的属性对特例。在其它情况下是表示的真实元素的宽度)。

所以 document.documentElement.clientWidth/Height 总是会返回的是视口的分辨率,而不管 html 元素的分辨率。

两个属性值对

window.innerWidth/Height 也可以返回视口的分辨率。

官方两者的主要区别是window.innerWidth/Height包括了滚动条而 document.documentElement.clientWidth/Height并不包含。这真是让人有些摸不着头脑。

这是由于浏览器大战的遗留问题。之前 Netscape 只支持 window.innerWidth/Height,而IE只支持document.documentElement.clientWidth/Height,从那里起其它浏览器也开始支持 document.documentElement.clientWidth/Height,而IE不支持window.innerWidth/Height

在桌面端拥有这两个属性值对只有细微的影响然而在移动却不尽然。

测量 <html> 元素

clientWidth/Height测量视口的分辨率,用document.documentElement.offsetWidth/Height 可以得出。

这些属性会让你以块状元素的形式访问 html 元素;如果你设置 html 元素的宽度的时候就可以取得到值。

事件的坐标

事件的坐标。当一个鼠标事件发生时,有不少于 5 个属性对会暴露给你为你提供准确的事件的坐标位置信息。这其中主要有三个是重要的:

  • pageX/Y 会提供相对于 html 元素的坐标以 CSS 像素表示。
  • clientX/Y 提供相对于视口的坐标以 CSS 像素表示。
  • screenX/Y 提供相对于屏幕的坐标以 设备像素表示。

在 90% 的时间里,你会使用 pageX/Y 来获得事件发生的位置相对于文档的坐标。其它 10% 的时间你会使用 clientX/Y。你永远都不会想知道相对于屏幕的事件坐标。

媒体查询

最后稍微谈一下媒体查询。主要思路是这样的:你想要定义 CSS 样式规则当页面大于,等于,小于设定的大小的时候生效。比如:

1
2
3
4
5
6
7
8
9
10
div.sidebar {
width: 300px;
}
@media all and (max-width: 400px) {
// styles assigned when width is smaller than 400px;
div.sidebar {
width: 100px;
}
}

现在侧边栏是 300 像素,除了当页面宽度比 400 像素还要小的时候它变为 100 像素。

现在的问题是这里的宽度是指的哪个元素?

主要有两个相关的媒体查询语句:width/heightdevice-width/device-height

  • width/height 值和 document.documentElement.clientWidth/Height(即视口)。是以 CSS 像素表示。
  • device-width/device-heightscreen.width/height 一样。是以设备像素表示。

你会使用哪个?毫无疑问当然是 width。开发进行们对于设备宽度是没有兴趣的。这个宽度是浏览器窗口计算得出的。

所以在桌面端使用 width 而不是 device-width。接下来,我们将会看到这种情况在移动端会相当的复杂。

结论

深入总结完了桌面浏览器的行为。第二部分将会把这些概念应用于移动端并且着重指出其与移动端的不同的地方。