漫谈移动端 viewport 视口设置的一些用途以及可能遭遇的各种问题
我们通过 viewport 视口设置,可以实现对移动端设备窗口的尺寸控制,这是个十分有用的功能,并且由于现在 viewport 已经支持动态修改了,所以还能在页面加载之后再根据需求对其进行调整,但实际上这个设置,尤其是动态修改,是存在许多问题的,XJ 为了解决这些问题,甚至还特意写了个 xj.viewport 插件,当然在这篇文章中插件不是重点,但插件的开发(踩坑)过程却是这篇文章诞生的基础。
本文主要分为三部分,一是讲 XJ 在开发过程中对于 viewport 的一些额外认知,二是讲利用 viewport 在移动端进行一些有用的尺寸设置,三是讲在移动端进行 viewport 设置时可能会遭遇到的各种 BUG 和解决方案,如果你之前对 viewport 的认识仅限于在 中设置
,那么阅读本篇文章后一定会有些意外收获的。
01. 一些 viewport 相关的基础性知识点
以下内容是 XJ 在开发实践中对 viewport 视口的一些发现和理解,涉及到了基础属性的一些设置细节以及使用建议,这里需要提前说明的是,下面我们提到设备的各种尺寸,都是指理想尺寸而不是设备分辨率(理想尺寸又被称为 CSS 像素),例如 iPhone8 的理想尺寸是 375 x 667
而设备分辨率是 750 x 1334
,这涉及到设备像素比既 DPR 问题,你可以参考 移动 web 开发之像素和 DPR 这篇文章。
————
01.01. 同时设置 width 和 initial-scale 属性
width
属性用于定义窗口的宽度,可以是个明确的数值如 500
,也可以设置为设备的原始宽度既 device-width
,如 iPhone4 的屏幕宽高为 320 x 480
则 device-width
在 iPhone4 中就是 320px
,而 iPhone8 的屏幕宽高是 375 x 667
则 device-width
在 iPhone8 中就 375px
,也就是说 device-width
是个相对尺寸,在不同型号的设备中并不相同,它取决于设备的原始宽度尺寸。
initial-scale
属性用于设置窗口的初始比例,但它是并非是以当前 width
属性的设置作为基础,而是以 device-width
既设备的原始宽度作为依据,例如在 iPhone8 中,initial-scale=1
则窗口宽度就是 375px
,因为 375/1=375
,initial-scale=0.75
则窗口宽度就是 500px
,因为 375/0.75=500
,很多人都误以为 initial-scale
属性是以当前的 width
为基础,这是不对的。
那么窗口宽度究竟是由 width
属性还是 initial-scale
属性决定的?实际上这是个兼容问题,有的环境是只设置 width
属性就够了,有的环境是只设置 initial-scale
属性就够了,但是考虑到兼容,最好是同时设置并保持一致,例如说想在 iPhone8 实现宽度尺寸为 500px
,则 meta
标签的 content
属性就该设置为 width=500,initial-scale=0.75
,这个 0.75
来自于 375/500
。
那么当这两个属性不同时会怎样?根据实测是浏览器会以大的那个值为准,例如在 iPhone8 设置 width=500,initial-scale=1
,这种情况下窗口宽度会等于 500px
,但初始进入页面可能依然会以设备的原始尺寸既 375px
来显示(在真实设备中很可能会这样),因为 initial-scale=1
,这个 1
就是 device-width
既 375px
,此时界面无法展示所有内容,得横向滚动才能看到右侧的东西。
这个时候就需要用户对界面进行捏合操作了,得缩小窗口既将 scale
缩小为 0.75
后窗口的宽度才会变成 500px
,多一步操作总是不理想的(虽然浏览器会记住这个尺寸操作),但每次都需要计算后再设置合适的 initial-scale
属性也很烦,这其实也是 XJ 写 xj.viewport 插件的起因,该插件可通过简单的设置让 initial-scale
属性和 width
属性自动保持一致,省去了手动计算的麻烦。
最后再提一点,在做 Demo 测试的时候,查看当前窗口的尺寸是比较容易的,如果是使用 Chrome 的移动模拟功能,那么直接审查元素即可,如果是在移动端,那么用 document.documentElement.clientWidth
属性也可以得知,但是当前窗口的比例既 scale
缩放就不好知晓,好在较新的浏览器现在都已支持 visualViewport 对象,所以我们使用 visualViewport.scale
属性就可以知道窗口比例了。
————
01.02. 间接实现 height 属性以定义窗口的高度
height
属性和 device-height
属性值,其实也在 viewport 的标准之中,只是到目前为止,从来就没有任何浏览器实现过它罢了,标准和实际不相符是前端的特色嘛,但不支持不代表我们就没法实现,如果你想定义移动端窗口的高度,可通过 width
和 initial-scale
属性来实现,因为移动设备的屏幕,宽高比例是固定的,也就是说在一定的宽度下就会有对应的一个高度,实现方法就在此中。
例如 iPhone8 的屏幕分辨率是 375 x 667
,如果想让窗口的高度为 800px
(这里暂不考虑地址栏和工具栏对屏幕尺寸的影响),那么 viewport 设为 width=450,initial-scale=0.8333
即可,450
源自 375 * 800 / 667
,0.8333
源自 375/450
,宽度计算说白了就只是个 "Cross Multiplication" 既 交叉相乘 而已,这是小学数学的知识点,忘记可百度一下来温故知新,这里就不解释了。
将窗口宽度设置为我们想要的那个目标宽度,由于移动端屏幕是具有固定比例的,所以自然可得到我们想要的目标高度,理论上来讲是这样,但实际上这个高度很可能是不精准的,因为高度是 width
属性和 initial-scale
属性根据等比例原理计算出来的,而有些浏览器并不支持 width
属性的小数那部分(可能被四舍也可能被五入),所以得出来的高度值可有能跟目标值有 ±1
或 ±2
的偏差。
些许偏差倒还没什么,更麻烦的是移动端有些浏览器如 Safari(IOS) 在页面滚动时,会出现地址栏和工具栏的收起或放下,这就导致了移动端页面的高度会频繁变化,但我们并不能跟着变化响应,因为重设 viewport 会引起页面的回流和重绘,此时页面就会发生抖动或闪烁,甚至窗口的比例都会被重置,这种情况下用户体验就会很糟,所以总结就是,窗口高度可被定义但不精准,如何响应也是个大问题。
————
01.03. 别设置 user-scalable=no 禁止用户缩放
user-scalable
属性用于控制用户是否可对界面进行缩放,minimum-scale
和 maximum-scale
属性则用于定义缩放的最小值和最大值,但实际上这三个属性在 IOS10+ 中就不再被支持了,因为 Safari 的开发团队认禁用缩放功能和限制缩放级别会影响访问性,导致视力不佳的人难以阅读和理解页面内容,虽然禁止缩放会让页面变得更像 APP,但能缩放本来是 HTML 的一种优势,干嘛要像 APP 呢?
不建议设置这三个属性来禁止缩放,还有另一个原因是,移动设备可能存在屏幕旋转既 orientation
的操作,而屏幕旋转后窗口的尺寸就可能发生变化,窗口比例也可能需要重新调整,此时就需要用户进行捏合操作将界面缩放到合适尺寸,如果 user-scalable
被设置为 no
或 minimum-scale
和 maximum-scale
被设置为 1
(也会导致不可缩放),则界面可能就会一直保持一个不理想的状态。
02. 利用 viewport 来进行各种尺寸设置
下面是使用 viewport 来设置移动端窗口尺寸的一些案例,需要注意的是,不使用插件的 Demo 并没有做 resize
事件监听,所以得按 F5 刷新才能重置尺寸,实际上这里的 resize
事件并不好监听,因为设置视窗的 content
属性也会引起 resize
事件,导致事件可能进入死循环,更不要说移动端窗口的尺寸有诸多问题和 BUG,如果把这些问题全考虑在内,代码会变得十分复杂,所以只能简化了。
————
02.01. 为视窗设置一个固定宽度
在下面的例子中,我们通过动态计算,将视窗的宽度固定为 512px
,不管设备的原始宽度是多少,也不管是竖屏或横屏模式,反正设备的宽度尺寸现在总是 512px
了,但固定窗口的宽度尺寸有什么用呢?实际上并没有什么用,大屏设备如 ipad 遭遇到一个小尺寸,会让内容显得臃肿,小屏设备如 iPhone8 遭遇到一个大尺寸,会让内容过小而不看不清,这个例子只是简单展示如何动态设置尺寸罢了。
width=512/height=512
width=512,initial-scale=1
↓ View & Code ↑
上面这个 Demo 并没有做 resize
事件监听,所以得手动刷新才行,在本文的最后一章我们会提到设置 viewport 视口将会遭遇的各种 BUG 和问题,到时你就会知道为什么 XJ 没在这里监听 resize
事件了,如果你懒得自己解决兼容问题,或是想节约开发时间,也许可以试试 xj.viewport 插件,它使用起来十分简单,并且由于解决了兼容问题,所以能自动响应无需手动刷新,下面是一个简单的例子。
width=512/height=512
width=512,initial-scale=1
↓ View & Code ↑
————
02.02. 为视窗设置一个最小宽度
在下面的例子中,如果视窗的宽度小于 416px
就设置为 416px
,如果视窗的宽度大于 416px
则不做处理,你肯定会问设置一个最小宽度有什么用?这个就很有用了,例如说你拿到的设计稿宽度是 375px
,但实际上市场里还是存在一些窗口宽度不足 375px
的设备,例如说 iPhone5/5C/SE,窗口宽度就只有 320px
,此时将最小宽度设置为 375px
,就不用担心小屏设备出现尺寸不足的问题了。
width=416/height=416
width=416,initial-scale=1
↓ View & Code ↑
设置最小宽度可以让我们不用再担心设备屏幕小于设计稿而样式出错的问题,有个最小尺寸兜底总是好的,当然跟上面的例子一样,由于 resize
事件难以监听,所以尺寸变化后还是得手动按 F5 来刷新,但是 xj.viewport 插件也还是提供了对应的解决方案,照样是设置简单且能自动响应尺寸变化,在 meta
标签后引入插件,然后为 meta
标签添加 xj-viewport="{minWidth:416}"
属性就可以了。
width=416/height=416
width=416,initial-scale=1
↓ View & Code ↑
————
02.03. 配合 fullPage 定义窗口
在下面的例子中,我们来让 320 x 480
的内容总是能在窗口中完美显示,当窗口尺寸太小的时候就进行放大,当窗口尺寸太大的时候就进行缩小,这种设置有什么用?在做 fullPage 既 "整屏翻页" 的时候就很有用了,fullPage 是种每次滚动都会滚出整个屏幕距离,就类似于书籍翻页效果的玩意,这类项目在移动端如何进行窗口适配,一向是个难题,现在通过 viewport 设置我们能很好的解决它了。
↓ View & Code ↑
在上面的例子中,经过视窗设置,能让 320 x 480
的内容被完整的显示出来,就是代码量有点多且在尺寸变化时还得手动刷新,改用 xj.viewport 会简单不少且无需手动刷新,在 meta
标签上添加 xj-viewport="{minWidth:320, minHeight:480, fillScreen:true, }"
即可,fillScreen
属性的意思是,如果窗口大于设置的最小宽高度,就将窗口缩小到符合设置的最小宽高度,也就是填充屏幕。
↓ View & Code ↑
上面两个 Demo 都只展示了窗口尺寸的设置,而没有真正的 fullPage 整屏翻页效果,下面我们展示一个完整的案例,业界中有不少插件如 swiper.js 和 fullpage.js 都可以实现整屏翻页的效果(后者是收费的请慎用),而下面这个 Demo 里的翻页效果是 XJ 自己写的,实际上也不复杂,就是代码有些长,这其实是 xj.viewport 插件的一个 Demo 案例,如果感兴趣的话可自行查看这个页面 : fullPage。