转: 关于viewport的理解
最近我做了一点儿针对手机的Web开发和相关研究。按说,Web自设计之初,就已经考虑了设备无关性。然而,现实总是不尽如人意。
我们知道大多数网页都是针对桌面显示器开发和测试的,但是手机屏幕通常要比桌面显示器小很多,比如iPhone也就是320px。那么那些网页在手机上如何浏览呢?
一种是把网页缩放到很小,你可以看到整个网页但是看不清字了;或者只看网页的一个局部,然后上下左右移动来看其他部分。现在的手机浏览器把两种模式结合使用。(或许还有第三种——分析网页,将其中内容抽取出来,重组,然后呈现,不过这里不讨论这种方式。)
如果仅仅是出现横向滚动条,那问题倒不是很大。对于那些采用固定布局的“像素级精确设计”确实就是如此。但是使用自适应布局的网页就有问题了。比如一个三栏布局,其中一个边栏可能只有20%宽度,320px的屏幕上只有60多像素,只能放5个汉字,排版不美观不要说,如果里面有一些图片,很可能图片的宽度都超过60像素,造成overflow,从而破坏了布局。另一种常见布局方式是采用固定大小的边栏,例如240px宽,而主体部分则自适应宽度。然而对于320px的屏幕来说,本来是次要的边栏占据了3/4空间,主要部分却只有1/4空间,另外也极有可能因为内容包含图片等造成overflow。
手机浏览器厂商发明了两个viewport。
简单来说,就是手机浏览器把自己冒充为拥有一个更宽的屏幕(~~layout viewport),这样页面的布局就能跟桌面浏览器一样了。不同浏览器冒充的数值不一样:iPhone是神奇的980——960它表弟;Android是保守的800——冒充一个800*600的显示器;而Windows Phone 7则是1024——冒充一个1024*768的显示器(充分体现微软的庸俗特质)。
原本布局就是按照layout viewport(桌面上的浏览器窗口)大小来进行的,现在决定布局的layout viewport和窗口分裂了,产生了两个viewport。
~~~在桌面浏览器中 layout viewport 和 visual viewport是一样的都是 浏览器窗口可见区域的宽高,也可以说桌面浏览器没有2个viewport的概念
两个viewport其实并不新鲜。就像前面说的,所有做固定布局的同学其实都在不自觉使用两个viewport——固定布局,其实相当于人为设定了布局viewport。
使用独立的布局viewport还有一个好处,那就是缩放变得很简单,不用引起relayout。
在浏览器的历史上,主要有两种不同的缩放方式,一种是以IE6为代表的缩放(文字大小),实质是改变root元素的字体大小。另一种是像素缩放,实质是改变CSS pixel的大小(即1个css像素不再对应1个物理像素),或者说改变了浏览器的内部DPI。(Firefox还支持真正的字体缩放,与改变root元素的字体大小不同,它会缩放所有元素的font-size最终使用的值(used value),因此不仅对于以em设定的字体大小有效,对于以px和pt等所有单位设定的字体大小都有效。)
在桌面浏览器上,因为缩放时viewport的物理大小是固定的,如果进行像素缩放,实际就意味着viewport以CSS pixel计算的宽度和高度会随之改变,比如若放大到200%,CSS宽高就会缩小到原来的50%。而这必然引起relayout。
相反,在mobile浏览器上,像素缩放时,布局viewport的物理大小可随之缩放,因此其CSS宽高值是不变的,就不必引起relayout。这不仅避免了relayout的大量计算,也更符合移动设备上的用户体验(考虑一下你如何缩放地图就明白了)。
虽然两个viewport能比较好的解决在手机上浏览过往网页的问题,然而,这毕竟是一个向后看的方案,体验毕竟是受限的。想象在320宽的窗口里看960固定布局的网页,不可避免的要时常缩放和使用横向滚动条。在触摸屏上我们可以用手势来控制,会方便一点,但是仍然很麻烦。所以绝大多数移动Web开发者最终还是选择针对小屏幕(重新)进行设计。
不过一个显然的问题是,如果我的网页已经考虑了小屏幕,甚至就是专门为手机设计的,那么我其实不需要两个viewport(也不需要缩放),这时候怎么办?更一般的问题是,如果我设计的网页不是针对960或者800或者1024的呢(至少你选了960,就排除了800和1024;你选了800,就排除了960和1024;你选了1024,就排除了800和960……)?
于是Apple发明了viewport的meta标签,例如:
<meta name="viewport" content="width=320, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
其中width表示网页的布局layout宽度。initial-scale表示初始时的缩放比例,minimum-scale和maximum-scale分别表示最小和最大缩放比例。这样,上面这个meta就表示布局宽度320像素,初始缩放为1倍(即不缩放),且禁止用户缩放(因为最大最小缩放都为1倍)——一个专为iPhone优化的网页通常就会用这样的设置。
如果你是针对960设计的,那么可以用这样一个meta:
<meta name="viewport" content="width=960, initial-scale=0.33">
这表示布局宽度为960像素,初始缩放为0.33,也就是,会缩小到大约1/3,这样正好可以在320像素的宽度里看到整个网页。你也可以不设initial-scale,因为手机浏览器大多默认会初始缩放到可容纳整个网页宽度。
嗯,看上去不错吧。
可是,说到底,手机浏览器的这种设计,实际上揭示了一个难堪的真相——你们这些网页仔,其实从来没真正做好过屏幕适应。因为一个能自动完美适应不同屏幕大小的“正确”方案,width的取值就不应该是一个固定数字。幸好,safari的工程师在发明viewport时还是给我们留了点面子,width的取值除了像320或960这样的数字,也可以是关键字device-width(其实我认为更合适的词是auto),表示采用设备宽度。
然而,历史总是杯具的重复自己。微软就又(嗯,我为什么要说“又”呢?)贡献了这样一个例子:Window Phone 7的设备宽度通常至少是480,那么按理说,如果viewport中设置了width=device-width,layout宽度应该是480像素,可是IE mobile会将viewport设为320!这是神马原因呢?
原来据说微软收集的数据表明,有大量站点使用了device-width,但是其实只为320像素宽(也就是iPhone的宽度)优化——比如直接用一个固定宽度320px的<div>将内容wrap起来!我勒个去,诸位移动Web开发者,你们有木有这么干?有木有!
那后面的故事就不用说了。微软当然是做出了一个“正确”的选择。
另外需要注意的是,width只是设置layout宽度,还要乘上缩放比例,才能得到最终的显示宽度。那么对于480像素的屏幕来说,若device-width“被320”,那initial-scale应该是1.5才能占满整个屏幕宽度。可是如前所述,针对性优化的网页会把initial-scale设成1.0!
对此微软是如何处理的呢?文档语焉不详,而其模拟器要在Windows 7上才能运行,所以我也暂时没有进行实测,不过据我推测,其initial-scale应该仍旧会保持1.0,也就是这1.5会变成额外的缩放因子。实际上其他移动浏览器已经这样做了,比如Fennec(Firefox的移动版)就是如此。Android和iPhone 4也都有类似的设计,为什么会这样?这样会不会产生新问题?留待下一篇博客再讨论。
------------------------------------------------
在响应式设计或移动Web开发当中经常见到的一句代码:
<meta name=”viewport” content=”width=device-width” >
content属性还包括initial-scale,user-scalable等,不过这里不谈,它们的意思都很容易理解。这里要谈得是:viewport代表什么?device-width又是啥?
先来理解两个概念:device pixels与CSS pixels。
device pixels指设备的物理像素,在PC端就是你在操作系统里设置的屏幕分辨率y,其值可以通过 screen.width/screen.height
获取。在移动端下面再说。
CSS pixels指在CSS文件中设置的字体大小、元素宽度等,如font-size: 14px;
width: 100px;
。在PC端,浏览器缩放比例为100%,也即默认情况下,1 CSS pixel = 1 device pixel。
当你放大页面到200%时,字体大小与元素宽度的像素值不会改变,是因为这些像素值是用CSS pixels表示的,实际上放大的是CSS pixels,此时 1 CSS pixel = 4 device pixels,高和宽都是200%。此时你获取 screen.width/screen.height
的值,并没有变化,而 window.innerWidth
和 window.innerHeight
的值变成了原来一半,是因为 window.innerWidth/window.innerHeight
的值也是用CSS pixels来表示的。
当你进行流式布局时,会用百分比设置元素的宽度,比如一个块级元素宽度为10%,那么你也知道10%实际上是父级元素宽度的10%。但是你并没有设置父级元素的宽度啊,好吧,你也知道父级元素的宽度与其父级元素宽度一样(通过继承得来,假设这些元素都是块级元素)。然后向上到body元素的宽度,最终为html元素的宽度,其值可以通过 document.documentElement.clientWidth
获取。那这个宽度怎么来得呢?
Viewport
viewport,翻译为视口,也即可视区域的大小,PC端通过window.innerWidth
和 window.innerHeight
获取。
html元素也即文档的宽度,来自于viewport的宽度,在PC端要加上滚动条的宽度才会与viewport的宽度一样。因此,文档的宽度最终来自于viewport的宽度,PC端通过 window.innerWidth
获取。
而在移动端,情况将变得复杂。
首先,上面提到文档的宽度来自于viewport的宽度,我们把这个viewport称为layout viewport,因为它和布局有关。在手机上面,因为手机的屏幕很小,当初iphone发布时,为了显示完整的桌面网页,就把给layout viewport设置了一个980px的值。手机上,可以通过 document.documentElement.clientWidth
来获取,我在安卓手机上测试也是980px。
但是这样显示网页,那网页的字体、元素都很小,小到打开这样一个网页,首先要做的就是放大页面。为了提高可读性,Apple允许通meta标签来设置layout viewport的宽度,也即文章开头的那行代码。
但是,device-width又是什么呢?
第一代iphone的分辨率为320*480,屏幕尺寸为3.5寸。当时把layout viewport设置成与浏览器宽度一样(而手机上浏览器宽度与手机屏幕宽度一样)时,不用每次打开网页放大了,而且显示的字体与桌面上差不多,可读性很好。因此就定义了一个device-width,即是手机的屏幕分辨率,此时device翻译为“设备”还合适。
但是第二代iphone发布时,屏幕的分辨率变成了480*960,而屏幕尺寸仍然为3.5寸,如果device-width仍然为手机的屏幕分辨率宽度,那么字体将会比第一代小很多。所以,维持device-width的值不变将会是个很好得选择,能与前面兼容。也因此,iphone上的device-width的值一直为320,只不过device再表示“设备”已经不合适了,实际上代表的是一个中间层。而Android也采用了这一概念,其device-width的值为360的多,360=540/1.5,360=720/2。
如何获取device-width的值呢?
浏览器并没有提供一个获取device-width的属性或方法,但是通过window.innerWidth
可以获取,需要注意的是,必须添加文章开头那行代码才可以跨浏览器获取。如果不添加那行代码,我自己在HTC G18/ Andoird OS 4.0.3中测试,自带浏览器/UC9.6/QQ5.0可以获取,而在Chrome33和Opera20中通过screen.width可以获取。iPhone与iPad我没测试。这是测试网页地址:
http://www.xiaocaoge.com/demo/viewport-screen-device-width-mobile.html。
Chrome与Opera比较深入实现了中间层的概念,屏幕的实际分辨率与Web开发关系并不大,Chrome与Opera就将 screen.width
返回中间层的宽度。这里我也不明白哪种设计更好些。
这里 有个链接 可以查看各种手机型号的device-width/device-height大小,虽然链接称为viewport size。
----------------------------------
看了以上的参考资料对于 <meta name="viewport" content="width=divice-width, initial-scale=1.0, target-densitydpi=hight-dpi" />感到还是不怎么清晰。
设备的屏幕宽度 == 浏览器的视口宽度
浏览器的默认layout viewport 一般都比屏幕宽度要大,模拟桌面环境的分辨率,没有设置 <meta name="viewport" ... />时,layout viewport采用默认宽度(960px 800px...因设备而异, px为 css pixel),
比如说一个320像素宽的手机(假如默认的layout viewport为800),为了在320像素中能容纳800px的内容,初始缩放会变为 320/800, 1css pixel被缩小为了320/800 css pixel, 所以一个800像素宽的页面能被完整显示在手机屏幕上,但是内容都缩小了,如原来css定义的12px字体,变为 12 * 320/800 px;
当我们在 <meta name="viewport" content="width=divice-width, initial-scale=1.0 " />
initial-scale, minimum-scale, maximum-scale 其实缩放的是 css pixel.
以上为自己的一些理解,不一定正确
target-densitydpi是做什么的呢,有待研究。
------------------------------
关于target-densitydpi
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" >
但发现页面依旧不根据手机屏幕进行自动缩放,后来找其他同事帮忙解决了,我看了源代码发现了是在原来的viewport中增加了target-densitydpi属性。看之初不太了解,也因为当时自己手上有其他需求在处理,最近突然想起了这件事,准备对它做一些了解,以备不时之需。
首先来了解一下这个属性到底是做什么的,从网上摘录了一段简介,如下:
一个屏幕像素密度是由屏幕分辨率决定的,通常定义为每英寸点的数量(dpi)。Android支持三种屏幕像素密度:低像素密度(~~120dpi),中像素密度(~~160dpi),高像素密度(~~240dpi)。一个低像素密度的屏幕每英寸上的像素点更少,而一个高像素密度的屏幕每英寸上的像素点更多。Android Browser和WebView默认屏幕为中像素密度。
下面是 target-densitydpi 属性的 取值范围
- device-dpi –使用设备原本的 dpi 作为目标 dp。 不会发生默认缩放。
- high-dpi – 使用hdpi 作为目标 dpi。 中等像素密度和低像素密度设备相应缩小。
- medium-dpi – 使用mdpi作为目标 dpi。 高像素密度设备相应放大, 像素密度设备相应缩小。 这是默认的target density.
- low-dpi -使用mdpi作为目标 dpi。中等像素密度和高像素密度设备相应放大。
- <value> – 指定一个具体的dpi 值作为target dpi. 这个值的范围必须在70–400之间。
注:以上信息取自http://www.php100.com/html/webkaifa/HTML5/2012/0831/10979.html
上面的信息我也从android官网找到了相关资料,这个资料在上面URL页面中都进行了翻译!
http://developer.android.com/guide/webapps/targeting.html
从这段简介可以得到如下信息:
1、它是指的屏幕分辨率,现在绝大多数智能手机屏幕都是可以通过viewport中的width来调整页面宽度的,而是浏览器通过width值来进行计算得出。
2、target-densitydpi这个属性只对android系统起作用,专有属性。IOS不支持它,所以说起来,还是有兼容性问题,如果页面是兼容IOS和android的话。