xj.viewport 视窗属性设置

基本介绍

简介 : xj.viewport 是一个用于浏览器视窗属性设置的插件,它可在移动端浏览器的页面中,检测设备的宽度和高度,然后动态的调整 <meta name="viewport" /> 标签的 content 属性,为页面设置最小宽高度或最大宽高度,需要注意的是,该插件只在移动端页面才会生效,因为 viewport 视窗属性对 PC 端的浏览器并不起作用,但你也可以使用 Chrome 浏览器的移动端模拟功能来查看插件的效果。

兼容 : IE10+ / Edge12+ / Firefox / Chrome / Safari / Opera / IOS Webkit / Android Platform

更新 : https://github.com/xjZone/xj.viewport/blob/master/upgrade.md

源码 : https://github.com/xjZone/xj.viewport/

协议 : Apache License 2.0

版本 :


窗口设置的须知

设置窗口尺寸,有两点需要注意,首先是这个设置只对移动端有效,但 Demo 可在 PC 端用 Chrome 的移动模拟查看,其次是插件提供了 minWidth & minHeightwidth & heightmaxWidth & maxHeight 共三对属性来设置尺寸,但和 div 等标签不同的是,视窗尺寸是有固定比例的,改动宽度会引发高度变化,反之亦然,所以这三对属性只能是单独使用,因为同时使用可能会引发宽高度冲突。


插件的提前加载

最好在 <head> 中的 meta[name="viewport"] 标签后就加载该插件并执行它的 set() 方法,如果将插件放到 <body> 渲染后才加载执行,页面就可能抖动或闪屏,这是因为插件可能会重设视窗尺寸,导致回流和重绘,如果你担心 JS 前置会让页面加载受阻,那么可用响应头信息中有设置 immutable 属性的 CDN 如 jsdelivr 的 CDN,关于该属性可参考 扼杀 304,Cache-Control : immutable

Cache-Controlimmutable 属性,则文件在本地有缓存,就不会再向服务器发送结果为 304 的请求了,浏览器将直接使用本地的缓存文件,这样相对的就会快上许多,当然你也可以选择为 <body> 设置 display:none,在执行完 set() 方法后才将 <body> 显示出来,之后我们提到的案例,为了简单都选择在 <head> 中加载并执行,在实际项目中,可选择更适合你项目的加载和执行方案。


两种执行的模式

插件有两种执行模式,一是在 meta[name="viewport"] 标签设置 xj-viewport="{}" 属性,让插件自动执行 xj.viewport.set() 方法,这种模式较为简洁,推荐使用这种,二是不写 xj-viewport 属性,改为手动执行 xj.viewport.set() 方法,下面的两段代码分别展示了这两种模式,你可以自由选择,但代码结果都是一样的 : 当页面的视窗宽度小于 400px 时,将视窗宽度设置为 400px


Demo01. 设置一个最小宽度

例如 iPhone4/5 的设备宽度既 device-width320px,你觉得这个值太小了,想要个大点的值,可用下面这写法来实现,内联属性 xj-viewport="{minWidth:512}" 的意思是当设备宽度小于 512px 就将设备宽度设置为 512px,当设备宽度大于等于 512px 则不做任何修改,注意视窗属性只对移动端浏览器生效,但可用 Chrome 的移动端模拟来查看,之后的例子也都是这样,不再重复提示。

点击链接即可查看 Demo 效果,实际上 Demo 页面还多了些提示的内容,诸如 meta[name="viewport"] 标签的 content 属性,以及当前视窗的宽高度和方向,下面展示出来的代码只写了些关键部分,虽然也能运行,但没有真实 Demo 页面那么多的信息,有兴趣可自行查看 Demo 页面的源码,后面的例子也都是这样,只展示关键性的内容,所以如果你发现代码和 Demo 内容有些许区别,那都是正常的。


Demo02. 设置一个最小高度

例如 iPhone4/iPhone5 的设备高度既 device-height480px/568px,你觉得这个值太小了,想要个大点的值,可用下面这写法来实现,内联属性 xj-viewport="{minHeight:768}" 的意思是当页面高度小于 768px 就将高度设置为 768px,当设备高度大于等于 768px 则不做任何的修改,跟上面的那个例子一样,下面的代码是 Demo 的简化版本,少了一些提示信息,有兴趣的可自行查看源码。

其实 viewportheight 属性并没有得到任何浏览器的支持,插件实际上是用 width 属性和 initial-scale 属性的计算来实现最小高度,但大部分移动端的浏览器在滚动后,地址栏都会收起或展开,导致窗口的高度会频繁变化,但我们却不能跟着频繁修改视窗属性,因为这样会引起页面抖动和视窗比例出错,总而言之,我们设置的最小高度,都只在页面滚动前是准确的,滚动之后就不一定了。

还要注意的是,视窗的最小高度是由 widthinitial-scale 属性计算出来,但 viewportwidth 属性并不支持小数,所以我们只能将 width 属性进行四舍五入取整,结果就导致最终计算出来的高度可能会有些偏差,无法保证绝对准确,如果你发现下面这个例子,窗口的高度是 767px769px,那也是正常的,对于偏差问题,只能是开发者写 CSS 的时候注意一下,自行去兼容了。


Demo03. 设置最小的宽高度

最小宽度和最小高度也可以同时设置,例如下面这个 Demo,内联属性 xj-viewport="{minWidth:512, minHeight:768}" 的意思是将视窗的最小宽度和最小高度高分别设为 512px768px,当页面的尺寸小于这个设置时将进行特殊的调整,确保屏幕总是能放下这个尺寸的内容,当设备宽高度分别大于 512px768px 时,则不做任何修改,可以简单的将这例子当作是前面两个例子的综合表现。

移动端一般用不着设置这么大的最小尺寸,这 Demo 只是为了方便查看效果而故意整出来的,跟第二个实例一样的原因,它的最小高度只在滚动前是准确的,滚动后就无法保证了,并且也可能会出现高度有 ± 2px 偏差的问题,还有就是 Firefox(Android) 浏览器可能会出现明明不能滚动,但地址栏却还是会上下伸缩的情况,所以特别的为这个浏览器设置禁止滚动,避免在这个浏览器中尺寸会出现异常。


Demo04. 限制尺寸填充屏幕

下面这个例子的设置是 xj-viewport="{minWidth:320, minHeight:480, fillScreen:true}",跟上面的实例类似,但这回最小宽高度被设置为 320px * 480px,并额外增加了 fillScreen:true 属性,意思是如果设备的宽高度大于我们设置的最小宽高度,就让设备宽高度变成我们设置的这个值,说白了就是将视窗缩小,或者说将内容放大,具体的表现,你点击 Demo 链接,自己体验一下就知道了。

一般的页面,设置 minWidth 就够了,需要用到 minHeight,往往是那种 fullPage 整屏翻页项目,这类项目不能滚动,拖曳时是整页切换的,此时设置最小宽高度的功能就能用上了,这有个更复杂的案例 : fullPage,它增加了更多细节,页面用到了 jQuery 库以及 jQuery-transit 插件,该插件能让 jQuery 支持 transform 动画,它将 translateY 属性简化为 y,更多细节可自行查看源码。

在上面的这个 fullPage 案例中,XJ 自己实现了拖曳翻页的功能,但实际上这类需求,业界已有很多现成且成熟的方案,如 swiperfullPage 插件(该插件是收费的)都可以做到,但这些跟 xj.viewport 插件都没什么关系,在这里就不多说了,有兴趣的可以自行研究,然而不管你选择了什么方案来实现拖曳翻页的功能,你总是需要设置到页面的最小视窗尺寸,此时 xj.viewport 插件就能派上用场了。


Demo05. 设置固定的宽度值

下面这个例子的设置是 xj-viewport="{width:512}",意思是不管设备的原始宽度尺寸是多少,反正就将宽度尺寸固定为 512px,但实际上这种做法很少会被用到,因为这样会导致在一些大屏设备如 iPad 中显得很怪异,因为页面的尺寸会被放大许多,导致页面显得臃肿不堪,如果设置的尺寸太大了,在小屏设备上又会因为内容过小而难以看清,总而言之固定尺寸的做法意义不大,这里只是简单展示。


Demo06. 设置固定的高度值

下面这个例子的设置是 xj-viewport="{height:768}",意思为不管设备的原始高度值是多少,反正就将高度尺寸固定为 768px 了,固定高度跟上面的固定宽度的例子相同的在展示上有缺陷,在有些设备上看着总会很怪异,所以也很少会被用到,此外固定高度也跟上面的设置最小高度的例子一样,存在因不支持小数而导致高度可能会有 ± 2px 的偏差,总而言之固定高度的做法在实际上意义也不大。

需要注意的是,跟 minWidthminHeight 这对属性不同,设置固定尺寸的 widthheight 这对属性不能同时存在且生效,因为屏幕的尺寸和 div 等标签不同,它是有固定比例的,宽度变化会影响到高度,高度变化也会影响到宽度,并且由于这个原因,设置固定尺寸的这对属性,跟 minWidth & minHeight 这对属性以及后面将提到的 maxWidth & maxHeight 这对属性也不能同时存在。


Demo07. 设置最大的宽高度

插件还提供了 maxWidth & maxHeight 这对属性,用于设置窗口的最大尺寸,但用法跟 minWidth & minHeight 这对属性也没什么区别,所以这里也不再啰嗦的逐个案例展示代码,有兴趣可直接查看这些 Demo 既 maxWidthmaxHeightmaxSize,XJ 虽然提供了这些属性和 Demo,但实际上它们究竟有什么使用场景,XJ 其实也没想出来,只是顺手提供了而已,毕竟限制窗口的最大尺寸真的很少见…

值得一提的是设置最大尺寸的两个属性也能配合 fillScreen 属性使用,但此时的结果就变成如果设备尺寸小于设置的最大尺寸,将会放大设备的尺寸,到达设置的最大尺寸值,说白了就是限制最大尺寸,但同时又在尺寸未到达最大尺寸时候设置为最大尺寸,听着很绕?自己点击 Demo 体会吧,实在看不懂也无所谓,这个案例就随他去吧,反正 XJ 也没想到有什么有能用到的场景,纯粹就只是凑数而已。

最后解释一下,为什么设置尺寸的三对属性不能同时存在,例如设备的尺寸是 400 * 600minWidth & minHeight 被设置为 600 * 600,那么设备尺寸将变成 600 * 900maxWidth & maxHeight 设置为 600 * 600,那么设备的尺寸将变成 400 * 600,此时两对属性的设置就有冲突,而固定尺寸的 width & height 还会让情况变得更乱,总之固定比例的屏幕尺寸就是没法随心所欲设置啦。


xj.viewport.set() 的参数

在上面的案例中,我们都是借助 meta[name="viewport"] 标签上的 xj-viewport 这个内联属性来进行参数设置,并让插件自动执行的,但说到底自动执行也只是调用了 xj.viewport.set() 方法而已,例如第一个案例 xj-viewport="{minWidth:512}" 的写法,就相当于 xj.viewport.set({minWidth:512}),这方法除了上面案例中提到的参数,还有其他一些参数可供设置,下面简单的罗列一下。


xj.viewport.set() 的预设

上面展示了 set() 方法可接受的所有参数,实际上插件也提供了参数预设的方法,不过你应该用不着,因为在项目中 set() 方法往往调用一次就够了,所以这个参数预设的功能并没有太大意义,只是 XJ 系列的插件都有参数预设,这里也做一下而已,需要注意的是预设得在引入插件前就设置好,否则插件加载并执行时找不到预设,就会认为预设不存在,且版本号还要对得上,否则插件也不会理会的。


xj.viewport 对象

xj.viewport 对象除了上面提到的 set() 方法外,还有其他一些实用的属性和方法,下面罗列一下,这里较为实用的是 height() 方法,因为 Safari(IOS) 的 window.innerHeight 属性的返回值会受到窗口缩放的影响,document.documentElement.clientHeight 属性的返回值可能会受到地址栏的干扰,也就是说可能都不准确,但使用插件的 height() 方法来获取窗口高度,一般就不会出问题。


全局配置

上面提到 set() 方法的参数预设,虽然实际上并没有太大意义,但插件还存在一个全局配置,这个你可能就会用到了,虽然配置只有两个属性:是否自动执行 set() 方法和 Android 上软键盘的高度判断临界值,全局配置的写法和 set() 方法的参数预设类似,也得在引入插件前就设置好,否则插件加载并执行时找不到全局配置,就会认为没全局配置,且版本号还要对得上,否则插件也不会理会。


已知问题01. 在窗口高度变大时不会进行视窗尺寸重置

在上面的 Demo02 中已经提过这个问题,移动端的浏览器在滚动后地址栏和工具栏可能会出现收起或展开,继而导致窗口的高度频繁发生变化,但我们并不能跟着这个变化去频繁的修改视窗属性,因为这样会引起页面抖动和视窗比例出错,插件为了解决这个问题,采用的策略是,在触发 resize 事件时,只有检测到窗口高度变小既地址栏和工具栏存在的情况,才会进行响应,而当窗口高度变大时则不理会。

以上这种做法,就导致了插件所设置的高度,是指地址栏和工具栏存在时的页面高度,这在真实的设备中并没有什么问题,而插件对屏幕翻转所引发的 resize 总会响应,唯一可能受到影响的就只有 Chrome 浏览器的移动模拟,当你从大屏设备转为小屏设备时,插件会出现不响应的情况,这种情况在真实环境中不大可能出现的,但有些开发者发现 Chrome 的移动模拟有时会不响应,所以这里特别解释一下。


已知问题02. Safari(IOS13-?) 横屏下最小高度的异常

Safari(IOS13-) 在横屏时,就算页面的内容很少,少到并不需要滚动条,但浏览器依然会存在垂直方向的滚动条,页面在垂直方向还是能滚动,之所以会这样,是因为浏览器强行给我们的页面多加了一段高度,这高度等于地址栏的高度,这其实是浏览器 BUG,这就导致了即使我们为视窗设置了一个高度,但由于浏览器会强行给我们多加一段高度,结果最后视窗的高度就可能会比我们设置的那个高度更大。

阻止页面的滚动能解决问题吗?并不能,阻止滚动很容易,但在这里需要解决的问题,是让浏览器的高度正常,而不是多出来一段,但遗憾的是 XJ 并没有找到什么解决方案,值得庆幸的是这 BUG 只在横屏时才会出现,并且从 IOS14+ 开始,这个 BUG 似乎已经被修复了,截至 2022 年 9 月,apple.com 给出的数据是 IOS14+ 市场占比已经超过了 99%,所以这问题我们也只能无视了,毕竟也确实修不了。


已知问题03. 设备方向翻转后重设的视窗初始比例不对

如果没在 xj-viewport 内联属性中设置 resize:false,也没在调用 xj.viewport.set() 方法时传入 resize:false,那么当设备发生翻转,插件就会根据翻转后的尺寸重新计算并设置视窗的宽高度了,此时视窗的比例就可能发生变化,大部分的浏览器都会帮我们把视窗比例调整为 1,此时页面既不会被放大也不会被缩小,一切都刚刚好,但有些浏览器如 Safari(IOS) 则可能会出现比例的问题。

问题的具体表现是,浏览器会继续保持翻转前的视窗比例,此时页面内容就可能被放大,需要用户手动操作捏合缩小比例才行,由于可能出现这种问题,所以并不推荐设置 userScalable:'no',因为不允许缩放就无法手动把视窗比例调整回最合适的状态,其实从 IOS10+ 开始,IOS 也不再支持 user-scalable 属性了,翻转屏幕的操作比较少见,用户手动缩小一下就正常了,所以这也不算什么大问题。


项目备注

01. 必须有设置 <meta name="viewport" content="width=device-width,initial-scale=1" />,否则插件初始化时就有可能会出错。

02. 即使设置了 userScalable:'no',Safari(IOS) 中页面还是能缩放,因为苹果觉得 "用户有权控制缩放",这不是本插件的 BUG。

03. 有疑问或者发现 BUG,可到 GitHub 提 Issue,如果觉得插件写得还行,请在 GitHub 为本项目点个 ★Star 吧,感谢啦 ಠ‿ಠ ❤。


推荐阅读

XJ.Chen - 漫谈 viewport 既移动端视口的设置用途以及可能会遭遇的各种问题


Copyright © 2015- XJ.Chen All Rights Reserved
More XJ Plugins : https://github.com/xjZone/