无论你可能会怎么想,目前,在浏览器里可靠地检测当前的设备是否有一个触摸屏是不可能的。
并且可能会在很久很久以后你才能做这个检测。
让我解释一下这样说的原因……
浏览器的环境是一个沙盒。为了限制恶意网站可能造成的伤害,你的应用的代码只能获得浏览器想给你的东西。
这意味着,你可以得到的系统的唯一信息是浏览器以HTML,CSS和JavaScript API的形式向你公开的那些。确定一个系统是否支持某个功能,我们能做两件事,一是看某个明确的API是否存在,或者第二,看它是否真的做了正确的事情。
从历史上看,有两个浏览器的功能已被用于“触摸屏检测”:媒体查询 和 Touch APIs。但这些离做到万无一失还很遥远。
跟我一起看下去。
手机拥有小屏幕,并且手机拥有触摸屏,所以小屏幕等于触摸屏,这正确吗?
var hasTouch = window.matchMedia('(max-device-width: 320px)').matches;
所以,这是非常错误的看法。大平板电脑和触屏笔记本电脑/台式机已经明显的证明了这是错的。再加上还有成千上万的旧的手机型号有小的非触摸屏。但不幸的是,现在到处都有应用这条咒语的网站:“如果这是一个小屏幕,它就是触摸屏;如果这是一个大屏幕,它就是由鼠标操作的”,把垃圾的体验留给平板和多端用户。
顺便说一句,这是 Modernizr.touch
使用的方法。
如果浏览器支持一些事件诸如 touchstart
(或者其他在 Touch Events 事件接口标准的事件),这一定就是一个触屏设备,对吗?
var hasTouch = 'ontouchstart' in window;
好吧,或许是这样。但问题是,从来没有人说过一个非触屏设备不能实现触摸接口,或者至少在 DOM 里拥有事件句柄。
Chrome 24.0 装载支持了所有这些接口,所以它们可以开始支持触摸屏而不需要分为“触屏”和“非触屏”来构建。但是大量开发者依然在使用上面例子中的检测方法,所以这损坏了大量网站。Chrome 团队用一次升级“修复”了这个问题,它只在当启动时检测到了可触摸输入设备时才启用触摸接口。
所以我们都没问题了,对吧?
不完全是。
对于设备本身,浏览器仍然有相当远的距离。它只能通过操作系统来使用设备,而操作系统有自己的一堆接口让浏览器知道设备连接上了。
虽然这些接口在大多数情况下是相当可靠的,但在我们最近遇到的情况下,他们在 Windows 8 的 Chrome 里给出了错误的结果……他们报告了一个触摸屏的存在(其实是“数字转换器”),其实并没有连接触摸屏。
Firefox 也做了一些类似的转换,并且同样在这种情况下和 Chrome 一样表现为了失败,所以看起来它也可能使用了和 Chrome 一样的规则——虽然我不能肯定的断言。
毫无疑问,设置和服务会混淆接口返回的结果。到目前为止我只在 Windows 8 里看到这种情况,但从理论上讲,它可以发生在任何操作系统。
一些 BlackBerry OS 的版本也已被知道在非触摸设备上持久启用了触摸的接口。
所以看起来浏览器也不能百分之一百的确定检测触屏设备。如果浏览器都不知道,那我们的应用又怎么知道呢?
假设有一个这些触摸接口存在就意味着这个设备有一个触摸屏……那是否意味着如果没有触摸接口存在,那触摸屏也一定不存在?
当然不是。最初的 iPhone (在2007年发布)是第一个支持 Touch Events 的设备,但是,从二十世纪70年代开始触摸屏已经以一种或另一种形式存在。甚至就算最近,诺基亚的塞班浏览器也不支持触摸事件,直到去年 8.2 版本的发布。
IE 10 在触摸设备上提供(可以说是优越的)Pointer Events API,用来代替 Touch Events 标准,所以会在测试 ontouchstart
时返回 false
。~~为了覆盖所有基线你也可以做这些检查例如 'onmspointerdown'inwindow
——虽然这会很容易的像 Chrome 和 Firefox 一样受到系统接口可靠性的影响。~~我说的不对, onmspointerdown
还和鼠标和其他指针有关。然而 IE 10 提供了 navigator.maxTouchPoints
可以用来代替利用。感谢 @jacobrossi指正我。
不管是 Safari 还是 Opera 都还没有在他们的桌面浏览器实现触摸接口,所以他们在触摸设备上也没有结果。
没有专门的触摸接口,浏览器仅仅模拟鼠标事件……因而有许多设备和触摸屏不等同,你根本不能用这些检测方法来检测这些设备。
一个触摸屏可以作为一个外围设备连接到另一个非触摸式笔记本电脑,或者一个 KVM 开关可以从非触摸屏切换到触摸屏。这可以在浏览器会话过程中的任何时间发生。
当应用在执行时,浏览器不应该添加和移除接口——这会造成混乱——所以随着已连接设备的变化,这种特性检测可能出现失败。
我之前说,另一种方法来测试特性是看接口是否真正的完成了他们支持的功能……
var hasTouch;
window.addEventListener('touchstart', function setHasTouch () {
hasTouch = true;
// 当事件触发后,将事件监听移除,否则它会干掉滚动性能
window.removeEventListener('touchstart', setHasTouch);
}, false);
这是比简单地看是否这个事件句柄在 DOM 上存在更加可靠的方法:除非这个浏览器大量的违反了标准,如果一个触摸捕获设备与浏览器交互,这个事件会被触发。
然而,这产生了三个严重需要注意的事项:
这或许对于一些使用场景已经足够好了,但是对于任何涉及修改布局的应用来说,当你戳它的时候,UI 会发生改变,这是一个相当可怕的使用体验。
它们被添加到 Media Queries Level 4 标准。他们只是在 WebKit 内被部分地实现,还没有出现在任何稳定的浏览器里。
var hasTouch = window.matchMedia('(pointer: coarse)').matches;
关于这个规范的细节在之前我有一个鲁莽的结论,但它实际上有可能被用作一个可靠的特征检测。不需要思考:它没有检测一个“触摸屏”例如……而是任何普遍的指针设备。
作为媒体查询,它自然是动态的:结果可以在任何时候即时反映连接的设备。
然而,它依然依赖于操作系统接口提供可信的数据。
目前还不清楚何时(或是否)其他浏览器将实现此功能……这个标准在 W3C 都还非常不稳定。在它广泛采纳之前,不支持这些媒体查询的浏览器们会一直像现在这样“不可检测”。
我认为,如果你在最开始的时候就在试着“监测触摸屏”,你很可能已经在做危险的假设。我将详细列出你想要监测屏幕的几个可能原因,并指出其中的错误。
胖手指比鼠标更不准确,所以听起来我们适应触摸屏布局是有道理的:较大的控件,控件之间更多的空隙,等等。
但触摸屏是唯一的具有较差的指点精度的输入设备吗?
那智能电视的手势遥控器、Wii游戏机的遥控手柄,或者类似 Leap Motion 的手指跟踪技术又怎么样呢?
如果你特希望触屏检测在这些设备上不会过时,千万别假定在监测屏幕时只需做好空间布局就够了。
所以你想要为你的幻灯效果和地图窗口部件设置滑动手势吗?这很酷。但是不要认为这意味着你不需要支持鼠标和键盘交互。
有视力障碍的用户经常在他们的智能手机上连接键盘和点击设备,这样许多设备都同时支持鼠标和触摸……你不能假设他们不想使用他们的鼠标、触控板以及键盘。
我强烈建议一起实施两种交互方法,在这种情况下,你不需要专门检测触摸屏。
Patrick Lauke 的这篇文章更加详细地提出了为什么(以及怎么做)你应该一起实现鼠标和触摸事件。这篇文章很值得一读(如果你原谅他最初声称你可以可靠地检测到触摸屏……)。
当前的触摸屏并不能传输鼠标/光标悬浮状态, 所以,最好调整我们对于触摸屏的UI设计,以便在触摸屏上能够继续使用。
当然,键盘也是不能悬停的。最好一开始就避免依赖于悬停状态——仅使用它们作为点缀。
修改: 这个总结分散了这篇文章的原始信息,就是“小心,你可能没有得到你认为你得到的结果”。如果你意识到这些检测方法的风险和他们所暗含的臆断,不管怎么样,这当然是由你决定是否使用他们。然而,如果你不确定,或你的论点是“支持每一个设备”,下面的建议可能是有用的。
关于布局,假设每个人都有触摸屏。鼠标用户们使用大的控件比触摸屏用户们使用小控件更加容易。悬停状态也是一样的。
关于事件和交互,假设任何人可能有触摸屏。同等的实现键盘,鼠标和触摸交互,确保没有阻止彼此。
或者,就像我在我的关于媒体查询的文章里建议的一样,你可以只询问浏览器。
往期精选文章 |
---|
使用虚拟dom和JavaScript构建完全响应式的UI框架 |
扩展 Vue 组件 |
使用Three.js制作酷炫无比的无穷隧道特效 |
一个治愈JavaScript疲劳的学习计划 |
全栈工程师技能大全 |
WEB前端性能优化常见方法 |
一小时内搭建一个全栈Web应用框架 |
干货:CSS 专业技巧 |
四步实现React页面过渡动画效果 |
让你分分钟理解 JavaScript 闭包 |
小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。