快速摘要:你听说过屏幕方向 API 吗?设备方向 API、振动 API 或联系人选择器 API 呢?Juan Diego Rodriguez 对这些不太为人所知的网络特性感兴趣,并讨论了如果它们获得更广泛的支持,如何利用它们来创建更易用和更健壮的渐进式 Web 应用。
几年前,在 JavaScript 状况调查中,四个 JavaScript API 在意识度排名中垫底。我对这些 API 产生了兴趣,因为它们有很大的潜力,但却没有得到应有的认可。即使经过快速搜索,我也惊讶地发现有多少新的 Web API 被添加到 ECMAScript 规范中,却没有得到应有的关注,缺乏意识和浏览器支持。
这种情况可能成为“第二十二条军规”:
这些 API 大多旨在为渐进式 Web 应用(PWA)提供动力,缩小 Web 和原生应用之间的差距。请记住,创建 PWA 不仅仅是添加一个 清单文件。当然,从定义上讲,它是一个 PWA,但实际上它在实践中的功能就像主屏幕上的一个书签。实际上,我们需要几个 API 来在 Web 上实现完全的原生应用体验。我想强调的四个 API 是 PWA 拼图中的一部分,它们为 Web 带来了我们曾经认为只有在原生应用中才可能的功能。
你可以在这个演示中看到所有这些 API 的实际应用,我们将一一介绍。
1. 屏幕方向 API
屏幕方向 API 可以用来检测设备当前的方向。一旦我们知道用户是以纵向还是横向浏览,我们就可以利用它来增强移动设备的用户体验,通过相应地改变用户界面。我们还可以使用它来锁定屏幕在某个特定位置,这对于显示视频和其他从更宽视口中受益的全屏元素非常有用。
使用全局 screen
对象,你可以访问屏幕用于渲染页面的各种属性,包括 screen.orientation
对象。它有两个属性:
type
: 当前屏幕方向。它可以是:"portrait-primary"
,"portrait-secondary"
,"landscape-primary"
, 或"landscape-secondary"
。angle
: 当前屏幕方向角度。它可以是从 0 到 360 度的任何数字,但它通常设置为 90 度的倍数(例如,0
,90
,180
, 或270
)。
在移动设备上,如果 angle
是 0
度,type
最有可能评估为 "portrait"
(垂直),但在桌面设备上,它通常是 "landscape"
(水平)。这使得 type
属性对于了解设备的真实位置非常精确。
screen.orientation
对象还有两个方法:
.lock()
: 这是一个异步方法,它接受一个type
值作为参数来锁定屏幕。.unlock()
: 这个方法解锁屏幕到其默认方向。
最后,screen.orientation
有一个 "orientationchange"
事件,用于知道方向何时改变。
浏览器支持
查找和锁定屏幕方向
让我们编写一个简短的演示,使用屏幕方向 API 来了解设备的当前方向并将其锁定在当前位置。
这是我们的 HTML 模板:
<main>
<p>
方向类型:<span class="orientation-type"></span>
<br />
方向角度:<span class="orientation-angle"></span>
</p>
<button type="button" class="lock-button">锁定屏幕</button>
<button type="button" class="unlock-button">解锁屏幕</button>
<button type="button" class="fullscreen-button">全屏</button>
</main>
在 JavaScript 方面,我们将屏幕方向的 type
和 angle
属性注入到我们的 HTML 中。
let currentOrientationType = document.querySelector(".orientation-type");
let currentOrientationAngle = document.querySelector(".orientation-angle");
currentOrientationType.textContent = screen.orientation.type;
currentOrientationAngle.textContent = screen.orientation.angle;
现在,我们可以看到设备的方位和角度属性。在我的笔记本电脑上,它们是 "landscape-primary"
和 0°
。
如果我们监听窗口的 orientationchange
事件,我们就可以看到每次屏幕旋转时值是如何更新的。
window.addEventListener("orientationchange", () => {
currentOrientationType.textContent = screen.orientation.type;
currentOrientationAngle.textContent = screen.orientation.angle;
});
要锁定屏幕,我们首先需要处于全屏模式,所以我们将使用另一个非常有用的功能:全屏 API。没有人希望网页在未经同意的情况下突然进入全屏模式,所以我们需要来自 DOM 元素的瞬时激活(即用户点击)才能工作。
全屏 API 有两个方法:
Document.exitFullscreen()
从全局文档对象中使用,Element.requestFullscreen()
使指定的元素及其后代全屏显示。
我们希望整个页面全屏,所以我们可以从 document.documentElement
对象调用该方法:
const fullscreenButton = document.querySelector(".fullscreen-button");
fullscreenButton.addEventListener("click", async () => {
// 如果已经在全屏,就退出到正常视图
if (document.fullscreenElement) {
await document.exitFullscreen();
} else {
await document.documentElement.requestFullscreen();
}
});
接下来,我们可以锁定屏幕的当前方向:
const lockButton = document.querySelector(".lock-button");
lockButton.addEventListener("click", async () => {
try {
await screen.orientation.lock(screen.orientation.type);
} catch (error) {
console.error(error);
}
});
用解锁按钮做相反的操作:
const unlockButton = document.querySelector(".unlock-button");
unlockButton.addEventListener("click", () => {
screen.orientation.unlock();
});
我们不能用媒体查询检查方向吗?
可以!我们确实可以通过 CSS 中的 orientation
媒体特性 在媒体查询中检查页面方向。然而,媒体查询通过检查宽度是否“大于高度”来计算当前方向为横向或“小于”为纵向。相比之下,
你可能已经注意到像 Instagram 和 X 这样的 PWA 即使在原生系统方向未锁定时也强制屏幕处于纵向模式。重要的是要注意,这种行为不是通过屏幕方向 API 实现的,而是通过在 manifest.json
文件上将 orientation
属性设置为所需的方向类型来实现的。
2. 设备方向 API
我想探究的另一个 API 是设备方向 API。它提供了访问设备陀螺仪传感器的权限,以读取设备在空间中的方向;这在移动应用中经常使用,主要是游戏。API 通过每次设备移动时触发的 deviceorientation
事件来实现这一点。它具有以下属性:
event.alpha
: 沿 Z 轴的方向,范围从 0 到 360 度。event.beta
: 沿 X 轴的方向,范围从 -180 到 180 度。event.gamma
: 沿 Y 轴的方向,范围从 -90 到 90 度。
浏览器支持
使用设备移动元素
在这种情况下,我们将使用 CSS 创建一个可以随着设备旋转的 3D 立方体!我用来制作初始 CSS 立方体的完整说明归功于 David DeSandro,可以在他的 3D 变换介绍 中找到。
查看 Pen Rotate cube [forked] 由 Dave DeSandro。
你可以在演示中看到原始的完整 HTML,但让我们在这里打印出来以供记录:
<main>
<div class="scene">
<div class="cube">
<div class="cube__face cube__face--front">1</div>
<div class="cube__face cube__face--back">2</div>
<div class="cube__face cube__face--right">3</div>
<div class="cube__face cube__face--left">4</div>
<div class="cube__face cube__face--top">5</div>
<div class="cube__face cube__face--bottom">6</div>
</div>
</div>
<h1>设备方向 API</h1>
<p>
Alpha: <span class="currentAlpha"></span>
<br />
Beta: <span class="currentBeta"></span>
<br />
Gamma: <span class="currentGamma"></span>
</p>
</main>
为了简洁起见,我这里不会解释 CSS 代码。只需记住它为 3D 立方体提供了必要的样式,并且可以通过 CSS rotate()
函数 在所有轴上旋转。
现在,使用 JavaScript,我们监听窗口的 deviceorientation
事件并访问事件方向数据:
const currentAlpha = document.querySelector(".currentAlpha");
const currentBeta = document.querySelector(".currentBeta");
const currentGamma = document.querySelector(".currentGamma");
window.addEventListener("deviceorientation", (event) => {
currentAlpha.textContent = event.alpha;
currentBeta.textContent = event.beta;
currentGamma.textContent = event.gamma;
});
要在桌面设备上查看数据如何变化,我们可以打开 Chrome 的 DevTools 并访问 Sensors Panel 来模拟旋转设备。
要旋转立方体,我们根据设备方向数据更改其 CSS transform
属性:
const currentAlpha = document.querySelector(".currentAlpha");
const currentBeta = document.querySelector(".currentBeta");
const currentGamma = document.querySelector(".currentGamma");
const cube = document.querySelector(".cube");
window.addEventListener("deviceorientation", (event) => {
currentAlpha.textContent = event.alpha;
currentBeta.textContent = event.beta;
currentGamma.textContent = event.gamma;
cube.style.transform = `rotateX(${event.beta}deg) rotateY(${event.gamma}deg) rotateZ(${event.alpha}deg)`;
});
这是结果:
3. 振动 API
让我们将注意力转向振动 API,它毫不意外地允许访问设备的振动机制。当我们需要像完成一个进程或接收到消息时通过应用内通知提醒用户时,这非常方便。也就是说,我们必须谨慎使用它;没有人希望他们的手机因为通知而震动不停。
振动 API 只提供了一个方法,而这正是我们需要的:navigator.vibrate()
。
vibrate()
可以从 navigator
对象全局获取,并接受一个表示振动持续时间的毫秒数的参数。它可以是一个数字,或者是一个表示振动和暂停周期的数字数组。
navigator.vibrate(200); // 振动 200 毫秒
navigator.vibrate([200, 100, 200]); // 振动 200 毫秒,等待 100,再振动 200 毫秒。
浏览器支持
振动 API 演示
让我们快速制作一个演示,用户可以输入他们希望设备振动的毫秒数,并有开始和停止振动的按钮,从标记开始:
<main>
<form>
<label for="milliseconds-input">毫秒数:</label>
<input type="number" id="milliseconds-input" value="0" />
</form>
<button class="vibrate-button">振动</button>
<button class="stop-vibrate-button">停止</button>
</main>
我们将为点击添加事件监听器并调用 vibrate()
方法:
const vibrateButton = document.querySelector(".vibrate-button");
const millisecondsInput = document.querySelector("#milliseconds-input");
vibrateButton.addEventListener("click", () => {
navigator.vibrate(millisecondsInput.value);
});
要停止振动,我们用一个零毫秒的振动覆盖当前的振动。
const stopVibrateButton = document.querySelector(".stop-vibrate-button");
stopVibrateButton.addEventListener("click", () => {
navigator.vibrate(0);
});
4. 联系人选择器 API
过去,只有原生应用才能连接到设备的“联系人”。但现在我们有了我想看的第四个也是最后一个 API:联系人选择器 API。
该 API 允许 Web 应用访问设备的联系人列表。具体来说,我们可以通过 navigator
对象获得 contacts.select()
异步方法,它接受以下两个参数:
properties
: 这是一个数组,包含我们想要从联系人卡片中获取的信息,例如"name"
,"address"
,"email"
,"tel"
, 和"icon"
。options
: 这是一个对象,只能包含multiple
布尔属性,用于定义用户是否可以一次选择一个或多个联系人。
浏览器支持
遗憾的是,浏览器支持几乎是零,目前仅限于我写这篇文章时的 Chrome Android、Samsung Internet 和 Android 的原生 Web 浏览器。
选择用户的联系人
我们将制作另一个演示,选择并在页面上显示用户的联系人。再次从 HTML 开始:
<main>
<button class="get-contacts">获取联系人</button>
<p>联系人:</p>
<ul class="contact-list">
<!-- 我们将注入联系人列表 -->
</ul>
</main>
然后,在 JavaScript 中,我们首先从 DOM 构建我们的元素,并选择我们想要从联系人中获取的属性。
const getContactsButton = document.querySelector(".get-contacts");
const contactList = document.querySelector(".contact-list");
const props = ["name", "tel", "icon"];
const options = {multiple: true};
现在,当用户点击 getContactsButton
时,我们将异步选择联系人。
const getContacts = async () => {
try {
const contacts = await navigator.contacts.select(props, options);
} catch (error) {
console.error(error);
}
};
getContactsButton.addEventListener("click", getContacts);
使用 DOM 操作,然后我们可以将每个联系人的列表项和图标添加到 contactList
元素。
const appendContacts = (contacts) => {
contacts.forEach(({name, tel, icon}) => {
const contactElement = document.createElement("li");
contactElement.innerText = `${name}: ${tel}`;
contactList.appendChild(contactElement);
});
};
const getContacts = async () => {
try {
const contacts = await navigator.contacts.select(props, options);
appendContacts(contacts);
} catch (error) {
console.error(error);
}
};
getContactsButton.addEventListener("click", getContacts);
添加图像有点棘手,因为我们需要将其转换为 URL 并为列表中的每个项目添加它。
const getIcon = (icon) => {
if (icon.length > 0) {
const imageUrl = URL.createObjectURL(icon[0]);
const imageElement = document.createElement("img");
imageElement.src = imageUrl;
return imageElement;
}
};
const appendContacts = (contacts) => {
contacts.forEach(({name, tel, icon}) => {
const contactElement = document.createElement("li");
contactElement.innerText = `${name}: ${tel}`;
contactList.appendChild(contactElement);
const imageElement = getIcon(icon);
contactElement.appendChild(imageElement);
});
};
const getContacts = async () => {
try {
const contacts = await navigator.contacts.select(props, options);
appendContacts(contacts);
} catch (error) {
console.error(error);
}
};
getContactsButton.addEventListener("click", getContacts);
这是结果:
注意:联系人选择器 API 只有在上下文是安全的时才会工作,即页面是通过 https://
或 wss://
URL 服务的。
结论
就是这样,我认为这四个 Web API 能够让我们构建更有用和更健壮的 PWA,但它们已经从许多人的视线中滑落。这当然是由于不一致的浏览器支持,所以我希望这篇文章能够提高对新 API 的意识,以便我们有更好的机会在未来的浏览器更新中看到它们。
它们不是很有趣吗?我们看到了我们对设备及其屏幕方向的控制程度,以及我们获得的访问设备硬件特性的水平,即振动,以及其他应用的信息,以便在我们自己的 UI 中使用。
但正如我早些时候所说的,缺乏意识导致缺乏浏览器支持的某种无限循环。所以,虽然我们涵盖的四个 API 非常有趣,但当你在生产环境中使用它们时,你的体验将不可避免地有所不同。请谨慎行事,并参考 Caniuse 了解最新的支持信息,或使用 WebAPI Check 检查你自己的设备。