在移动端,判断横竖屏的场景并不少见,比如根据横竖屏以不同的样式来适配,抑或是提醒用户切换为竖屏以保持良好的用户体验。
判断横竖屏的实现方法多种多样,本文就此来探讨下目前有哪些实现方法以及其中的优缺点。

CSS Media Queries

通过媒体查询的方式,我们可以通过以下方法来实现根据横竖屏不同的情况来适配样式:

1.内联样式

2.外联样式

window.matchMedia()

除此之外,CSS Object Model(CSSOM)Views 规范增加了对 JavaScript 操作 CSS Media Queries 的原生支持,它在 window 对象下增加了 matchMedia() 方法,让我们能够通过脚本的方式来实现媒体查询。

window.matchMedia() 方法接受一个 Media Queries 语句的字符串作为参数,返回一个 MediaQueryList 对象。该对象有 media 和 matches 两个属性:

  • media:返回所查询的 Media Queries 语句字符串
  • matches:返回一个布尔值,表示当前环境是否匹配查询语句

同时,它还包含了两个方法,用来监听事件:

  • addListener(callback):绑定回调 callback 函数
  • removeListener(callback):注销回调 callback 函数

那么,通过 window.matchMedia() 的方法,我们可以这样判断横竖屏:

通过Can I Use – matchMeida可以知道,该API在移动端得到良好的支持,并无兼容性问题。

window.innerHeight/window.innerWidth

The ‘orientation’ media feature is ‘portrait’ when the value of the ‘height’ media feature is greater than or equal to the value of the ‘width’ media feature. Otherwise ‘orientation’ is ‘landscape’.
—— CSS/Mediaqueries/orientation

在 CSS Media Queries 中,Orientation 属性有两个值:

  • portrait,指的是当 height 大于等于 width 的情况
  • landscape,指的是当 height 小于 width 的情况

所以,还有一种最为常见的方法是通过比较页面的宽高,当页面的高大于等于宽时则认为是竖屏,反之则为横屏。

window.orientation

在 iOS 平台以及大部分 Android 手机都有支持 window.orientation 这个属性,它返回一个与默认屏幕方向偏离的角度值:

  • 0:代表此时是默认屏幕方向
  • 90:代表顺时针偏离默认屏幕方向90度
  • -90:代表逆时针偏离默认屏幕方向90度
  • 180:代表偏离默认屏幕方向180度

在 iOS 的开发者文档(iOS Developer Library – Handling Orientation Events)是这样明确定义的:

也就是如下图所示:

iPhone-orientation.png

图来自William Malone – DETECT IOS DEVICE ORIENTATION WITH JAVASCRIPT

在实际应用中,对于 iPhone 和大部分 Android 是没有180度的手机竖屏翻转的情况的,但是 iPad 是存在的。所以,简化下代码,我们可以绑定orientationchange事件来判断横竖屏:

影响判断的问题所在

1.对window.orientation属性值的不一致

在 iOS 平台,对 window.orientation 属性值是无异议的,规范当中有明确规定每个值对应的情况。但是对于 Android 平台,就有不一致的特殊情况出现。

A misconception about window.orientation中作者 Matthew Gifford 就有提到部分 Android 机型(该文章中测试用的 Toshiba Thrive 机型)返回的情况是与期望情况是相反的;除此之外,在 StackOverflow 上也有反馈过这样的问题(例如,window.orientation returns different values in iOS and Android中提到的 Samsung Tab 2 机型)。

其实,Matthew Gifford 认为这并不是 BUG(笔者也认同),按照Compatibility Standard – 4.2 window.orientation API规范中的定义,0 值指的是 natural 、 default 的屏幕方向,所以如果生厂商对 natural 、 default 状态是用户应当手持设备方向为横屏,那么 0 值对应为 landscape 的横屏方向了。
针对这种不一致情况的出现,对于追求完美的开发者来说,通过 window.orientation 的方法来判断横竖屏则变得有点不可靠的。

2.软键盘的弹出

是否除了 window.orientation 的其它方法都是可靠的呢?
然而,实际上是事与愿违的。在 Android 下,如果页面中出现软键盘弹出的情况(存在有 Input 的元素)时,页面有时会因为软键盘的弹出而导致页面回缩,即页面的宽度(竖屏时)或者高度(横屏时)被改变。
无论是 CSS Media Queries 还是 window.matchMedia() 方法,还是根据 window.innerWidthwindow.innerHeight的页面宽高比对方法来实现的横竖屏判断方法,都会因此受到影响,出现判断失误的情况( Samsung SCH-i699 机型,在竖屏时由于软键盘弹出导致页面高度小于宽度,被错误地判定为横屏)。
所以,在这样的情况下,这几种方式也变得不可靠。

探讨最佳实现方式

本着核心的原则——具体情况具体解决来讨论。

如果你没有遇上以上两个问题所在,恭喜你!上面所提到的方法都可以被应用,选择你最为喜欢的方法就好。

但是如果想要避免以上两个问题所在,有没有更好的办法呢?

理论上来说,基于开发环境兼容性要求(iOS与Android下的微信内置浏览器与原生浏览器),屏幕分辨率应该是值大小不会改变的(毕竟手机屏幕大小并不会改变),那么我们是否可以尝试比对页面宽高和屏幕分辨率来判断横竖屏呢?下面我们研究下如何实现。

实现原理

获取页面的宽高,我们通过document.documentElement.clientWidth/-clientHeight检测,同时这也是Layout Viewport的宽高值。

获取屏幕分辨率,我们则是通过screen.width/height检测。

同时,页面需要设置Meta Viewport的属性如下:

这是为了保证页面是始终适配屏幕的,这样我们才能够有效地采用屏幕分辨率和页面宽高两个量来进行比对。

那么,接下来我们是不是可以直接通过判断“页面宽度等于屏幕宽度或者是页面高度等于屏幕高度则认为是横屏,而页面宽度等于屏幕高度或者页面高度等于屏幕宽度则认为是竖屏”实现横竖屏的判断呢?并不是,在实际的研究中,还存在几点影响因素是事与愿违的。

页面宽高与屏幕宽高的差异

在微信内置浏览器是带有顶栏的,而Android和iOS的原声浏览器也是带有底栏或顶栏底栏兼有的。因此,在实际中,页面宽高等于屏幕宽高除去顶栏或者底栏部分的。

图为iPhone 6s下的微信内置浏览器与原声浏览器截图

那么,我们只要取其一进行判断就好:当横屏时,页面宽度是等于屏幕宽度的;而竖屏时,页面宽度是等于屏幕高度。

图为以 iPhone 6s 竖屏下的微信内置浏览器为例的截图

图为以 iPhone 6s 横屏下的微信内置浏览器为例的截图

屏幕宽高的值交换

在移动端,屏幕翻转时,screen.widthscreen.height 的值是一成不变的吗?并不是,在实际的研究中(感谢@Jc、@百思不得姐夫的指出),在 华为P9 的微信(6.5.4)、华为荣耀的微信(6.5.7)和 Chrome 浏览器上,screen.widthscreen.height 均会随着横竖屏的切换而变,但是可喜可贺的是它们只是出现值交换,而并不是值大小的改变。

6F887A52D376BFAF6D0C6F3E22B7D133-1

5CDAFECB21D63813CB6546136193F55E

图为Chrome模拟器下的截图

那么,我们稍加修正下,获取screen.width/height之后进行大小判断,就可以确认哪个是屏幕宽度哪个是屏幕高度:

优化:避免重复访问screen与解决resize多次触发

对于screen.width/height的值,我们不需要每次执行函数时都去访问一遍,通过数据存储的手段(如Session Stroage、LocalStroage等)存储起来以便后续继续使用。存储的手段大家可以择优而用,这里笔者选用LocalStroage作为示例。

在iPhone 6上测试发现,resize会被执行两次(其它手机暂未测)。虽然并没有影响到事件的判断结果,但也是一个优化点。那么,我们只要通过函数去抖( Debounce Function ) 办法来进行简单的解决就好。

最终实现

逐步突破发现的问题之后,对最初的想法进行完善,最后得出的源码如下:体验地址点这里

今后的发展

目前,W3C引入Screen Orientation API,该标准能够帮助Web应用获得屏幕方向的状态,在状态改变时获得通知,并能够从应用程序中将屏幕状态锁定到特定状态。

但截止目前,该标准仍在W3C草案阶段。在移动端,它在Android和iOS平台上仍未得到支持,仅仅在Chrome for Android 39版本及以上才得到实现,所以对目前的开发来说意义不大。只能期待它能够尽快通过并得到广泛支持,这样的探测屏幕方向的问题就能够得到规范化的解决。

小后话:该文章原先发布在凹凸实验室的博客上,经过读者的反馈后,进行修正与二次整理发布在此。再次感谢读者的热情指正!

参考文档

喜欢(16) 评论(0) 分享

Leave a Reply

© 2014 JDC. All Rights Reserved.