【51CTO译文】在现代移动设备当中,加速计、陀螺仪以及指南针已经成为普遍存在的标准配置。在之前讨论地理定位 API的文章中,我们曾经了解过开发人员如何利用地理定位API改善用户的使用体验。今天我们要着重考量的则是另一种有趣的API,即设备定向API。
对于很多应用程序来说,检测设备朝向都是实现自身功能的重要前提,从导航应用到体感游戏皆在此列。不知道大家有没有玩过移动设备上的赛车游戏,我们可以将设备当作方向盘左右倾斜、从而实现对车辆的操控。
该API面向的另一类应用程序则关注在用户设备朝的向发生改变时,及时调整用户界面以充分利用屏幕空间、从而带来更为出色的使用体验。如果大家是YouTube的忠诚观众,那么肯定对这项功能的优势非常熟悉。
在今天的文章中,我将向大家介绍设备定向API,并解释它能为我们提供怎样的数据类型以及如何在自己开发的应用程序中对其加以利用。
1. 设备定位API是什么?
引用W3C中的设备定位API的规范描述可知,该API“……定义了多种新型DOM事件,旨在提供与主机设备相关的物理朝向与运动状态信息。”由API提供的数据产生自多种来源,其中包括设备上的陀螺仪、加速计以及指南针等。不同的设备所配备的数据来源也有所区别,具体情况取决于其上搭载的传感器类型。
该API从属于W3C Working Draft,也就是说相关规范并非最终确定、在未来其具体内容可能还会出现一定程度的变动。另外值得注意的是,已知该API在多种浏览器以及操作系统之上可能出现不一致性。举例来说,在基于Blink渲染引擎的Chrome与Opera浏览器上,该API会与Windows 8系统产生deviceorientation事件的兼容性冲突。另一个实例则是,该API中的 interval属性在Opera Mobile版本中并非恒定的常数。
2. 实际使用
该API所显示的三个事件全部用于提供与设备定位相关的信息:
• deviceorientation
• devicemotion
• compassneedscalibration
这些事件在window对象当中执行,也就是说我们需要为window对象附加一个处理程序。下面让我们对这三个事件进行逐一分析。
deviceorientation
首先出场的是deviceorientation事件,当加速计检测到设备方位发生改变时,它就会被触发。正如我之前所提到,我们可以监听这一事件并通过为window对象附加事件处理程序对任意变更作出回应。当事件处理程序介入时,它会获得DeviceOrientationEvent type的一条参数,其中包含以下四种属性:
• alpha代表的是环绕z轴的角度。它的取值范围在0到360度之间。当设备顶端指向正北方向时,该属性的取值为0。
• beta代表的是环绕x轴的角度。它的取值范围在-180到180度之间。当设备与地球表面保持平行时,该属性的取值为0。
• gamma代表的是环绕y轴的角度。它的取值范围在-90到90度之间。当该设备与地球表面保持平行时,该属性的取值为0。
• absolute用于指定设备本身提供的定位数据是否与地球坐标系相对应。在这种情况下,大家可以将其值取为true,或者采用任意其它坐标系作为基准。
下面这幅图片来自W3C的官方规范文件,其中标明了前面提到的相对于设备设定的x、y与z轴。
devicemotion
每当设备运动状态出现加速或者减速时,devicemotio事件都会被触发。大家可以对该事件进行监听,正如我们监听deviceorientation事件一样。当该事件的处理程序介入时,它会获得来自DeviceMotionEvent type的一条参数,参数当中包含四种属性:
• acceleration负责指定设备相对于地球在x、y与z轴上的加速状况,大家可以分别通过其x、y与z属性进行访问。该数值的单位为m/s2。
• accelerationIncludingGravity与acceleration属性所取的数值相同,但它会把地球重力因素考虑在内。这项属性的取值应当被用在设备硬件不知道如何去除加速数据中重力影响效果的情况下。事实上,在这类实例当中,acceleration属性往往不该由用户代理提供。
• rotationRate负责指定设备在各个轴上每秒运动多少度。我们可以通过其alpha、beta与gamma属性访问rotationRate的各独立取值。
• interval负责指定不同数据获取操作之间的时间间隔。一旦设定完毕,该数值就绝不能再进行更新。它以毫秒作为计算单位。
compassneedscalibration
这个事件会在用户代理检测到指南针需要校准时被触发。其规范还规定,“用户代理应当只在校准指南针能够增加deviceorientation事件数据准确性的前提下被触发。”该事件应当被用于通知用户指南针需要校准这一情况,同时需要指导用户如何完成这一调整。
3. 检测支持能力
检测浏览器或者用户代理是否支持前面提到的两个事件,即deviceorientation与devicemotion,本身非常简单,只需要添加一条微不足道的状态声明即可。大家可以查看以下代码片段,我们会在其中检测对deviceorientation事件的支持能力:
- if (window.DeviceOrientationEvent) {
- // We can listen for change in the device's orientation...
- } else {
- // Not supported
- }
为了测试compassneedscalibration事件,我们要用到以下代码片段:
- if (!('oncompassneedscalibration' in window)) {
- // Event supported
- } else {
- // Event not supported
- }
4. 浏览器支持能力
即使对于设备定向API的支持能力良好,我们仍然需要考虑到其它可能与API产生冲突的重要因素。除了介绍当中提到的事项,absolute属性在Mobile Safari还会出现undefined问题。
不过真正的问题在于,每一款能够支持设备定向API的浏览器都只能实现部分支持。事实上,就在我撰写这份文章的时候,仍然几乎没有几种浏览器能够支持compassneedscalibration事件。在Chrome或者火狐浏览器中执行上述代码片段时就会出现这类问题。
有鉴于此,能够支持设备定位API的桌面版本浏览器包括Chrome 7及以上版本、火狐6及以上版本、Opera 15及以上版本再加上IE 11。这类支持能力在移动浏览器上表现得更好。除了前面提到过的浏览器之外,该API的支持能力还存在于BlackBerry 10、Opera Mobile 12及以上版本、Mobile Safari 4.2及以上版本外加Android上的Chrome 3及以上版本中。
对于目前对于设备定位API的准确支持能力,我建议大家点击此处查阅细节信息。
5. 演示
现在我们已经明确了需要创建怎样的演示应用来利用设备定位API。这套演示实例的目的在于建立一个采用普通HTML与CSS的方块,并在设备位置发生变化时随之进行转动。
我们还需要检索来自该API的信息,其中显示我们获取自设备定位API的数据类型。我们也会在原始文本中显示信息,这是因为虽然一部分浏览器能够支持设备定位API、但CCS属性并不能对方块进行渲染。举例来讲,Opera Mobile就符合这种情况。
由于已经确定了并不是每种浏览器都能支持该API,因此我们还需要对该API中的每一种功能进行支持能力测试并将结果传达给用户。
演示应用的原代码如下所示,但大家可以点击此处查看运行效果。
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
- <meta name="author" content="Aurelio De Rosa">
- <title>Device Orientation API Demo by Aurelio De Rosa</title>
- <style>
- *
- {
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- }
- body
- {
- max-width: 500px;
- margin: 2em auto;
- padding: 0 0.5em;
- font-size: 20px;
- }
- h1
- {
- text-align: center;
- }
- .hidden
- {
- display: none;
- }
- .cube
- {
- width: 150px;
- height: 150px;
- position: relative;
- margin: 30px auto;
- -webkit-transform-style: preserve-3d;
- transform-style: preserve-3d;
- }
- .face
- {
- width: 150px;
- height: 150px;
- position: absolute;
- font-size: 80px;
- text-align: center;
- line-height: 150px;
- background-color: #999999;
- box-shadow: inset 0 0 20px #333333;
- opacity: 0.6;
- }
- .cube .one
- {
- -webkit-transform: translateZ(75px);
- transform: translateZ(75px);
- }
- .cube .two
- {
- -webkit-transform: rotateY(90deg) translateZ(75px);
- transform: rotateY(90deg) translateZ(75px);
- }
- .cube .three
- {
- -webkit-transform: rotateY(180deg) translateZ(75px);
- transform: rotateY(180deg) translateZ(75px);
- }
- .cube .four
- {
- -webkit-transform: rotateY(-90deg) translateZ(75px);
- transform: rotateY(-90deg) translateZ(75px);
- }
- .cube .five
- {
- -webkit-transform: rotateX(90deg) translateZ(75px);
- transform: rotateX(90deg) translateZ(75px);
- }
- .cube .six
- {
- -webkit-transform: rotateX(-90deg) translateZ(75px) rotate(0deg);
- transform: rotateX(-90deg) translateZ(75px) rotate(0deg);
- }
- .value
- {
- font-weight: bold;
- }
- .author
- {
- display: block;
- margin-top: 1em;
- }
- </style>
- </head>
- <body>
- <h1>Device Orientation API</h1>
- <span id="do-unsupported" class="hidden">deviceorientation event not supported</span>
- <span id="dm-unsupported" class="hidden">devicemotion event not supported</span>
- <span id="cnc-unsupported" class="hidden">compassneedscalibration event not supported</span>
- <div id="do-results">
- <div id="cube" class="cube">
- <div class="face one">1</div>
- <div class="face two">2</div>
- <div class="face three">3</div>
- <div class="face four">4</div>
- <div class="face five">5</div>
- <div class="face six">6</div>
- </div>
- <div id="do-info" class="hidden">
- <p>
- Coordinates:
- (<span id="beta" class="value">null</span>,
- <span id="gamma" class="value">null</span>,
- <span id="alpha" class="value">null</span>)
- </p>
- Position absolute? <span id="is-absolute" class="value">unavailable</span>
- </p>
- </div>
- <div id="dm-info" class="hidden">
- <p>
- Acceleration:
- (<span id="acceleration-x" class="value">null</span>,
- <span id="acceleration-y" class="value">null</span>,
- <span id="acceleration-z" class="value">null</span>)
- m/s<sup>2</sup>
- </p>
- <p>
- Acceleration including gravity:
- (<span id="acceleration-including-gravity-x" class="value">null</span>,
- <span id="acceleration-including-gravity-y" class="value">null</span>,
- <span id="acceleration-including-gravity-z" class="value">null</span>)
- m/s<sup>2</sup>
- </p>
- <p>
- Rotation rate:
- (<span id="rotation-rate-beta" class="value">null</span>,
- <span id="rotation-rate-gamma" class="value">null</span>,
- <span id="rotation-rate-alpha" class="value">null</span>)
- </p>
- <p>
- Interval: <span id="interval" class="value">0</span> milliseconds
- </p>
- </div>
- </div>
- <small class="author">
- Demo created by <a href="http://www.audero.it">Aurelio De Rosa</a>
- (<a href="https://twitter.com/AurelioDeRosa">@AurelioDeRosa</a>)
- </small>
- <script>
- if (!window.DeviceOrientationEvent) {
- document.getElementById('do-unsupported').classList.remove('hidden');
- } else {
- document.getElementById('do-info').classList.remove('hidden');
- window.addEventListener('deviceorientation', function(event) {
- document.getElementById('cube').style.webkitTransform =
- document.getElementById('cube').style.transform =
- 'rotateX(' + event.beta + 'deg) ' +
- 'rotateY(' + event.gamma + 'deg) ' +
- 'rotateZ(' + event.alpha + 'deg)';
- document.getElementById('beta').innerHTML = Math.round(event.beta);
- document.getElementById('gamma').innerHTML = Math.round(event.gamma);
- document.getElementById('alpha').innerHTML = Math.round(event.alpha);
- document.getElementById('is-absolute').innerHTML = event.absolute ? "true" : "false";
- });
- }
- if (!window.DeviceMotionEvent) {
- document.getElementById('dm-unsupported').classList.remove('hidden');
- } else {
- document.getElementById('dm-info').classList.remove('hidden');
- window.addEventListener('devicemotion', function(event) {
- document.getElementById('acceleration-x').innerHTML = Math.round(event.acceleration.x);
- document.getElementById('acceleration-y').innerHTML = Math.round(event.acceleration.y);
- document.getElementById('acceleration-z').innerHTML = Math.round(event.acceleration.z);
- document.getElementById('acceleration-including-gravity-x').innerHTML =
- Math.round(event.accelerationIncludingGravity.x);
- document.getElementById('acceleration-including-gravity-y').innerHTML =
- Math.round(event.accelerationIncludingGravity.y);
- document.getElementById('acceleration-including-gravity-z').innerHTML =
- Math.round(event.accelerationIncludingGravity.z);
- document.getElementById('rotation-rate-beta').innerHTML = Math.round(event.rotationRate.beta);
- document.getElementById('rotation-rate-gamma').innerHTML = Math.round(event.rotationRate.gamma);
- document.getElementById('rotation-rate-alpha').innerHTML = Math.round(event.rotationRate.alpha);
- document.getElementById('interval').innerHTML = event.interval;
- });
- }
- if (!('oncompassneedscalibration' in window)) {
- document.getElementById('cnc-unsupported').classList.remove('hidden');
- } else {
- window.addEventListener('compassneedscalibration', function(event) {
- alert('Compass needs calibrating! Wave your device in a figure-eight motion');
- });
- }
- </script>
- </body>
- </html>
总结
在今天的文章中,我们已经通过了解设备定位API的特性与潜在作用建立了对其的初步认识。在撰写本文的时候,对于该API的支持还比较有限,不过我可以肯定它的出现为移动应用开发者、特别是游戏开发者带来了无穷的可能性。再次建议大家点击此处查看该API的实际演示运行效果。
原文:An Introduction to the Device-Orientation API
核子可乐译