与Viewport有关的理解

2,279 阅读18分钟

前言

国内有关viewport的文章和信息,我想大多来源于PPK的文章(篇一篇二)。从二手资料获取到的信息总是不够深, 也有限,我就有很多地方没法理解,看完原文才理解地透彻,推荐你去看看。有时间我想把这两篇文章翻译出来,既锻炼翻译能力,又学习技术,一举两得;若是对大家还有点帮助,那就更好了。

一些重要基础知识

有些基础概念,大家看得很多,但未必真理解透了,有必要好好澄清。

由于一些术语翻译成中文,我觉得更容易混淆,所以保留英文名称,容易辨别和理解。

Device Pixel, CSS Pixel

Device Pixel

Device Pixel,说出来大家都知道,设备的物理像素。没错,它是指设备显示屏幕的实际像素。设备呈现画面是靠一些单颜色的色块显示的,这个色块在屏幕上就是一个一个小点,它们排列组合形成显示画面,这些在屏幕上实际显示的点就是设备像素。

设备的分辨率单位就是device pixel。比如,设备分辨率是320*480,表示横向有320个点,即320device pixel,纵向有480个点,即480device pixel

注意,320,不是长度单位,不是它有320那么宽,而是表示横向有320个点,至于这点有多大,那不同设备就不同样了。所以,640*960的设备不一定比320*480的宽,只是表明前者横向的点多些,密度大些。所以同样设备宽度,比如4.7英寸,分辨率可以是640*960,也可以是320*480。前者像素更多,能显示的图像信息更多,但我们看到的图像的宽度是一样的,只是前者清晰,后者画质差些。

CSS Pixel

CSS Pixel, 大家叫法不一样,有设备独立像素,相对像素,参考像素(reference pixel,这个叫法相对靠谱),懒得翻译了,就叫CSS像素,形象,因为这个像素就是指CSS中用的像素,适用于开发者的像素单位。之所以称为单位,是因为我觉得它是一种单位体制。开发网站时,我们用的是一套统一的单位,使得页面上各元素的布局和相对大小是固定的。

比如,我设定body的宽度是800px,里面有一个div,设定的宽度是400px,那么表现出来的效果就是这个div占了body的一半。不管你是在电脑上看,在平板上看,还是在手机上看,它始终都是占一半,不同的是,电脑上看着宽一些(电脑屏幕有二十几厘米吧)手机上看着窄一些(手机屏幕不到十厘米)。

你看,我设定的宽度是800px,但不同设备上的显示效果一样,这就像我们编程写了一个通用接口,接口的底层我不管,我只要最后通用的效果;又或者像Java的JVM,你写通用的java代码,至于运行在windows上还是Linux上,它们用的什么命令执行,你无需操心,我只关心在各个设备上的执行结果是否一致。

CSS pixel就像是这个通用接口或者Java代码,device pixel就是底层接口。所以CSS Pixel是一种与设备无关的开发者像素单位。

两者关系

各自的概念搞清楚了,再来看看两者的关系。

前面打了个比方,说CSS Pixel就像是一个接口,Device Pixel就像是底层,接口表现出效果,而不用管各种各样的底层。那么接口和底层的“适配”是怎么适配的呢?也就是说当要表现出800px(CSS pixel)的效果时,各设备怎么计算用多少实际的device pixel来呈现呢?

对于电脑端,很简单,所见即所得,也就是800px的CSS pixel显示出来就是800个device pixel。

手机端就复杂些,“适配”机制由手机厂家说了算。厂家根据自己的生产工艺,决定说,我这台4.7英寸的手机,对外显示的CSS像素是320*480(CSS pixel),也就是说,不管手机一行有多少个实际像素点,一行都表示320px(css pixel),这就是device pixel与css的“适配”机制。

呈现的方式就是,

  • 对于 320*480分辨率的手机,一行表示320px(css pixel),而手机一行有320个点(device pixel),因此刚好一个CSS pixel对应一个device pixel来显示;
  • 对于 640*960分辨率的手机,一行还是表示320px(css pixel),而这里手机的一行有640个点(device pixel),因此用2个点(device pixel)来显示1个px(CSS pixel)示;

下面的图例,css宽高都设置为2px:

  • 左图是1个css pixel对应1个device pixel,那么2px(css pixcel)的宽高,用2个device pixel显示;
  • 右图是1个css pixel对应2个device pixel,那么2px(css pixcel)的宽高,用4个device pixel显示。

Viewport

PPK定义了三种viewport:Layout viewport, Visual viewport, Ideal viewport,前两者我是认同的,第三种我认为应该定义为“厂家定义的对外适配视窗”更好(Adapt viewport),说理想视窗,反而让大家迷糊,原因后面解释。

Layout viewport

Layout viewport, 布局视窗(PS. 翻译过来总觉得变了味),它是用于承载我们的html文档的一个视窗容器,对,当成容器更容易理解。它有固定宽度,如果里面的html文档宽度比它的宽度还宽,就会出现横向滚动条;如果比它还窄,就会出现空白。

Layout viewport的宽高用document.documentElement.clientWidthdocument.documentElement.clientHeight获得(单位是CSS pixel,与web开发有关的px都是CSS pixel)。

document.documentElement就是<html>元素,获得<html>所在的客户端的宽度,其实就是外面容器Layout viewport的宽度

对于电脑端,Layout viewport比较好理解,就是我们看到的浏览器的窗口区域。

手机端就复杂了。

手机端 Layout viewport

由于手机端屏幕窄,不能像电脑端那样看到那么多的东西,但手机厂商也希望能在手机上查看适配于电脑的网页,但又不能让浏览器的宽度跟手机屏幕一样宽,因为那样的话,我们的网页宽度超过了窗口宽度,会出现横向滚动条。比如浏览器的宽度设成和手机屏幕(对外适配屏幕宽度)一样的宽度320px(css pixel),而电脑网页的宽度可能是1024px(css pixel),显然需要横向滚动,用户需要右划来查看网页。

能否不出现横向滚动条,一加载就看到网页全貌呢?他们想了个办法,让手机浏览器宽度还是足够宽(比如980px),手机浏览器宽度超过手机屏幕宽度,这时拿手机看浏览器的网页,只看到网页的一小部分(想象管中窥豹),这时候浏览器的宽度是超过手机屏幕的。然后把浏览器缩放,缩放到和手机屏幕一样宽,这时候看到的,就是一个缩小版的电脑网页。

缩小前
缩小后

layout viewport在手机端不直观,通过上面两张图能够更好理解layout viewport。

最后,Layout viewport size(css pixel)一旦设置了就是固定的,即容器设定了就不会变化。页面加载完成后,它不会因为页面放大和缩小而改变,改变的是我们看到的内容范围,也就是后面要说的visual viewport。

注意,这里说的浏览器宽度,是开发者意义上的宽度,不是我们见到的尺寸的宽度。比如,layout viewport宽度是980px,那么它不管放在电脑上显示,还是缩放在手机屏幕上,代表的都是980px,如果里面有一个div是490px宽,那么我们看到的效果都是占一半宽度,只不过电脑上的长度很长,手机上的看着很短,但这个div都是490px。

通常设置的viewport

我们通常设置viewport的<meta>,设置的就是layout viewport,可以理解为它是浏览器中物理存在的视窗。

 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />

Visual viewport

Visual viewport, 可视窗口,是可见到的文档部分的窗口区域。

想象一张桌子上放了一张地图,你正在用放大镜看地图。桌子就是layout viewport,地图就是html文档,放大镜就是Visual viewport。

Visual viwport size(css pixel)就是你放大镜里看到的内容区域的大小,它不是固定的。放大镜里看到的是一个县,visual viewport size就是一个县的面积那么大;看到的是一个镇,visual viewport size就是一个镇的面积那么大。

你可以右手持放大镜,左手移动地图来查看不同部分内容(当然生活中不会这么做),相当于手机端滑动查看文档;你可以把放大镜放得离地图近些或者远些,来看得更清楚或看得更多,相当于手机端的缩放。想象地图会缩小,缩小到跟你的放大镜一样大,你看到的就是整张地图。

所以,Visual viewport是文档可见部分的视窗,就像手机后面有一个浏览器,里面装了文档,你拿一个矩形方框在看文档一样。

Visual viwport的宽高可以用window.innerWidthwindow.innerHeight来获取,不过支持性不是非常全面,未验证。

Adapt viewport(Ideal viewport)

前面两者都是真实存在的视窗,第三种是只有手机端才有的概念。

电脑端的网页在手机端查看,效果肯定不如意,太小,希望针对手机端的屏幕,有手机端的理想网页显示尺寸。这个尺寸不会太宽,宽到电脑浏览器那么宽;不会太窄,窄到变成一条竖条,要刚好能适配手机的宽度。符合这个尺寸的视窗,PPK称为理想视窗,这个理想视窗是相对于用电脑浏览器宽度的。我倒认为应该定义为适配视窗,因为手机厂家为自己的手机设置了固定的适配尺寸,即,我这台手机横向代表多少个CSS pixel, 纵向代表多少个CSS pixel, 这是定死的。

手机分辨率也是固定的。因此device pixel和css pixel的适配关系也就固定了。也就是我们常见到的DPR

且不说是不是理想视窗,看看安卓那一堆的DPR,1.5, 2, 2.5, 3.5, 乱七八糟,适配尺寸五花八门,谁知道它们定出来的适配尺寸是不是理想的。苹果的尺寸都好歹有规律,DPR成倍数关系,适配尺寸也就那几种。

因此,Adapt viewport是手机厂商设定的该手机的适配窗口尺寸,即屏幕的横向纵向代表的CSS pixel数,与手机的分辨率无关。

  • 假设手机的分辨率是320*480, Adapt viewport size可以是320*480,横向320px(CSS pixel),一个CSS pixel对应一个device pixel;
  • 假设手机的分辨率是640*960, Adapt viewport size也可以是320*480,横向还是代表320px(CSS pixel),只不过这时一行里1个CSS pixel用2个device pixel来呈现。

Adapt viewport获取

adpat viewport的宽度通过<meta>标签里的device-width得到,以及媒体查询的device-width

<!-- 设置layout viewport的宽度为adapt viewport的宽度(比如320px) -->
 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
@media screen and (max-device-width: 375px) {
    .footer {
        background-color: black;
    }
}

注意,width是layout viewport的宽度,device-width才是adapt viewport的宽度,也就是厂家设定的页面显示适配宽度。大家可以用chrome的手机模式测试下。这也好理解,特别强调device-width,就表示厂家对这台设备对外宣称的设备宽度,用来说明它横向表示多少像素(css pixel),纵向多少宽度(css pixel)。

看看<meta>标签的设置width=device-width, 就是让layout viewport的宽与adapt viewport的宽一致,从这里也可以区分width, device-width的意义了。

关于width和device-width的区别也可以参看此文章:Media Queries: Width vs. Device Width

viewport <meta>

viewport的meta是苹果手机最开始引入的,用来设置屏幕的布局尺寸(layout viewport),后来安卓也跟随其方法,都支持了这个标签。

 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minum-scale=1.0, user-scalable=no" />
  • name, meta字段名称,为“viewport”
  • content,具体设置字段,用key value键值对来表示,键值对之间用逗号分隔。
    • width, 直接设置layout viewport宽度,可以是具体数值,如300,也可以是一个特殊值device-width(css pixel), 表示手机的adapt viewport width(css pixel),也就是厂家设定的适配尺寸。
    • initial-scale, 初始的缩放值,可以决定layout viewport尺寸和visual viewport尺寸
    • maximum-scale, 用户能缩放的最大值
    • minimum-scale, 用户能缩放的最小值。如果maximum-scal和minimum-scale都为1,用户就不能对页面进行缩放。
    • user-scalable, 用户是否能缩放,值:yes/no,这个值没测试,似乎是为了兼容性。

viewport scale

视窗缩放,这个东西困扰了我很久。

首先思考的是,缩放的是什么视窗?Layout viewport? visual viewport?

答案是,Layout viewport

试想之前放大镜的例子,这次换成用一块黑色矩形边框的玻璃看地图,地图和玻璃的距离固定,要缩放地图怎么办?玻璃(手机屏幕)大小是固定的,所以要缩放地图。

玻璃不动,地图放大,玻璃里看到的地图文字就变大了(相当于文档字体变大),但所看到的地区的面积变少了(原来看到一个县,现在只能看到一个镇),看到的面积变小,也就是visual viewport size变小了;地图缩小,则反之。

补一句,放大时,visual viewport size变小,这里可能比较费解,之前说visual viewport size是看到的内容区域的大小,我们能耐看到的文档内容变少了,因而visual viewport size变小了。

<meta>标签中的scale

那么<meta>标签中的scale是设置什么的呢?initial-scale=2的意义是什么?

始终记得,viewport<meta>标签设置的是layout viewport。前面也说了scale是对layout viewport的缩放,所以首先scale是设置layout viewport的。

但是这里还没这么简单,这个scale还有一层意思,就是将layout viewport缩放后,要使其能刚刚好铺满屏幕(fit the screen)。

举个例子,initial-scale=0.5,首先它表示将layout viewport缩小到原来的一半;其次,缩小后,整个layout viewport要刚好铺满这个屏幕,换句话说,这时候的visual viewport尺寸就刚好等于layout viewport尺寸,因为我们看到了layout的全部。

现在就相当于做一道小学数学应用题:

=================================

已知adapt viewport的尺寸为375*667(css pixel),即layout viewport不进行缩放,且尺寸也为375*667时,那么整个浏览器页面要刚好铺满屏幕。问,假设将layout viewport缩放至原来的一半后,其刚好铺满整个屏幕,layout viewport尺寸需要多大,此时的visual viewport尺寸是多少?

解:

  • layout viewport尺寸为375*667时,即scale=1,刚好铺满整个屏幕,此时visual viewport尺寸等于layout viewport尺寸
  • layout viewport尺寸为750*1334时,假设不对layout viewport进行缩放(scale=1),此时layout viewport有一半伸展到了屏幕外面,只有一半显示在手机中,visual viewport尺寸为375*667
  • 接上面,此时如果把layout viewport缩放到原来的一半,那么伸展在屏幕外面的区域缩到了手机屏幕中,整个layout viewport刚好铺满屏幕,此时visual viewport尺寸为750*1334

答:layout viewport尺寸需要750*1334,此时visual viewport尺寸为750*1334

====================================

从上面的推导可以看出一个关系:当scale=0.5时,adapt viewport是layout viewport的一半,即

adapt-viewport = 0.5 * layout-viewport = scale * layout-viewport

那么

layout-viewport = adapt-viewport / scale

由于scale的定义要求最后缩放的结果是layout viewport的尺寸刚好铺满屏幕,铺满屏幕就是visual viewport了,因此这种情况下layout-viewport-size = visual-viewport-size。于是,

*【visual-viewport = layout-viewport = adapt-viewport / scale

注意,这里的layout-viewport与adapt-viewport的关系是基于scale得出的关系,如果layout viewport width另外单独定义了(如定义width=400),那么visual viewport不一定等于layout viewport。

这就是定义scale后得出的关系。只要有了scale,就有了layout viewport和visual viewport的尺寸。

initial-scale

scale是通过initial-scale来定义的,它表示页面第一次加载时的scale,此后如果页面修改了这个值,再刷新是无效的,需要关闭重新打开页面。

前面说了设置了scale,就有了layout viewport,如果同时还定义了width呢?

例如:假设设备的adapt viewport为320*480。定义meta:

<meta name="viewport" content="width=700, initial-scale=0.5">
  • width=700,定义了layout viewport宽为700;
  • initial-scale=0.5, 可以得出layout viewport为640

那么问题来了,取那个呢?

答案是,取最大的那个值

  • 根据initial-scale的设置,此时页面处于缩小0.5倍的状态,visual-viewport-width=640px(css pixel), layout-viewport-width=640px(css pixel)
  • 由于设置了width=700,大于640, 故 layout-viewport-width=700px(css pixel)(如果width设置为600,那么layout-viewport宽还是640px)

产生的效果就是屏幕有滚动条,需要滚动一点点(60像素)。

<meta>默认值

如果不设置viewport <meta>标签,那么layout viewport宽度是多少,visual viewport是多少,scale又是多少?

layout viewport大多数浏览器默认是980px,有部分浏览器是其他值。手机浏览器厂商为了提高用户体验,默认会缩放layout将整个layout铺满屏幕显示,使得页面不会出现横向滚动条。我们看到全部页面,所以visual viewport尺寸等于layout viewport,因此visual viewport宽等于980px(css pixel)

再根据前面的计算公式visual-viewport = adapt-viewport / scale, 那么

scale = adapt-viewport / visual-viewport

假设手机适配尺寸adapt-viewport为375*667(css pixel),那么

scale = 375/980 = 0.382653

这个值浏览器是自动计算得出的。

width, initial-scale只设置一个值

  • 如果只设置了initial-scale,根据scale的作用,浏览器会计算layout viewport宽度,并且将layout viewport自动适配到屏幕,使其刚好铺满屏幕;
  • 如果只设置了width,那么浏览器也会将layout viewport自动适配到屏幕,使其刚好铺满屏幕。scale的值会自动计算。

总结

  1. Device pixel是设备物理像素。CSS pixel是参考像素,与实际像素无关。
  2. Viewport有三种:Layout viewport, Visual viewport, Adapt viewport,Layout viewport是文档容器视窗,Visual viewport是文档可见部分视窗,Adapt viewport是手机适配视窗,是厂家为自己的手机设定的屏幕显示尺寸。
  3. <meta>标签设置的是layout viewport,可以通过width字段设置,还可以通过scale字段设置。不设置时,大多数浏览器layout viewport为980px,且完全铺满于屏幕中。

最后

啰啰嗦嗦说了这么多,希望把知道的都表述清楚了。大概我比较笨,看别人的文章,有时觉得不够详细,看完不理解,所以我尽量细致的把来龙去脉和注意事项都说了,生怕哪里掉了什么。

viewport这块看了不少文章,写完这些理解才真正加深了。只要能够理解好viewport的这些知识,移动端的适配就轻松多了。

参考文章