1. 简介与设置
Web 功能
我们希望缩小 Web 和原生代码之间的功能差距,让开发者能够轻松地在开放 Web 上打造出色的体验。我们坚信,每位开发者都应该有权使用所需的功能来打造出色的网络体验,因此,我们致力于打造更强大的网络。
不过,有些功能(例如文件系统访问和空闲检测)在原生代码中可以使用,但在网络上无法使用。这些缺失的功能意味着某些类型的应用无法在网络上分发,或者实用性较低。
我们将运用现有的开放网络平台标准流程,以开放透明的方式设计和开发这些新功能,同时在对设计进行迭代的过程中收集开发者和其他浏览器供应商的早期反馈,以确保实现可互操作的设计。
构建内容
在此 Codelab 中,您将试用多个全新或仅在标志后可用的 Web API。因此,此 Codelab 侧重于 API 本身以及这些 API 可解锁的用例,而不是构建特定的最终产品。
学习内容
在此 Codelab 中,您将了解多个前沿 API 的基本机制。请注意,这些机制尚未一成不变,非常感谢您针对开发者流程提供反馈。
所需条件
由于此 Codelab 中介绍的 API 处于最前沿,因此每个 API 的要求各不相同。请务必仔细阅读每个部分开头的兼容性信息。
如何学习此 Codelab
此 Codelab 并不一定要按顺序完成。每个部分都代表一个独立的 API,因此您可以随意挑选最感兴趣的内容。
2. 标志 API
Badging API 旨在让用户因此需要注意在后台发生的事件为简单起见,在此 Codelab 中进行演示,我们将使用 API 为用户提供注意前景中发生的事情。然后,你可以让精神转移到后台发生的事件。
安装 Airhorner
为了让此 API 正常运行,您需要安装在主屏幕上的 PWA,因此首先要安装 PWA,例如声名狼藉、闻名遐迩的 airhorner.com。点击右上角的安装按钮或使用三点状菜单进行手动安装。
系统会显示确认提示,请点击安装。
现在,操作系统的 Dock 中出现了一个新图标。点击该图标即可启动 PWA。它将拥有自己的应用窗口,并以独立模式运行。
设置徽章
安装 PWA 后,您还需要在标志上显示一些数字数据(标志只能包含数字)。在 The Air Horner 中,可以统计一个非常简单的叹息数。安装了 Airhorner 应用后,请尝试调节喇叭并查看工牌。每当您鸣笛时,都会计数一次。
那么,它是如何运作的呢?实质上,代码如下所示:
let hornCounter = 0;
const horn = document.querySelector('.horn');
horn.addEventListener('click', () => {
navigator.setExperimentalAppBadge(++hornCounter);
});
鸣笛几下,然后检查 PWA 的图标:它会每更新一次。。。汽笛声。就是这么简单。
清除标记
计数器增加到 99,然后重新开始。您也可以手动重置。打开开发者工具控制台标签页,粘贴下面这行代码,然后按 Enter 键。
navigator.setExperimentalAppBadge(0);
或者,您也可以通过明确清除徽章来去除徽章,如以下代码段所示。现在,您的 PWA 图标看起来应该和开头一样清晰,并且没有标记。
navigator.clearExperimentalAppBadge();
反馈
您觉得这个 API 怎么样?请简要回答此调查问卷,这对我们很有帮助:
此 API 是否直观易用?
您是否获得了运行示例?
还有其他意见吗?是否缺少某些功能?请在此调查问卷中提供简短的反馈意见。谢谢!
3. 原生文件系统 API
通过 Native File System API,开发者可以构建与用户本地设备上的文件进行交互的强大 Web 应用。用户向 Web 应用授予访问权限后,此 API 可让 Web 应用直接读取或保存对用户设备上的文件和文件夹所做的更改。
读取文件
“Hello, world”就是读取本地文件并获取文件内容。创建一个普通的 .txt
文件并输入一些文本。接下来,导航到任一安全网站(即通过 HTTPS 提供的网站),例如 example.com,并打开开发者工具控制台。将以下代码段粘贴到控制台中。由于 Native File System API 需要用户手势,因此我们在文档上附加一个双击处理程序。我们稍后需要用到该文件句柄,因此我们将其设为全局变量。
document.ondblclick = async () => {
window.handle = await window.chooseFileSystemEntries();
const file = await handle.getFile();
document.body.textContent = await file.text();
};
随后双击 example.com 页面中的任意位置,系统就会显示文件选择器。
选择您之前创建的 .txt
文件。然后,文件内容将替换 example.com 的实际 body
内容。
保存文件
接下来,我们需要进行一些更改。因此,我们通过粘贴以下代码段将 body
设为可修改。现在,您可以像使用文本编辑器一样编辑文本。
document.body.contentEditable = true;
现在,我们需要将这些更改写回原始文件。因此,我们需要文件句柄的写入者,这可通过将以下代码段粘贴到控制台中获取。同样,我们需要用户手势,因此这次我们会等待对主文档的点击。
document.onclick = async () => {
const writer = await handle.createWriter();
await writer.truncate(0);
await writer.write(0, document.body.textContent);
await writer.close();
};
现在,当您点击(而非双击)文档时,系统会显示权限提示。授予权限后,文件的内容将是您之前在 body
中修改过的内容。在其他编辑器中打开文件以验证更改(或再次双击文档并重新打开文件,重新开始此过程)。
恭喜!您刚刚创建了世界上最小的文本编辑器:[citation needed]
。
反馈
您觉得这个 API 怎么样?请简要回答此调查问卷,这对我们很有帮助:
此 API 是否直观易用?
您是否获得了运行示例?
还有其他意见吗?是否缺少某些功能?请在此调查问卷中提供简短的反馈意见。谢谢!
4. 形状检测 API
Shape Detection API 可用于使用加速形状检测器(例如,适用于人脸的形状检测器),并适用于静态图片和/或实时图片 Feed。操作系统拥有性能出色且高度优化的功能检测器,例如 Android FaceDetector。Shape Detection API 会打开这些原生实现,并通过一组 JavaScript 接口公开它们。
目前,支持的功能包括通过 FaceDetector
接口进行人脸检测、通过 BarcodeDetector
接口检测条形码,以及通过 TextDetector
接口检测文本(光学字符识别)。
人脸检测
Shape Detection API 的一项出色功能是人脸检测。要进行测试,我们需要一个包含人脸的网页。带有作者面孔的此页面是一个好的开端。具体如以下屏幕截图所示。在受支持的浏览器中,系统会识别人脸的边框和人脸特征点。
您可以通过混搭或修改 Glitch 项目(尤其是 script.js 文件),了解只需编写少量代码即可实现这一目的。
如果您想完全动态化,而不只是处理作者的面孔,那么可以在私密标签页或访客模式下访问这个包含大量面孔的 Google 搜索结果页。现在,在该页面上,右键点击任意位置,然后点击检查,以打开 Chrome 开发者工具。接下来,在“控制台”标签页上,粘贴以下代码段。该代码将使用半透明的红色框突出显示检测到的人脸。
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const faces = await new FaceDetector().detect(img);
faces.forEach(face => {
const div = document.createElement('div');
const box = face.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left + left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
img.before(div);
});
} catch(e) {
console.error(e);
}
});
您会发现,有一些 DOMException
邮件,并非所有图片都会得到处理。这是因为,首屏图片作为数据 URI 内嵌,因此可供访问,而非首屏图片则来自未配置为支持 CORS 的其他网域。为了便于演示,我们无需担心这一点。
人脸特征点检测
除了人脸本身之外,macOS 还支持人脸特征点检测。如需测试人脸特征点的检测,请将以下代码段粘贴到控制台中。温馨提醒:由于 crbug.com/914348 的影响,地标阵容并不尽如人意,但您可以看一下这个趋势的方向以及此功能有多么强大。
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const faces = await new FaceDetector().detect(img);
faces.forEach(face => {
const div = document.createElement('div');
const box = face.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left + left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
img.before(div);
const landmarkSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
landmarkSVG.style.position = 'absolute';
landmarkSVG.classList.add('landmarks');
landmarkSVG.setAttribute('viewBox', `0 0 ${img.width} ${img.height}`);
landmarkSVG.style.width = `${img.width}px`;
landmarkSVG.style.height = `${img.height}px`;
face.landmarks.map((landmark) => {
landmarkSVG.innerHTML += `<polygon class="landmark-${landmark.type}" points="${
landmark.locations.map((point) => {
return `${scaleX * point.x},${scaleY * point.y} `;
}).join(' ')
}" /></svg>`;
});
div.before(landmarkSVG);
});
} catch(e) {
console.error(e);
}
});
条形码检测
Shape Detection API 的第二个功能是条形码检测。与之前类似,我们需要一个带有条形码的页面,例如这个页面。当您在浏览器中打开它时,会看到已解码的各种二维码。重新混合或修改 Glitch 项目(尤其是 script.js 文件),看看是如何工作的。
如果您希望看到更具活力的内容,我们同样可以使用 Google 图片搜索。这次,请使用浏览器在无痕式标签页或访客模式下前往这个 Google 搜索结果页。现在,将以下代码段粘贴到 Chrome 开发者工具的“控制台”标签页中。稍后,系统将使用原始值和条形码类型对识别出的条形码进行注释。
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const barcodes = await new BarcodeDetector().detect(img);
barcodes.forEach(barcode => {
const div = document.createElement('div');
const box = barcode.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 255, 255, 0.75)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left - left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
div.style.color = 'black';
div.style.fontSize = '14px';
div.textContent = `${barcode.rawValue}`;
img.before(div);
});
} catch(e) {
console.error(e);
}
});
文本检测
Shape Detection API 的最后一个功能是文本检测。现在,您应该知道了:我们需要一个包含文字的图片的网页,比如包含 Google 图书扫描结果的这个网页。在支持的浏览器中,您会看到系统识别出的文本,以及在文本段落周围绘制一个边界框。重新混合或修改 Glitch 项目(尤其是 script.js 文件),看看是如何工作的。
若要进行动态测试,请在无痕式标签页或访客模式下访问此搜索结果页。现在,将以下代码段粘贴到 Chrome 开发者工具的“控制台”标签页中。稍等片刻,系统就会识别部分文字。
document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
try {
const texts = await new TextDetector().detect(img);
texts.forEach(text => {
const div = document.createElement('div');
const box = text.boundingBox;
const computedStyle = getComputedStyle(img);
const [top, right, bottom, left] = [
computedStyle.marginTop,
computedStyle.marginRight,
computedStyle.marginBottom,
computedStyle.marginLeft
].map(m => parseInt(m, 10));
const scaleX = img.width / img.naturalWidth;
const scaleY = img.height / img.naturalHeight;
div.style.backgroundColor = 'rgba(255, 255, 255, 0.75)';
div.style.position = 'absolute';
div.style.top = `${scaleY * box.top + top}px`;
div.style.left = `${scaleX * box.left - left}px`;
div.style.width = `${scaleX * box.width}px`;
div.style.height = `${scaleY * box.height}px`;
div.style.color = 'black';
div.style.fontSize = '14px';
div.innerHTML = text.rawValue;
img.before(div);
});
} catch(e) {
console.error(e);
}
});
反馈
您觉得这个 API 怎么样?请简要回答此调查问卷,这对我们很有帮助:
此 API 是否直观易用?
您是否获得了运行示例?
还有其他意见吗?是否缺少某些功能?请在此调查问卷中提供简短的反馈意见。谢谢!
5. Web Share Target API
Web Share Target API 允许已安装的 Web 应用向底层操作系统注册为分享目标,以便通过 Web Share API 或系统事件(例如操作系统级分享按钮)接收分享的内容。
安装要分享到的 PWA
首先,您需要一个可分享内容的 PWA。这次 Airhorner(幸运)无法胜任,但 Web Share Target 演示版应用是您的后盾。将应用安装到设备的主屏幕上。
将内容分享到 PWA
接下来,您需要一些要分享的内容,例如 Google 相册中的照片。使用“共享”按钮,然后选择“剪贴簿”PWA 作为共享目标。
点按应用图标后,您会直接进入剪贴簿 PWA,照片就在那里。
那么,它是如何运作的呢?如需了解详情,请查看剪贴簿 PWA 的 Web 应用清单。让 Web Share Target API 正常运行的配置位于清单的 "share_target"
属性中,该属性的 "action"
字段中指向用 "params"
中列出的参数修饰的网址。
然后,共享方会据此填充此网址模板(无论是通过共享操作实现,还是由开发者使用 Web Share API 以编程方式进行控制),以便接收方可以提取参数并对其执行一些操作,例如显示参数。
{
"action": "/_share-target",
"enctype": "multipart/form-data",
"method": "POST",
"params": {
"files": [{
"name": "media",
"accept": ["audio/*", "image/*", "video/*"]
}]
}
}
反馈
您觉得这个 API 怎么样?请简要回答此调查问卷,这对我们很有帮助:
此 API 是否直观易用?
您是否获得了运行示例?
还有其他意见吗?是否缺少某些功能?请在此调查问卷中提供简短的反馈意见。谢谢!
6. Wake Lock API
为避免消耗电池电量,大多数设备会在空闲时快速进入休眠状态。虽然大多数情况下这样做没有问题,但有些应用需要让屏幕或设备保持唤醒状态才能完成其工作。Wake Lock API 提供了一种阻止设备调暗和锁定屏幕或阻止设备进入休眠状态的方法。此功能可实现新的体验,迄今为止仍需要原生应用。
设置屏保
如需测试 Wake Lock API,您必须先确保设备会进入休眠状态。因此,请在操作系统的偏好设置窗格中激活所选的屏保,并确保该屏保在 1 分钟后启动。在这段时间内不要放任何设备,确保设备能正常运行(是的,我知道这很痛苦)。以下屏幕截图显示的是 macOS,您当然可以在您的 Android 移动设备或任何受支持的桌面平台上尝试此方法。
设置屏幕唤醒锁定
现在,您已确定屏保正在正常运行,接下来您将使用 "screen"
类型的唤醒锁定来阻止屏保正常运行。前往 Wake Lock 演示版应用,然后点击 Activate (激活)
screen
Wake Lock 复选框。
从那一刻起,唤醒锁定就会启用。如果您有足够的耐心,可以让设备保持不动一会儿,那么您现在会看到屏保确实没有启动。
那么,它是如何运作的呢?如需了解详情,请前往 Wake Lock 演示应用的 Glitch 项目,并查看 script.js。代码段的要点如下。打开一个新标签页(或使用您打开的任何标签页),然后将以下代码粘贴到 Chrome 开发者工具控制台中。点击窗口后,您应该会看到一个唤醒锁定,该锁定正好处于活动状态 10 秒(请参阅控制台日志),并且您的屏保应该不会启动。
if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
let wakeLock = null;
const requestWakeLock = async () => {
try {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
console.log('Wake Lock was released');
});
console.log('Wake Lock is active');
} catch (e) {
console.error(`${e.name}, ${e.message}`);
}
};
requestWakeLock();
window.setTimeout(() => {
wakeLock.release();
}, 10 * 1000);
}
反馈
您觉得这个 API 怎么样?请简要回答此调查问卷,这对我们很有帮助:
此 API 是否直观易用?
您是否获得了运行示例?
还有其他意见吗?是否缺少某些功能?请在此调查问卷中提供简短的反馈意见。谢谢!
7. 联系人选取器 API
我们非常高兴的一个 API 就是 Contact Picker API。它允许 Web 应用访问设备的原生联系人管理器中的联系人,这样您的 Web 应用就能够访问您的联系人姓名、电子邮件地址和电话号码您可以指定是需要一个联系人还是多个联系人,还可以指定是需要所有字段还是只希望部分字段(包括姓名、电子邮件地址和电话号码)。
隐私注意事项
选择器打开后,您就可以选择要分享的联系人。您会发现,没有“全选”选项选择,这是有意为之:我们希望分享是一个理智的决定。同样,访问不是连续的,而是一次性的。
访问通讯录
访问联系人是一项简单的任务。在选择器打开之前,您可以指定所需的字段(选项为 name
、email
和 telephone
),以及是要访问多个联系人还是仅访问一个联系人。您可以打开演示版应用,在 Android 设备上测试此 API。源代码的相关部分实质上是以下代码段:
getContactsButton.addEventListener('click', async () => {
const contacts = await navigator.contacts.select(
['name', 'email'],
{multiple: true});
if (!contacts.length) {
// No contacts were selected, or picker couldn't be opened.
return;
}
console.log(contacts);
});
8. 异步剪贴板 API
复制和粘贴文本
在此之前,仍无法以编程方式将图片复制并粘贴到系统的剪贴板中。最近,我们为 Async Clipboard API 添加了图片支持,
这样,你现在可以复制和粘贴图片了。新功能是您还可以将图片写入剪贴板。异步剪贴板 API 目前支持复制和粘贴文本。您可以通过调用 navigator.clipboard.writeText() 将文本复制到剪贴板,稍后通过调用 navigator.clipboard.readText() 粘贴该文本。
复制和粘贴图片
现在,您还可以将图片写入剪贴板。为此,您需要将图片数据作为 blob 传递给剪贴板项构造函数。最后,您可以通过调用 navigator.clipboard.write() 来复制此剪贴板项。
// Copy: Writing image to the clipboard
try {
const imgURL = 'https://developers.google.com/web/updates/images/generic/file.png';
const data = await fetch(imgURL);
const blob = await data.blob();
await navigator.clipboard.write([
new ClipboardItem(Object.defineProperty({}, blob.type, {
value: blob,
enumerable: true
}))
]);
console.log('Image copied.');
} catch(e) {
console.error(e, e.message);
}
从剪贴板粘贴图片看起来相当复杂,但实际上只是从剪贴板项中重新获取 blob。由于可能有多种结果,因此您需要逐一检查,直到找到感兴趣的结果。出于安全考虑,目前仅支持 PNG 图片,但将来可能会支持更多图片格式。
async function getClipboardContents() {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
try {
for (const type of clipboardItem.types) {
const blob = await clipboardItem.getType(type);
console.log(URL.createObjectURL(blob));
}
} catch (e) {
console.error(e, e.message);
}
}
} catch (e) {
console.error(e, e.message);
}
}
您可以在演示版应用中查看此 API 的实际效果,上面嵌入了其源代码中的相关代码段。无需权限即可将图片复制到剪贴板,但需要授予从剪贴板粘贴图片的权限。
授予访问权限后,您可以从剪贴板中读取图片并将其粘贴到应用中:
9. 成功了!
恭喜,您已完成了此 Codelab 的学习。再次提醒您,大多数 API 仍处于变化和积极开发阶段。因此,我们衷心感谢您的反馈,因为只有与您这样的用户进行互动,我们才能正确提供这些 API。
我们还建议您经常查看我们的功能着陆页。我们会持续更新,并在其中提供指向我们所开发的 API 的所有深入文章的链接。继续保持!
Tom 和整个 Capabilities 团队 🐡?