深入浅出viewport
开发移动网页时,你一定会遇到下面这段代码:
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=2.0, user-scalable=yes" />
在网页的<head>中增加以上这句话,可以让网页的宽度自动适应手机屏幕的宽度。其中:
width=device-width :表示宽度是设备屏幕的宽度
initial-scale=1.0:表示初始的缩放比例
minimum-scale=0.5:表示最小的缩放比例
maximum-scale=2.0:表示最大的缩放比例
user-scalable=yes:表示用户是否可以调整缩放比例
懂了吗?没懂?!好,继续往下看,保证你懂。
设备像素和CSS像素
你需要明白的第一个概念是CSS像素,以及它和设备像素的区别。
设备像素是我们直觉上觉得「靠谱」的像素。这些像素为你所使用的各种设备都提供了正规的分辨率,并且其值可以(通常情况下)从screen.width/height属性中读出。
如果你给一个元素设置了width: 128px的属性,并且你的显示器是1024px宽,当你最大化你的浏览器屏幕,这个元素将会在你的显示器上重复显示8次(大概是这样;我们先忽略那些微妙的地方)。
如果用户进行缩放,那么计算方式将会发生变化。如果用户放大到200%,那么你的那个拥有width: 128px属性的元素在1024px宽的显示器上只会重复显示4次。
现代浏览器中实现缩放的方式无怪乎都是「拉伸」像素。所以,元素的宽度并没有从128个像素被修改为256个像素;相反是实际像素被放大了两倍。形式上,元素仍然是128个CSS像素宽,即使它占据了256个设备像素的空间。
换句话说,放大到200%使一个CSS像素变成为一个设备像素的四倍。(宽度2倍,高度2倍,总共4倍)
一些配图可以解释清楚这个概念。这儿有四个100%缩放比的元素。这儿没有什么值得看的;CSS像素与设备像素完全重叠。
1现在让我们缩小。CSS像素开始收缩,这意味着现在一个设备像素覆盖了多个CSS像素。
2如果你进行放大,相反的行为会发生。CSS像素开始变大,现在一个CSS像素覆盖了多个设备像素。
3这儿的要点是你只对CSS像素感兴趣。这些就是那些控制你的样式表如何被渲染的像素。
设备像素对你(译者:指的是开发者)来说基本上没用。但是对于用户不一样;用户将会放大或者缩小页面直到他能舒服的阅读为止。无论怎样,缩放比例对你不会产生影响。浏览器将会自动的使你的CSS布局被拉伸或者被压缩。
100%缩放
我是以假设缩放比例为100%来开始这个例子的。是时候需要更加严格的来定义一下这个100%了:
在缩放比例100%的情况下一个CSS像素完全等于一个设备像素。
100%缩放的概念在接下来的解释中会非常有用,但是在你的日常工作中你不用过分的担心它。在桌面环境上你将会在100%缩放比例的情况下测试你的站点,但即使用户放大或者缩小,CSS像素的魔力将会保证你的布局保持相同的比率。
为桌面设计的网页在手机上如何显示?
先说一下PC web的两种布局,一般的网页如果用固定布局都会定义好整个页面的宽度,常见的宽度是980px,当屏幕分辨率的宽度大于980px的时候,如:1024768,页面就居中,两边留白;如果屏幕分辨率小于980px的时候,如:800600,页面就会出现横向的滚动条,这应该是所有前端开发人员都不希望出现的,所幸的是目前大多数显示器的屏幕分辨率都是1024*768以上的,所以宽度为980px的固定布局是安全又放心的。而如果用流动布局做网页的话一般要自应适不同的分辨率满屏显示以让内容区域达到最大化,流动布局的例子有很多,如邮箱、博客园等等。
在显示面积上手机屏幕相对桌面显示器要小很多,在几年前(现在也如此)大部分网站都是为桌面显示器浏览而设计,很少考虑到适应手机屏幕,所以如果用手机浏览大多网站时会出现问题,比如常见固定宽度的网页会出现横向竖向滑动条,当然这不算什么大问题;但如果是浏览流动布局的网页那情况会非常糟糕,设想一个宽度为 30% 的侧边栏对于 320px 手机屏幕而言也就 96px,只能容纳八个 12px 的汉字,可阅读性非常差。
接下来说说手机上打开一个PC web页面,用手机打开一个宽度为980的固定布局页面,页面会默认缩放到刚好满屏显示,并不会出现横向滚动条,这个现象并不让我感到奇怪,我认为这是手机厂商的一些设定造成的,但关键是做了什么“手脚”,后来网上查阅了很多资料知道这是因为浏览器的两个viewport:layout viewport & visual viewport
手机浏览器在显示网页时,会在设备屏幕(设备分辨率)上创建一个980px(css像素)的虚拟窗口,然后使用该虚拟窗口显示网页,所以网站会缩小显示。一般把这个虚拟窗口称为layout viewport。
说白了其实就是把980px的CSS像素装进了320px(假设手机分辨率是320px)的设备像素下。
layout viewport的默认值
在Apple实现viewport后,其他浏览器也加入了对viewport meta的支持,但彼此间还是有些差异,差异最大的是layout viewport的表现:
Safari iPhone: 980px
Opera: 850px
Android WebKit: 800px
IE: 974px
为什么要使用viewport?
有了 layout viewport 似乎解决手机浏览网页的难题,但如果遇到专门为手机优化的网页就又有新的问题:
4是的,因为 iPhone 的 layout viewport 默认为 980px,导致专为其优化的 320px 宽的页面只能以缩放的方式显示,这时就需要对 viewport 进行设置:
<head>
...
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
...
</head>
这个是最常见的一条 viewport meta 代码,将 viewport 定义为:宽度为设备宽度,初始缩放比例为 1 倍,禁止用户缩放。设置好后我们的页面就显示完美了:
4一般的桌面端网页都不会添加 viewport 设置,用智能手机查看这些页面时,是在layout viewport下显示的。
width=device-width
因为大多数情况下,<meta name="viewport" content="width=device-width" /> 这个标签对我们来说是最给力的,可以让我们的页面里的图文显示的是正常的,很大程度上提高了页面的可读性。而这一meta标签的功能就是设置layout viewport为device-width的宽度。但是device-width具体是什么呢?
第一代iphone的时候,分辨率为320480,屏幕尺寸为3.5寸(注意,这个3.5寸说的是屏幕的对角线宽),这时候device-width就是320px,也是手机的分辨率宽,此时device-width就是设备宽。但第二代的iphone分辨率提高为了480960,屏幕尺寸为依然为3.5寸,如果device-width还是设备宽,那么同样是320px的页面放480960的手机屏上,图文就会变得比较小,又会影响其可读性。因此iphone的device-width一直维持在320px,ipad一直维持在1024px。这个时候,device-width就不是设备宽了(也就不是分辨率的宽了),是一个中间层。Android采用的也是这一概念,其device-width值以360居多,但也不乏有像540px和600px这样的奇葩。在设置了<meta />标签以后,device-width值可以用window.innerWidth*来获取device-width值。
获取device-width
经实践,在手机端(sony z2)以下两种方法都可以获取device-width的值:
1. window.innerWidth/innerHeight
2. document.documentElement.clientWidth/clientHeight
//在sony z2下获取到的值均为(360,513)
引用自Viewport 不权威指南:
<blockquote>
其实这个属性值很有意思,字面意应该是(layout) viewport 宽度等于设备宽度,但在实际中不同的浏览器都给出了个定值:320px;这个值还是源于 Apple,因为早期 iPhone 的分辨率为 320px × 480px,大量为 iPhone 量身定制的网站都设置了width=device-width,并且按照宽度 320px 来设计制作,所以其他浏览器加入 viewport 支持时为了兼容性也将 device-width 定义为了 320px。
</blockquote>
initial-scale=1
你肯定不知道
<meta name="viewport" content="initial-scale=1">
与
<meta name="viewport" content="width=device-width">
这两句代码能达到一样的效果,也可以把当前的的viewport变为 ideal viewport。
呵呵,傻眼了吧,因为从理论上来讲,这句代码的作用只是不对当前的页面进行缩放,也就是页面本该是多大就是多大。那为什么会有 width=device-width 的效果呢?
要想清楚这件事情,首先你得弄明白这个缩放是相对于什么来缩放的,因为这里的缩放值是1,也就是没缩放,但却达到了 ideal viewport 的效果,所以,那答案就只有一个了,缩放是相对于 ideal viewport来进行缩放的,当对ideal viewport进行100%的缩放,也就是缩放值为1的时候,不就得到了 ideal viewport吗?事实证明,的确是这样的。
测试结果表明 initial-scale=1 也能把当前的viewport宽度变成 ideal viewport 的宽度,但这次轮到了windows phone 上的IE 无论是竖屏还是横屏都把宽度设为竖屏时ideal viewport的宽度。但这点小瑕疵已经无关紧要了。
但如果width 和 initial-scale=1同时出现,并且还出现了冲突呢?比如:
<meta name="viewport" content="width=400, initial-scale=1">
width=400表示把当前viewport的宽度设为400px,initial-scale=1则表示把当前viewport的宽度设为ideal viewport的宽度,那么浏览器到底该服从哪个命令呢?是书写顺序在后面的那个吗?不是。当遇到这种情况时,浏览器会取它们两个中较大的那个值。例如,当width=400,ideal viewport的宽度为320时,取的是400;当width=400, ideal viewport的宽度为480时,取的是ideal viewport的宽度。(ps:在uc9浏览器中,当initial-scale=1时,无论width属性的值为多少,此时viewport的宽度永远都是ideal viewport的宽度)
initial-scale的默认值
好了,现在再来说下initial-scale的默认值问题,就是不写这个属性的时候,它的默认值会是多少呢?很显然不会是1,因为当 initial-scale = 1 时,当前的layout viewport宽度会被设为 ideal viewport的宽度,但前面说了,各浏览器默认的 layout viewport宽度一般都是980啊,1024啊,800啊等等这些个值,没有一开始就是 ideal viewport的宽度的,所以 initial-scale的默认值肯定不是1。安卓设备上的initial-scale默认值好像没有方法能够得到,或者就是干脆它就没有默认值,一定要你显示的写出来这个东西才会起作用,我们不管它了,这里我们重点说一下iphone和ipad上的initial-scale默认值。
根据测试,我们可以在iphone和ipad上得到一个结论,就是无论你给layout viewpor设置的宽度是多少,而又没有指定初始的缩放值的话,那么iphone和ipad会自动计算initial-scale这个值,以保证当前layout viewport的宽度在缩放后就是浏览器可视区域的宽度,也就是说不会出现横向滚动条。比如说,在iphone上,我们不设置任何的viewport meta标签,此时layout viewport的宽度为980px,但我们可以看到浏览器并没有出现横向滚动条,浏览器默认的把页面缩小了。根据上面的公式,当前缩放值 = ideal viewport宽度 / visual viewport宽度,我们可以得出:
当前缩放值 = 320 / 980
也就是当前的initial-scale默认值应该是 0.33这样子。当你指定了initial-scale的值后,这个默认值就不起作用了。
总之记住这个结论就行了:在iphone和ipad上,无论你给viewport设的宽的是多少,如果没有指定默认的缩放值,则iphone和ipad会自动计算这个缩放值,以达到当前页面不会出现横向滚动条(或者说viewport的宽度就是屏幕的宽度)的目的。
兼容性
<meta name="viewport" content="width=device-width">
可以看到通过width=device-width,所有浏览器都能把当前的viewport宽度变成ideal viewport的宽度,但要注意的是,在iphone和ipad上,无论是竖屏还是横屏,宽度都是竖屏时ideal viewport的宽度。
<meta name="viewport" content="initial-scale=1">
测试结果表明 initial-scale=1 也能把当前的viewport宽度变成 ideal viewport 的宽度,但这次轮到了windows phone 上的IE 无论是竖屏还是横屏都把宽度设为竖屏时ideal viewport的宽度。
最后,总结一下,要把当前的viewport宽度设为ideal viewport的宽度,既可以设置 width=device-width,也可以设置 initial-scale=1,但这两者各有一个小缺陷,就是iphone、ipad以及IE 会横竖屏不分,通通以竖屏的ideal viewport宽度为准。所以,最完美的写法应该是,两者都写上去,这样就 initial-scale=1 解决了 iphone、ipad的毛病,width=device-width则解决了IE的毛病:
<meta name="viewport" content="width=device-width, initial-scale=1">
viewport的自动调整
其实 viewport 能够自动调整的。
- 当 width=320 时,横屏后的 viewport 宽度仍然是 320px ,但可见区域宽度为 480px ,因此 viewport 要放大为 320 的 1.5 倍,把 320px 的页面显示到 480px 的屏幕上,从而页面显示的效果就被放大了。
- 而当 width=device-width 时,横屏后的 viewport 宽度变为了 480px ,此时的 viewport 没有缩放, initial-scale 值仍然为 1 ,所以页面没有经过缩放。
viewport 自动调整特性:为了让页面适应 viewport 的显示区域进行显示,浏览器会自动根据已设置值的属性调整其它未设置值的属性值。
<blockquote>
If you set only some of the properties, then Safari on iOS infers the values of the other properties with the goal of fitting the webpage in the visible area.
</blockquote>
这些可相互影响的属性值主要包括:width、height、initial-scale。
为什么不制作固定尺寸的页面,让浏览器自己去缩放
http://segmentfault.com/q/1010000002551392/a-1020000002551691
固定尺寸的页面:
<meta name="viewport" content="target-densitydpi=device-dpi,width=640,user-scalable=no" />
另外也有这样的写法。这是用js的方式。
var phoneScale = parseInt(window.screen.width)/640;
document.write('<meta name="viewport" content="width=640, initial-scale = '+phoneScale+', maximum-scale = '+phoneScale+', maximum-scale = '+phoneScale+', target-densitydpi=device-dpi">');
}
这两个会让浏览器去缩放。你也知道了,这是缩放。既然是缩放,那么就会失真,这是由于浏览器的自身渲染导致的。你发的网页,我用nexus4测了一下,虽然不是太过明显,但里面的灰色线条会有粗细不一致的问题,也就是说在某些分辨率也会产生几条1px的直线看起来不一样粗的情况。这个问题进一步的引申,就变成了你无法准确的在页面上画出规整的直线。
在一些手机上,如果用了一些下载的字体,甚至会发虚(字体的问题你可以找资料仔细看看)。而且一些情况下会有轻微的撕裂和发糊现象,如果你用了部分CSS3的属性,发糊的现象可能会更严重,就是是楼上的说法。还有一个问题就是渲染带来的卡顿,生产中iPhone表现出了部分页面滑动不自然。我认为这是各个浏览器的实现不一样带来的渲染兼容问题,不知事实上如何。
我对这种方案的评价是
够用,但不完美
参考文献
Viewport 不权威指南
探索viewport:页面如何在手机上显示
移动前端开发之viewport的深入理解
一些关于Viewport与device-width的东西~
Mobile web开发日记
两个viewport的故事(第一部分)
两个viewport的故事(第二部分)
自适应网页设计(Responsive Web Design)
请教viewport的选择
网页宽度自动适应手机屏幕宽度的方法
这只是我参考以上文献后自己的理解,可能会有一些不正确的地方,欢迎指正。
QQ : 459135899