Skip to content

移动web开发 - viewport #12

@xwcoder

Description

@xwcoder

移动web开发 - viewport

这篇东西主要整理自ppk的系列文章:

需要了解设备像素设备独立像素(dip)css像素等概念。可以参考移动web开发 - 像素

由于移动端的概念相对复杂,所以先从桌面浏览器相关概念开始介绍。

桌面浏览器部分

首先看几组web开发中会接触到的尺寸度量值。

Screen size

desktop_screen.jpg

严格来说screen.width/height取到的值并不可靠。

对于桌面浏览器,Chrome和Safari中screen.width/height存储的是以物理像素或者设备独立像素为单位的屏幕尺寸。这取决于是不是Retina屏幕。比如13寸Retina屏的MacBook pro,dpr=2,物理分辨率是2560 x 1600,而screen.width = 1280 screen.height = 800,而且会随着手动调整分辨率而变化。Edge/IE和Firefox中screen.width/height的值还会随着页面的缩放而变化。

在移动端浏览中,screen.width/height通常存储的是ideal layout viewport的尺寸。在一些旧的移动端浏览器中screen.width/height存储的是设备的物理分辨率。

关于ideal viewport在移动端的部分会有介绍。

所以结论是:我们没有办法准确得到屏幕的尺寸。

web开发中通常不太需要关心screen.width/height,除了统计需求只在极少情况下会需要用到这组值,比如定位桌面程序的悬浮窗。

4FAB31F4-65D7-45AB-B264-872DE757ACE9.png

Window size

desktop_inner.jpg

window.innerWidth/Height获取的是浏览器可见窗口(browser window)的大小,包含滚动条的宽度,不包含工具栏、任务栏、地址栏等。它代表了我们在屏幕中能看到多少网页内容。

window.innerWidth/Height的度量单位是css像素所以当缩放页面时这组值会相应变化。

Scrolling offset

desktop_page.jpg

window.pageXOffset/window.pageYOffset代表网页水平和垂直滚动的距离。度量单位是css像素

viewport

viewport就是浏览器视口,它等于浏览器的可见窗口(borwser window)

The viewport, in turn, is exactly equal to the browser window: it’s been defined as such.

In CSS 2.1 a viewport is a feature of a user agent for continuous media and used to establish the initial containing block for continuous media

  • viewport并不是HTML中的元素。
  • 它只是拥有browser window的宽高(桌面浏览器)。
  • 它的度量单位是css像素
  • 它设置了<html>元素的默认宽度。

我们知道默认情况下大多数块级元素的宽度是父元素宽度的100%,那么作为文档顶级元素的<html>的默认宽度是多少呢?

在默认情况下<html>元素的宽度就是viewport的100%。

viewport的大小

desktop_client.jpg

可以通过document.documentElement.clientWidth/Height获取viewport的大小。

因为桌面浏览器只有一个viewport,viewport就是浏览器的可见窗口,所以document.documentElement.clientWidth/Height近似等于window.innerWidth/Height,差别是document.documentElement.clientWidth/Height不包含滚动条的宽度。

浏览器中之所以同时存在document.documentElement.clientWidth/Heightwindow.innerWidth/Height这两组值,是由于当年的浏览器大战:Netscape只支持window.innerWidth/Height,IE只支持document.documentElement.clientWidth/Height,因此所有的浏览器都开始支持clientWidth/Height,但是IE并没有支持window.innerWidth/Height

还有一点需要特别注意:document.documentElement.clientWidth/Height给出的永远是viewport的大小,而不是<html>元素的大小,这和其他元素是不一样的。我们可以通过css设置<html>的大小。

desktop_client_smallpage.jpg

当我们放大页面时,css像素会变大,viewport可容纳的css像素数会变化,即viewport的尺寸会变小。反之亦然。

获取<html>元素的尺寸

通过document.documentElement.offsetWidth获取<html>元素的大小。

desktop_offset.jpg

desktop_offset_smallpage.jpg

也可以通过document.documentElement.getBoundingClientRect().width获取。

获取文档的宽度(document width)

目前没办法直接获取文档的宽高。有一个间接办法:计算每个独立块元素的宽高、margin、padding,然后相加。但是也并不可靠。

事件中的坐标

通常在鼠标事件中有三组坐标值是我们需要的:pageX/YclientX/YscreenX/Y

  • pageX/Y相对于<html>元素的坐标,以css像素为单位。
  • clientX/Y相对于viewport的坐标,以css像素为单位。
  • screenX/Y相对于屏幕的坐标,单位值参考Screen size部分。

移动端部分

现在来看看移动端的情况。由于大多时候我们并不关心高度,所以以下讨论除非特别说明都是指宽度。

layout viewport和visual viewport

智能手机问世之初,绝大部分网站并没有为移动设备进行显示优化。这时手机厂商就面临一个问题:如何在移动设备如此小的屏幕上显示pc网页。浏览器引入了两个viewport(layout viewportvisual viewport)来解决这个问题。

layout viewport用于css布局,度量单位是css像素<html>默认宽度是layout viewport宽度的100%。默认情况下layout viewport是多大呢? Safari iPhone 是980px, Android WebKit是800px, IE是974px(这个值会随着厂商的修改而变化)。这样网页就在一个比较大的viewport上进行布局,即使没有针对移动端进行优化的网页也可以正常布局而不会出现错乱。

但是这时网页上的元素和文字就会变得非常小,难以阅读(由于css像素和设备像素的对应关系)。用户可以通过pinch-out放大页面来”看清“页面上的内容,试想一下,如果此时浏览器的行为像桌面端一样,layout viewport就会变小,不但会造成页面的reflow,极大可能页面布局会乱掉(取决于页面的布局方式)。

浏览器通过引入visual viewport来解决这个问题,visual viewport是用户观看网页的窗口,当用户在pinch-in/pinch-out时,layout viewport是不变的(这样页面就不会reflow),进行缩放的是visual viewportvisual viewport也是以css像素进行度量的。

可以这样来理解layout viewportvisual viewport,将layout viewport想象成一张画布,大小是不变的,网页就是画布上的内容,我们用手机对画布进行拍照,我们可以通过调整相机焦距来选择是拍画布的全部内容还是部分内容,调整焦距时显示在相机里的画布内容会放大/缩小,但是画布本身是不变的,这时相机就相当于visual viewport

mobile_visualviewport.jpg

还有一点需要指出,通常浏览器都会选择这样的行为:当缩放到最小时,visual viewport的宽度刚好等于layout viewport的宽度,这时我们通过visual viewport可以看到页面的全部(宽度上)。这也是浏览器初次加载页面时的默认行为,这个默认行为非常有用,它是之后我们要讲的一种布局方式的基础。

mobile_viewportzoomedout.jpg

获取layout viewportvisual viewport的大小

通过document.documentElement.clientWidth/Height获取layout viewport的大小。

通过window.innerWidth/Height获取visual viewport的大小。

其他尺寸值

Scrolling offset和<html>元素的尺寸与在PC端获取方式一致。


最后看看meta viewport tag

meta viewport tag

我们可以通过meta viewport tag来设置layout viewport的宽度。最初苹果公司引入了meta viewport tag用于设置viewport和缩放,之后其他系统也对此进行了跟进支持。

meta viewport tag语法如下:

<meta name="viewport" content="name=value, name=value">

例如:

<!--将layoutviewport的宽度设置为640px-->
<meta name="viewport" content="width=640"> 

meta viewport tag支持如下6个指令:

  • width:设置layout viewport的宽度。
  • initial-scale:设置初始缩放系数和layout viewport的宽度。
  • minimum-scale:设置最小缩放系数。
  • maximum-scale: 设置最大缩放系数。
  • height:设置layout viewport的高度,浏览器并不支持,通常我们也不需要设置这个值。
  • user-scalable:设置是否允许用户进行缩放,no:不允许。

ideal viewport(最佳视口/完美视口)

既然我们可以设置layout viewport的大小,那么对于网页来说哪个尺寸才是最佳尺寸呢?答案是:设备不同,最佳尺寸也不同。例如iPhone 3g/s的最佳尺寸是320 x 480(物理像素是320 x 480),iPhone 4/s是320 x 480(物理像素是640 x 960),iPhone 6是375 x 667(物理像素是750 x 1334)。更多设备参考这里

我们将设置成最佳尺寸的layout viewport称为ideal viewport

大部分设备的ideal viewport尺寸等于以dip度量的屏幕尺寸,参考

设置ideal viewport

通过width=device-width或者initial-scale=1都可以将layout viewport设置成ideal viewport的值。

有一点需要特别注意:所有scale相关的指令都是相对于ideal viewport的值进行计算的

获取ideal viewport的值

通过meta viewport tag设置ideal viewport,然后通过document.documentElement.clientWidth/Height获取。

<meta name="viewport" content="width=device-width,initial-scale=1">
var w = document.documentElement.clientWidth;
var h = document.documentElement.clientHeight;

缩放公式

visual viewport width = ideal viewport width / zoom factor
zoom factor = ideal viewport width / visual viewport width

缩放系数总是相对于ideal viewport的,与当前layout viewport的大小无关。

initial-scale

前边说initial-scale用来设置初始缩放系数和layout viewport的宽度。现在看看当设置initial-scale时究竟发生了什么。

当设置initial-scale时发生了如下两件事:

  1. 设置页面的缩放系数(zoom factor), 根据缩放系数计算出visual viewport的宽度。
  2. 设置layout viewport的宽度等于visual viewport的宽度。

bugs:

  1. 部分Android设备不支持1以外的值。

zoom factor的取值范围

有一条约束:visual viewport的宽度不能大于layout viewport的宽度。

关于minimum-scalemaximum-scale取值范围,Apple的文档表述如下:

minimum-scale: The default is 0.25. The range is from > 0 to 10.0.
maximum-scale: The default is 5.0. The range is from >0 to 10.0.

不同系统支持情况不同,详见

width与initial-scale的冲突

既然widthinitial-scale都可以设置layout viewport的宽度,那么同时设置这两个值时就会产生冲突。例如:

<meta name="viewport" content="initial-scale=1, width=400">

对于iPhone 4/s来说:

  • initial-scale=1:在竖屏下layout viewport的宽度是320px,横屏下是480px。
  • width=400:横竖屏下layout viewport的宽度都是400px。

浏览器处理这种冲突的方案是:取最大值。竖屏下layout viewport的宽度是400px,横屏下是480px。

Android WebKit在处理这种情况时有兼容问题,对于Android 4.4之前的Android WebKit,如果宽度等于device-width或者小于320,总是使用ideal viewport的宽度;如果宽度大于320,总是使用width制定的值。

target-densitydpi

Android 4.4之前的Android WebKit对width指令和缩放支持非常糟糕。

以三星 Note II 为例,它的device-width是 360px。如果设置meta viewport tag的 width为小于等于 360 的值,则不会有任何作用;而设置为大于 360 的值,不会使画面产生缩放,而是出现了横向滚动条。

可以使用target-densitydpi来解决缩放问题。target-densitydpi是Android的私有属性,在Android 4.4之后已声明被废弃。

target-densitydpi的作用是设置目标设备的像素密度等级,它可以取如下值:

  • device-dpi:使用设备自身的dpi。
  • low-dpi:120dpi。
  • medium-dpi:160dpi,默认值。
  • high-dpi:240dpi。
  • <value>: 指定一个具体的dpi,取值范围在70–400之间。
<meta name="viewport" content="target-densitydpi=device-dpi, width=400">

In the "viewport" meta tag, you can specify "target-densityDpi".
If it is not specified, it uses the default, 160dpi as of today.
Then the 1.0 scale factor specified in the viewport tag means 100%
on G1 and 150% on Sholes. If you set "target-densityDpi" to
"device-dpi", then the 1.0 scale factor means 100% on both G1 and Sholes.

当设置成target-densitydpi=device-dpi可以正确的缩放。

尽量避免使用target-densitydpi。


总结

移动浏览器中有两个viewport:layout viewport, visual viewportlayout viewport用于网页布局。我们将设置成最佳尺寸的layout viewport称为ideal viewport

可以通过meta viewport tag设置缩放系数和layout viewport的宽度。

通过设置width=device-width或者initila-scale=1都可以将layout viewport设置成最佳宽度。

大部分设备的ideal viewport尺寸等于以dip度量的屏幕尺寸,参考

visual viewport的宽度不会大于layout viewport的宽度。

缩放(scale)相关的指令都是相对于ideal viewport的宽度进行计算。

缩放系数公式:
visual viewport width = ideal viewport width / zoom factor
zoom factor = ideal viewport width / visual viewport width

当设置initial-scale时浏览器做了如下工作:

  1. 根据initial-scale的值计算visual viewport的宽度。
  2. 设置layout viewport的宽度等于visual viewport的宽度。

没有设置initial-scale时,visual viewport的初始宽度等于layout viewport的宽度。

当没有通过width或者initial-scale指令设置layout viewport的宽度时,使用浏览器的默认宽度。

通过document.documentElement.clientWidth/Height可以获取layout viewport的大小。
通过window.innerWidth/Height可以获取visual viewport的大小。

bugs:

  1. 部分Android设置不支持设置1以外的initial-scale
  2. Android 4.4之前的Android WebKit存在缩放问题,可以通过私有属性target-densitydpi=device-dpi解决。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions