本篇文章修改、整理自我之前写的一篇文章。css
在阅读这篇文章以前,你须要了解设备像素、逻辑像素(设备独立像素)和CSS像素的区别,见个人前一篇文章理解设备像素、设备独立像素和css像素。html
在经典文章A tale of two viewports中,做者定义了两种视口:前端
visual viewport 用户看到的的浏览窗口(在CSS标准中被称为viewport)。若是页面内容溢出了visual viewport,用户须要移动visual viewport(滚动)才能看完页面中的全部内容。visual viewport只是一个屏幕上的一个“窗口”,用户经过这个窗口来观察页面。segmentfault
溢出、滚动条的原理,我总结在了另外一篇文章中: css溢出机制探究。
在讨论layout viewport、visual viewport的尺寸的时候,咱们应该使用CSS像素为单位,而不是设备独立像素。由于咱们关心的是它们能容纳多大的元素、多少个元素,这些元素的大小都是经过CSS来定义的。浏览器
在这篇文章,咱们从CSS2.1标准(主要是八、九、十、11章)出发,更加规范地讨论这些内容。less
首先须要先了解一下containing block。containing block影响着其中元素的尺寸和定位。好比咱们都知道position:absolute的元素是相对于【最近已定位祖先】来定位的,其背后的缘由是:这个元素的盒子(box)的containing block由【最近已定位祖先的padding edge】产生。详见MDNLayout and the containing block。dom
在CSS标准中,<html>元素的containing block称为initial containing block。其余文章所说的layout viewport其实就是initial containing block。后面我将混用这两个词。iphone
initial containing block的尺寸有什么用?它能够决定<html>元素的尺寸。当<html>的宽度、高度、padding、margin使用百分数的值时,这个百分数的基准就是initial containing block的尺寸。布局
padding、 margin使用百分数值的时候都是相对于containing block的 width计算的,包括 xxx-top、 xxx-bottom!
<html>元素是一个block element,与其余的block element同样,它的宽度默认为containing block的100%(对于<html>就是initial containing block的100%),它的高度默认由子元素<body>撑开(除非明确设置了高度)。
那么initial containing block的尺寸是怎么肯定的呢?字体
在桌面浏览器中,initial containing block的尺寸等于visual viewport的尺寸。
为了不混淆,在这篇文章都使用visual viewport来指代浏览窗口。
如下例子验证了,initial containing block的尺寸是等于浏览窗口的。而且咱们能够利用它,来元素的width、height、padding(margin同理):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <title>test</title> <style> * { padding: 0; margin: 0; box-sizing: border-box; } html, body { /* 使html, body的尺寸始终与visual viewport相同(即便你缩放、调整浏览器窗口的大小) 对于默认为block的元素能够省略width: 100%; */ width: 100%; height: 100%; } html { /* 相对于initial containing block计算百分比 */ padding-left: 50%; } #box { /* 填满body元素,方便看出body的大小 */ width: 100%; height: 100%; /* 为何不直接经过在body上应用background-color来看它的大小? 由于body上使用background会有一个诡异的现象:background会超出body覆盖整个页面。 https://css-tricks.com/just-one-of-those-weird-things-about-css-background-on-body/ */ background-color: aqua; } </style> </head> <body> <div id="box"> </div> </body> </html>
在移动端浏览器上,layout viewport的尺寸有一些不一样:如今大部分的移动端浏览器都有2种模式:“查看桌面版网站”和“查看移动版网站”:
经常使用的viewport meta tag是<meta name="viewport" content="width=device-width, initial-scale=1.0">
。它告诉“查看移动版网站”模式下的浏览器,将layout viewport的宽度(CSS像素)设为设备的宽度(设备独立像素,通常是360px左右)。这样,在缩放为100%的状况下(CSS像素大小=设备独立像素大小),屏幕刚好能装下layout viewport,从而不会出现横向滚动条。
能够看出,在移动端浏览器,无论处于哪一种模式,无论有没有viewport meta tag,layout viewport的尺寸在加载之后就固定了。
不要以为"initial containing block"名字听起来很厉害,就确定会将全部内容包含在其区域内。就像其余普通的containing block,页面中的内容彻底能够溢出它。好比绝对定位、overflow:visible。
例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>test viewport</title> <style> * { padding: 0; margin: 0; box-sizing: border-box; } .box { width: 100%; height: 200px; background-color: greenyellow; } .out { position: absolute; right: -30px; background-color: rosybrown; } </style> </head> <body> <div class="box">box</div> <div class="out">out</div> </body> </html>
其中div.out就溢出了initial containint block的区域。
因为有内容溢出了visual viewport,所以在visual viewport上出现了横向滚动条。visual viewport上的滚动条在css溢出机制探究中讨论。
缩放、调整浏览器窗口大小的时候,会改变visual viewport的尺寸(用可容纳的CSS像素数量来衡量):
在桌面浏览器中,layout viewport(initial containing block)始终保持与visual viewport尺寸相同(这是为了防止出现横向滚动条,见我上一篇文章对page zoom的解释),所以当你经过缩放、调整浏览器窗口大小来改变visual viewport的大小时,layout viewport(initial containing block)也会随之改变。
好比,你在桌面端增大缩放比例,visual viewport会缩小,initial containing block随之缩小,这就是为何咱们在桌面端缩放可能会形成布局错乱。(顺便提一下,这个问题的简单解决方案是在HTML元素上设置min-width,防止HTML元素跟着initial containing block一块儿变小,不过会出现横向滚动条。复杂解决方案:移动端适配)
例子+注释:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>test</title> <style> * { padding: 0; margin: 0; box-sizing: border-box; } html, body, main { /* 对于block元素其实能够省略width: 100%。 放在这里只是为了强调一下,经过级联的width:100%,main的宽度始终等于visual viewport的宽度。 若是你缩小浏览器窗口的宽度,main的宽度(以CSS像素或设备独立像素为单位)也会(响应式地)减少,从而会增长更多的换行以便容纳内部的div.ilbk。 若是你增长缩放比例(经过Ctrl+鼠标滚轮),main的宽度(以CSS像素为单位)也会(响应式地)减少,从而会增长更多的换行以便容纳内部的div.ilbk。 */ width: 100%; } .ilbk { display: inline-block; width: 200px; height: 50px; background-color: aquamarine; } </style> </head> <body> <main> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> <div class="ilbk"></div> </main> </body> </html>
以上例子中,经过级联的百分数宽度作到了响应式宽度,即,元素的宽度由客户端的宽度动态决定(在这个例子中是<main>元素),而不是写死在CSS中。
用桌面浏览器打开以上例子,随便改变浏览器窗口大小、改变缩放比例,你会发现<main>的宽度(以CSS像素为单位)会随之改变:
在移动端浏览器,无论处于哪一种模式,无论有没有viewport meta tag,layout viewport的尺寸(以CSS像素为单位)在页面加载之后就固定了。不管用户如何缩放、调整浏览器窗口大小(这在手机上彷佛作不到),layout viewport的尺寸都不会改变。
所以,无论你在移动端浏览器如何缩放,页面布局都不会改变。
“layout viewport的尺寸在页面加载之后就固定了”,这个概括有一个例外:用户能够在加载好页面之后切换横屏、竖屏模式,从而meta viewport tag中的device-width发生改变,从而layout viewport宽度改变。
形成以上不一样的缘由是,在桌面端的缩放和在移动端的缩放有不一样的性质。见我在上一篇文章的讨论。
使用media query查询width、height的时候(好比@media screen and (max-width: 500px) {...}
),查到的是layout viewport的尺寸,而且px指的是CSS像素。在桌面端和移动端都是如此。
MDN 文档也指出了这一点:... if the virtual viewport(也就是这里所说的layout viewport) is 980px for example, media queries that kick in at 640px or 480px or less will never be used, limiting the effectiveness of such responsive design techniques.
例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>test1</title> <style> * { padding: 0; margin: 0; box-sizing: border-box; } html, body, main { /* 对于block元素其实能够省略width: 100%。 放在这里只是为了强调一下,经过级联的width:100%,main的宽度始终等于visual viewport的宽度。 若是你缩小浏览器窗口的宽度,main的宽度(以CSS像素或设备独立像素为单位)也会(响应式地)减少,从而会增长更多的换行以便容纳内部的div.ilbk。 若是你增长缩放比例(经过Ctrl+鼠标滚轮),main的宽度(以CSS像素为单位)也会(响应式地)减少,从而会增长更多的换行以便容纳内部的div.ilbk。 */ width: 100%; height: 100%; background-color: aquamarine; } @media screen and (max-width: 500px) { main { background-color: purple; } } </style> </head> <body> <main> </main> </body> </html>
这个例子中,在桌面浏览器,经过改变浏览器窗口大小或者改变缩放比例,都能形成媒体查询结果的改变。前面已经解释过了,这两个操做都会形成layout viewport尺寸的改变。
为了让读者明白meta viewport、媒体查询出现的缘由,这里举一个例子:
有不少网站没有针对移动端进行优化。对于这些网站,若是在移动端上将layout viewport的尺寸设置为visual viewport的尺寸(宽度为360CSS像素左右),那么排版可能会彻底乱掉(意料以外的换行、溢出)。为了能正确显示这种网站的排版,若是没有meta viewport的指示,移动端浏览器将layout viewport的尺寸设为与电脑浏览器同样,好比980px(单位:CSS像素)。因为手机的屏幕逻辑像素宽度通常只有300~400逻辑像素,所以须要将多个css像素由1个逻辑像素显示(也就是缩小,不要忘记缩放比例=css像素边长/逻辑像素边长
),经过缩小css像素让手机屏幕显示的css像素与网页的css像素同样多。
可是这会引起一个问题:字体小得难以阅读。用户阅读的时候又不得不用手指将缩放比例调整到100%左右(一个设备独立像素显示一个css像素,对于个人手机来讲,水平方向只有360个设备独立像素),这个时候visual viewport只显示layout viewport的一部分了。阅读的时候须要横向、纵向滚动。
虽然可以阅读网站内容,但这依然是一种很是差的用户体验。
适配移动端的时候,先使用<meta name="viewport" content="width=device-width, initial-scale=1.0">
来定义layout viewport的宽度,而后经过媒体查询来为不一样的layout viewport定义不一样的CSS排版。如下是浏览的效果(使用“查看移动版网站”模式):
如今的字体大小合适了,网页的排版变化了,没有元素横向溢出,没有横向滚动条,在移动端上的阅读体验更好。
上一篇文章说过的screen.width/height:整个屏幕的宽度和高度。这两个数值的单位是设备独立像素。这两个数值不随页面缩放、浏览器窗口大小而改变,在前端开发的过程当中能够认为是固定不变的(除非你经过操做系统改变屏幕的分辨率)。这两个数值是操做系统决定的,因为设备独立像素:设备像素常常不等于1:1,实际屏幕物理像素的分辨率不必定是screen.width×screen.height。
在上图中列出了iphone各代的设备分辨率(物理分辨率)和逻辑分辨率,咱们只须要看这两行。
设备分辨率就是屏幕上的物理像素的数量,当手机厂商宣传本身的屏幕有多么清晰锐利的时候,相互攀比的就是这个数值。
逻辑分辨率就是screen.width/height。为何iphone3GS之后的iphone都要把这个值设为实际屏幕分辨率的1/2或1/3呢?由于随着屏幕上塞进愈来愈多的物理像素,屏幕大小的变化却不那么明显,所以像素密度也愈来愈高。若是还让逻辑分辨率:真实屏幕分辨率=1:1,那么12px的字体就会愈来愈小,影响阅读体验。所以,后续的iphone用4个物理像素(甚至9个像素)组合成一个“逻辑像素”。这样,即便物理像素愈来愈小,每个“逻辑像素”的大小变化不大。浏览器能够放心地使用逻辑像素来衡量大小,而不用担忧真实大小在不一样的显示器上出现严重误差。
visual viewport的大小,也就是浏览器内容窗口的大小,不包括菜单栏、地址栏、状态栏等,可是包括滚动条。单位是CSS像素。经过这个属性你能够知道,当前的浏览器窗口能够容纳多少个css像素。当用户放大的时候这个数值会减小(由于css像素变大了),当用户缩小的时候这个数值增大。缩放改变浏览器窗口都会改变这个属性的值。
与之对应的,window.outerWidth/outerHeight给出整个浏览器窗口的大小(包括各类栏),可是单位是 设备独立像素。
Layout Viewport(initial containing block)的尺寸。注意,Layout Viewport没有滚动条(根据css溢出机制探究中的讨论,只有元素或者visual viewport才能拥有滚动条)。单位是CSS像素。
document.documentElement指的是html元素,一般 Element.clientWidth应该给出元素的内容区域的大小,可是document.documentElement.clientWidth/Height并不衡量html元素的大小,这是一个特例。各个浏览器都遵循着这个约定。而且, 这个约定正在被标准化。
<html>元素的尺寸。前面已经讨论过<html>元素的尺寸是如何计算的了,默认状况下<html>的宽度始终与Layout Viewport宽度相同。单位是CSS像素。<html>元素的高度由内容撑开。
滚动距离,描述visual viewport已经向右、向下滚动了多少个像素。也能够理解为visual viewport相对于layout viewport的偏移值。单位是CSS像素。
它们分别有1个别名(前者的兼容性更好些):
window.pageXOffset == window.scrollX; // always true window.pageYOffset == window.scrollY; // always true
此外,因为Element上就有获取内容滚动的scrollLeft、scrollTop属性(全部Element均可以使用),所以还有:
window.pageXOffset === document.documentElement.scrollLeft; // always true window.pageYOffset === document.documentElement.scrollTop; // always true
当用户进行缩放的时候,浏览器会 尽可能保证:原先在内容区顶部的元素,在缩放之后依然在内容区顶部,看如下例子:
放大前:
![]()
放大后:
![]()
本来数字3在顶部,放大后3依然在顶部。window.pageYOffset 大体相同。大体相同的缘由是CSS像素数量不随着缩放而变化, 本来在上方的内容高度有多少个CSS像素,放缩之后依然是多少个CSS像素。至于 为何不是彻底相同,是由于 "原先在内容区顶部的元素,在缩放之后依然在内容区顶部"这一机制没法完美地作到。
一些比css2.1更新的文档(可是尚未正式做为Recommondation规范):