现在可以读取和写入 NFC 标签。
什么是 Web NFC?
NFC 的全称是近距离无线通信 (NFC),是一种近距离无线技术。 在 13.56 MHz 运行,实现距离相距的设备之间的通信 小于 10 cm,传输速率最高为 424 kbit/s。
Web NFC 使网站能够对 NFC 标签进行读取和写入 靠近用户设备(通常为 5-10 厘米、2-4 英寸)。 当前范围仅限于 NFC 数据交换格式 (NDEF),一种轻量级 适用于各种不同代码格式的二进制消息格式。
<ph type="x-smartling-placeholder">建议的用例
Web NFC 仅限于 NDEF,因为 这样写 NDEF 数据就更容易量化低层级 I/O 操作(例如 ISO-DEP、NFC-A/B、NFC-F)、点对点通信模式和基于主机的卡 不支持模拟 (HCE)。
可以使用 Web NFC 的网站的示例包括:
- 博物馆和美术馆可以展示有关展览的更多信息 当用户将其设备轻触展览附近的 NFC 卡时触发。
- 广告资源管理网站可以从 更新有关其内容的信息。
- 会议网站可利用该设备在活动期间扫描 NFC 标记,并确保 它们被锁定以防止进一步更改上面写入的信息。
- 网站可以使用它来分享设备或服务所需的初始密钥 并在运维套件中部署配置数据 模式。
当前状态
步骤 | 状态 |
---|---|
1. 创建铺垫消息 | 完成 |
2. 创建规范的初始草稿 | 完成 |
3. 收集反馈和对设计进行迭代 | 完成 |
4. 源试用 | 完成 |
5. 投放 | 完成 |
使用网络 NFC
功能检测
针对硬件的功能检测与您所习惯的检测不同。
如果存在 NDEFReader
,则表示浏览器支持 Web NFC,
而不是是否存在所需的硬件。特别是,如果硬件
则某些调用返回的 promise 将拒绝。我将提供
当我描述NDEFReader
时,会非常有用。
if ('NDEFReader' in window) { /* Scan and write NFC tags */ }
术语
NFC 标签是一种被动 NFC 设备,也就是 当有活跃的 NFC 设备(如手机)在附近时感应到感应。NFC 标签 有多种形式和形式,如贴纸、信用卡、手臂手腕 。
<ph type="x-smartling-placeholder">NDEFReader
对象是 Web NFC 中的入口点,用于公开功能
用于准备在显示 NDEF 标签时执行的读取和/或写入操作
就会出现这种问题NDEFReader
中的 NDEF
代表 NFC 数据交换
格式,由 NFC Forum 标准化的轻量级二进制消息格式。
NDEFReader
对象用于对来自 NFC 标签的传入 NDEF 消息执行操作
以及将 NDEF 消息写入范围内的 NFC 标签。
支持 NDEF 的 NFC 标签就像便笺一样。任何人都可以阅读,并且 除非它是只读的,否则任何人都可以对其进行写入。它包含一个 NDEF 消息,它封装了一条或多条 NDEF 记录。每条 NDEF 记录都是 包含数据载荷和关联类型信息的二进制结构。 Web NFC 支持以下 NFC Forum 标准化记录类型:空、文本、 网址、智能海报、MIME 类型、绝对网址、外部类型、未知和本地 类型。
<ph type="x-smartling-placeholder">扫描 NFC 标签
如需扫描 NFC 标签,请先实例化新的 NDEFReader
对象。正在呼叫scan()
返回一个 promise。如果之前没有访问权限,系统可能会提示用户
已授予。如果满足以下所有条件,promise 将解析:
- 只有在响应用户手势(例如触摸手势或 点击鼠标。
- 用户已允许网站与 NFC 设备互动。
- 用户的手机支持 NFC。
- 用户已在手机上启用 NFC。
一旦解析了 promise,传入的 NDEF 消息就可以通过
通过事件监听器订阅 reading
事件。您还应订阅
readingerror
事件,以便在出现不兼容的 NFC 标签时收到通知
距离。
const ndef = new NDEFReader();
ndef.scan().then(() => {
console.log("Scan started successfully.");
ndef.onreadingerror = () => {
console.log("Cannot read data from the NFC tag. Try another one?");
};
ndef.onreading = event => {
console.log("NDEF message read.");
};
}).catch(error => {
console.log(`Error! Scan failed to start: ${error}.`);
});
当有 NFC 标签靠近时,会触发 NDEFReadingEvent
事件。它
包含两个独有的属性:
serialNumber
表示设备的序列号(例如 00-11-22-33-44-55-66),如果没有,则为空字符串。message
表示存储在 NFC 标签中的 NDEF 消息。
如需读取 NDEF 消息的内容,请循环遍历 message.records
并
根据其 recordType
适当处理其 data
会员。
data
成员作为 DataView
公开,因为它允许处理
数据以 UTF-16 编码的情况。
ndef.onreading = event => {
const message = event.message;
for (const record of message.records) {
console.log("Record type: " + record.recordType);
console.log("MIME type: " + record.mediaType);
console.log("Record id: " + record.id);
switch (record.recordType) {
case "text":
// TODO: Read text record with record data, lang, and encoding.
break;
case "url":
// TODO: Read URL record with record data.
break;
default:
// TODO: Handle other records with record data.
}
}
};
写入 NFC 标签
如需编写 NFC 标签,请先实例化新的 NDEFReader
对象。正在呼叫
write()
会返回一个 promise。如果用户未授予访问权限,则系统可能会提示用户
权限。此时,NDEF 消息已“准备”并承诺
满足以下所有条件时,解析成功:
- 只有在响应用户手势(例如触摸手势或 点击鼠标。
- 用户已允许网站与 NFC 设备互动。
- 用户的手机支持 NFC。
- 用户已在手机上启用 NFC。
- 用户已点按 NFC 标签,且已成功写入 NDEF 消息。
如需将文本写入 NFC 标签,请向 write()
方法传递一个字符串。
const ndef = new NDEFReader();
ndef.write(
"Hello World"
).then(() => {
console.log("Message written.");
}).catch(error => {
console.log(`Write failed :-( try again: ${error}.`);
});
要将网址记录写入 NFC 标签,请传递表示 NDEF 的字典
发送给 write()
的消息。在下面的示例中,NDEF 消息是一个字典
并带有 records
键。其值是一个记录数组,在本例中为网址
记录定义为一个对象,其中 recordType
键设置为 "url"
,data
键设置为网址字符串。
const ndef = new NDEFReader();
ndef.write({
records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
}).then(() => {
console.log("Message written.");
}).catch(error => {
console.log(`Write failed :-( try again: ${error}.`);
});
也可以将多个记录写入一个 NFC 标签。
const ndef = new NDEFReader();
ndef.write({ records: [
{ recordType: "url", data: "https://w3c.github.io/web-nfc/" },
{ recordType: "url", data: "https://web.dev/nfc/" }
]}).then(() => {
console.log("Message written.");
}).catch(error => {
console.log(`Write failed :-( try again: ${error}.`);
});
如果 NFC 标签包含不应被覆盖的 NDEF 消息,则将
在传递给 write()
的选项中将 overwrite
属性设为 false
方法。在这种情况下,如果 NDEF 消息
已存储在 NFC 标签中。
const ndef = new NDEFReader();
ndef.write("Writing data on an empty NFC tag is fun!", { overwrite: false })
.then(() => {
console.log("Message written.");
}).catch(error => {
console.log(`Write failed :-( try again: ${error}.`);
});
将 NFC 标签设为只读
为防止恶意用户覆盖 NFC 标签的内容,可以通过以下方法: 使 NFC 标签永久处于只读状态。此操作是一个单向过程 无法撤消。NFC 标签一旦设为只读,便无法写入 。
如需将 NFC 标签设为只读,请先实例化一个新的 NDEFReader
对象。正在呼叫
makeReadOnly()
会返回一个 promise。如果用户未授予访问权限,则系统可能会提示用户
权限。如果满足以下所有条件,promise 将解析
符合:
- 只有在响应用户手势(例如触摸手势或 点击鼠标。
- 用户已允许网站与 NFC 设备互动。
- 用户的手机支持 NFC。
- 用户已在手机上启用 NFC。
- 用户已点按 NFC 标签,且 NFC 标签已成功设为只读。
const ndef = new NDEFReader();
ndef.makeReadOnly()
.then(() => {
console.log("NFC tag has been made permanently read-only.");
}).catch(error => {
console.log(`Operation failed: ${error}`);
});
下面介绍了如何在向 NFC 标签写入数据后将其设为永久只读状态。
const ndef = new NDEFReader();
try {
await ndef.write("Hello world");
console.log("Message written.");
await ndef.makeReadOnly();
console.log("NFC tag has been made permanently read-only after writing to it.");
} catch (error) {
console.log(`Operation failed: ${error}`);
}
由于 makeReadOnly()
适用于 Android 设备的 Chrome 100 或更高版本,请查看
如果以下产品支持此功能:
if ("NDEFReader" in window && "makeReadOnly" in NDEFReader.prototype) {
// makeReadOnly() is supported.
}
安全与权限
Chrome 团队按照核心原则设计和实现了 Web NFC 详见控制对强大的 Web 平台的访问权限 功能,包括用户控制、透明度和人体工程学。
由于 NFC 扩展了恶意信息可能获取的信息域 限制 NFC 的可用性,以最大限度地提高用户的认知度和 对 NFC 的使用进行控制。
<ph type="x-smartling-placeholder">Web NFC 仅适用于顶级框架和安全浏览上下文 (HTTPS)
)。源站在处理"nfc"
用户手势(例如点击按钮)。NDEFReader
scan()
、write()
和
如果之前未授予访问权限,则 makeReadOnly()
方法会触发用户提示
已授予。
document.querySelector("#scanButton").onclick = async () => {
const ndef = new NDEFReader();
// Prompt user to allow website to interact with NFC devices.
await ndef.scan();
ndef.onreading = event => {
// TODO: Handle incoming NDEF messages.
};
};
用户发起的权限提示与现实世界、物理环境的组合 使设备位于目标 NFC 标记的移动反映选择器 可在其他文件和设备访问 API 中找到的 JSON 文件。
要执行扫描或写入,网页必须在用户触摸时可见 NFC 标签。浏览器使用触感反馈来指示 点按。如果显示屏关闭或设备已关闭,将无法访问 NFC 无线装置 已锁定。对于不可见的网页,接收和推送 NFC 内容 暂停,并在网页再次显示时恢复。
借助 Page Visibility API,可以跟踪 更改。
document.onvisibilitychange = event => {
if (document.hidden) {
// All NFC operations are automatically suspended when document is hidden.
} else {
// All NFC operations are resumed, if needed.
}
};
食谱集
下面是一些可帮助您上手的代码示例。
检查权限
Permissions API 允许检查 "nfc"
权限是否
已授予。此示例展示了在以下情况下,如何在不发生用户互动的情况下扫描 NFC 标签:
否则,系统会显示一个按钮。请注意,
机制适用于编写 NFC 标签,因为它在
。
const ndef = new NDEFReader();
async function startScanning() {
await ndef.scan();
ndef.onreading = event => {
/* handle NDEF messages */
};
}
const nfcPermissionStatus = await navigator.permissions.query({ name: "nfc" });
if (nfcPermissionStatus.state === "granted") {
// NFC access was previously granted, so we can start NFC scanning now.
startScanning();
} else {
// Show a "scan" button.
document.querySelector("#scanButton").style.display = "block";
document.querySelector("#scanButton").onclick = event => {
// Prompt user to allow UA to send and receive info when they tap NFC devices.
startScanning();
};
}
取消 NFC 操作
使用 AbortController
基元可以轻松取消 NFC
操作。以下示例展示了如何将 signal
的
AbortController
通过 NDEFReader 的选项scan()
、makeReadOnly()
、
write()
方法并同时中止这两项 NFC 操作。
const abortController = new AbortController();
abortController.signal.onabort = event => {
// All NFC operations have been aborted.
};
const ndef = new NDEFReader();
await ndef.scan({ signal: abortController.signal });
await ndef.write("Hello world", { signal: abortController.signal });
await ndef.makeReadOnly({ signal: abortController.signal });
document.querySelector("#abortButton").onclick = event => {
abortController.abort();
};
写入后读取
依次使用 write()
和 scan()
与 AbortController
基元支持在向 NFC 标签写入消息后对其进行读取。
以下示例展示了如何将短信写入 NFC 标签并读取
NFC 标记中收到新消息。它会在三秒后停止扫描。
// Waiting for user to tap NFC tag to write to it...
const ndef = new NDEFReader();
await ndef.write("Hello world");
// Success! Message has been written.
// Now scanning for 3 seconds...
const abortController = new AbortController();
await ndef.scan({ signal: abortController.signal });
const message = await new Promise((resolve) => {
ndef.onreading = (event) => resolve(event.message);
});
// Success! Message has been read.
await new Promise((r) => setTimeout(r, 3000));
abortController.abort();
// Scanning is now stopped.
读取和写入文本记录
文本记录 data
可通过使用TextDecoder
记录 encoding
属性。请注意,文本记录的语言为
通过其 lang
属性提供。
function readTextRecord(record) {
console.assert(record.recordType === "text");
const textDecoder = new TextDecoder(record.encoding);
console.log(`Text: ${textDecoder.decode(record.data)} (${record.lang})`);
}
要写入简单的文本记录,请将字符串传递给 NDEFReader write()
方法。
const ndef = new NDEFReader();
await ndef.write("Hello World");
默认情况下,文本记录采用 UTF-8 编码,并采用当前文档的语言,
这两个属性(encoding
和 lang
)都可以使用完整语法进行指定
(用于创建自定义 NDEF 记录)。
function a2utf16(string) {
let result = new Uint16Array(string.length);
for (let i = 0; i < string.length; i++) {
result[i] = string.codePointAt(i);
}
return result;
}
const textRecord = {
recordType: "text",
lang: "fr",
encoding: "utf-16",
data: a2utf16("Bonjour, François !")
};
const ndef = new NDEFReader();
await ndef.write({ records: [textRecord] });
读取和写入网址记录
使用 TextDecoder
解码记录的 data
。
function readUrlRecord(record) {
console.assert(record.recordType === "url");
const textDecoder = new TextDecoder();
console.log(`URL: ${textDecoder.decode(record.data)}`);
}
要写入网址记录,请将 NDEF 消息字典传递给 NDEFReader
write()
方法结合使用。NDEF 消息中包含的网址记录定义为
对象,其中 recordType
键设置为 "url"
,data
键设置为网址
字符串。
const urlRecord = {
recordType: "url",
data:"https://w3c.github.io/web-nfc/"
};
const ndef = new NDEFReader();
await ndef.write({ records: [urlRecord] });
读取和写入 MIME 类型记录
MIME 类型记录的 mediaType
属性表示
NDEF 记录载荷,以便正确解码 data
。例如,使用
JSON.parse
用于解码 JSON 文本,并使用 Image 元素解码图片数据。
function readMimeRecord(record) {
console.assert(record.recordType === "mime");
if (record.mediaType === "application/json") {
const textDecoder = new TextDecoder();
console.log(`JSON: ${JSON.parse(decoder.decode(record.data))}`);
}
else if (record.mediaType.startsWith('image/')) {
const blob = new Blob([record.data], { type: record.mediaType });
const img = new Image();
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
}
else {
// TODO: Handle other MIME types.
}
}
要写入 MIME 类型记录,请将 NDEF 邮件字典传递给 NDEFReader
write()
方法结合使用。NDEF 消息中包含的 MIME 类型记录已定义
作为对象,并且 recordType
键设置为 "mime"
,mediaType
键设置为
内容的实际 MIME 类型,以及将 data
键设置为
是 ArrayBuffer
或提供对 ArrayBuffer
的视图(例如,
Uint8Array
、DataView
)。
const encoder = new TextEncoder();
const data = {
firstname: "François",
lastname: "Beaufort"
};
const jsonRecord = {
recordType: "mime",
mediaType: "application/json",
data: encoder.encode(JSON.stringify(data))
};
const imageRecord = {
recordType: "mime",
mediaType: "image/png",
data: await (await fetch("icon1.png")).arrayBuffer()
};
const ndef = new NDEFReader();
await ndef.write({ records: [jsonRecord, imageRecord] });
读取和写入绝对网址记录
绝对网址记录 data
可通过简单的 TextDecoder
进行解码。
function readAbsoluteUrlRecord(record) {
console.assert(record.recordType === "absolute-url");
const textDecoder = new TextDecoder();
console.log(`Absolute URL: ${textDecoder.decode(record.data)}`);
}
要写入绝对网址记录,请将 NDEF 消息字典传递给
NDEFReader write()
方法。NDEF 中包含的绝对网址记录
消息定义为对象,并将 recordType
键设置为 "absolute-url"
以及一个设置为网址字符串的 data
键。
const absoluteUrlRecord = {
recordType: "absolute-url",
data:"https://w3c.github.io/web-nfc/"
};
const ndef = new NDEFReader();
await ndef.write({ records: [absoluteUrlRecord] });
读取和写入智能海报记录
智能海报记录(用于杂志广告、传单、广告牌
等),将某些 Web 内容描述为包含 NDEF 的 NDEF 记录
作为其载荷。调用 record.toRecords()
以将 data
转换为列表
智能海报记录中包含的记录。它应该包含网址记录、
一个用于标题的文本记录、一个图片的 MIME 类型记录以及一些自定义
本地类型记录(如 ":t"
、":act"
和 ":s"
)
智能海报记录的类型、操作和大小。
本地类型记录仅在包含
NDEF 记录。在类型的含义在外部无关紧要时使用它们
所包含记录的本地上下文,以及存储难以使用的情况
限制条件。在 Web NFC 中,本地类型记录名称始终以 :
开头(例如
":t"
、":s"
、":act"
)。这是为了区分文本记录和本地
例如输入文本记录。
function readSmartPosterRecord(smartPosterRecord) {
console.assert(record.recordType === "smart-poster");
let action, text, url;
for (const record of smartPosterRecord.toRecords()) {
if (record.recordType == "text") {
const decoder = new TextDecoder(record.encoding);
text = decoder.decode(record.data);
} else if (record.recordType == "url") {
const decoder = new TextDecoder();
url = decoder.decode(record.data);
} else if (record.recordType == ":act") {
action = record.data.getUint8(0);
} else {
// TODO: Handle other type of records such as `:t`, `:s`.
}
}
switch (action) {
case 0:
// Do the action
break;
case 1:
// Save for later
break;
case 2:
// Open for editing
break;
}
}
要写入智能海报记录,请将 NDEF 消息传递给 NDEFReader write()
方法。NDEF 消息中包含的智能发帖者记录被定义为
对象,该对象的 recordType
键设置为 "smart-poster"
,data
键设置为
一个对象,该对象(再次)表示包含在
智能海报记录。
const encoder = new TextEncoder();
const smartPosterRecord = {
recordType: "smart-poster",
data: {
records: [
{
recordType: "url", // URL record for smart poster content
data: "https://my.org/content/19911"
},
{
recordType: "text", // title record for smart poster content
data: "Funny dance"
},
{
recordType: ":t", // type record, a local type to smart poster
data: encoder.encode("image/gif") // MIME type of smart poster content
},
{
recordType: ":s", // size record, a local type to smart poster
data: new Uint32Array([4096]) // byte size of smart poster content
},
{
recordType: ":act", // action record, a local type to smart poster
// do the action, in this case open in the browser
data: new Uint8Array([0])
},
{
recordType: "mime", // icon record, a MIME type record
mediaType: "image/png",
data: await (await fetch("icon1.png")).arrayBuffer()
},
{
recordType: "mime", // another icon record
mediaType: "image/jpg",
data: await (await fetch("icon2.jpg")).arrayBuffer()
}
]
}
};
const ndef = new NDEFReader();
await ndef.write({ records: [smartPosterRecord] });
读取和写入外部类型记录
如需创建应用定义的记录,请使用外部类型记录。这些问题
包含可通过 toRecords()
访问的 NDEF 消息作为载荷。他们的
name 包含签发机构的域名、冒号和类型
长度至少为一个字符的名称,例如 "example.com:foo"
。
function readExternalTypeRecord(externalTypeRecord) {
for (const record of externalTypeRecord.toRecords()) {
if (record.recordType == "text") {
const decoder = new TextDecoder(record.encoding);
console.log(`Text: ${textDecoder.decode(record.data)} (${record.lang})`);
} else if (record.recordType == "url") {
const decoder = new TextDecoder();
console.log(`URL: ${decoder.decode(record.data)}`);
} else {
// TODO: Handle other type of records.
}
}
}
要写入外部类型记录,请将 NDEF 消息字典传递给
NDEFReader write()
方法。NDEF 中包含的外部类型记录
消息定义为对象,并将 recordType
键设置为
外部类型和 data
键,该键设置为表示 NDEF 消息的对象
包含在外部类型记录中。请注意,data
键也可以
ArrayBuffer
,或者提供对 ArrayBuffer
的视图(例如,
Uint8Array
、DataView
)。
const externalTypeRecord = {
recordType: "example.game:a",
data: {
records: [
{
recordType: "url",
data: "https://example.game/42"
},
{
recordType: "text",
data: "Game context given here"
},
{
recordType: "mime",
mediaType: "image/png",
data: await (await fetch("image.png")).arrayBuffer()
}
]
}
};
const ndef = new NDEFReader();
ndef.write({ records: [externalTypeRecord] });
读取和写入空记录
空记录没有载荷。
要写入空记录,请将 NDEF 消息字典传递给 NDEFReader
write()
方法结合使用。NDEF 消息中包含的空记录定义为
一个对象,其 recordType
键设置为 "empty"
。
const emptyRecord = {
recordType: "empty"
};
const ndef = new NDEFReader();
await ndef.write({ records: [emptyRecord] });
浏览器支持
Android 设备(Chrome 89)支持 Web NFC。
开发提示
下面列出了当我开始使用 Web NFC 时应该提前了解的一些信息:
- 在 Web NFC 运行之前,Android 会在操作系统级别处理 NFC 标签。
- 您可以在 material.io 上找到 NFC 图标。
- 使用 NDEF 记录
id
可在需要时轻松识别记录。 - 支持 NDEF 且未格式化的 NFC 标签包含一条空类型的记录。
- 编写 Android 应用记录很简单,如下所示。
const encoder = new TextEncoder();
const aarRecord = {
recordType: "android.com:pkg",
data: encoder.encode("com.example.myapp")
};
const ndef = new NDEFReader();
await ndef.write({ records: [aarRecord] });
演示
试用官方示例并查看一些酷炫的 Web NFC 演示:
。 <ph type="x-smartling-placeholder">反馈
网络 NFC 社区小组和 Chrome 团队非常希望了解您对 Web NFC 的想法和体验。
向我们介绍 API 设计
API 是否存在无法按预期运行的地方?或者,在那里 缺少实现想法所需的方法或属性?
在 Web NFC GitHub 代码库中提交规范问题,或将您的想法添加到 一个现有问题。
报告实现存在的问题
您在 Chrome 的实现过程中是否发现了错误?还是 与规范不同?
访问 https://new.crbug.com 提交 bug。务必尽可能多添加一些
提供重现错误的简单说明
组件设置为 Blink>NFC
。Glitch 非常适用于以下情况:
轻松快速的重现问题
表示支持
您打算使用 Web NFC 吗?您的公开支持对 Chrome 团队有所帮助 向其他浏览器供应商展示重要性 支持他们。
使用 # 标签向 @ChromiumDev 发送推文
#WebNFC
并告诉我们您使用它的地点和方式。
实用链接
- 规格
- Web NFC 演示 |Web NFC 演示来源
- 跟踪错误
- ChromeStatus.com 条目
- Blink 组件:
Blink>NFC
致谢
非常感谢 Intel 员工实现 Web NFC。Google Chrome 浏览器 需要一个由提交者组成的社区,他们共同努力,将 Chromium 。并非每位 Chromium 提交者都是 Google 员工, 值得特别表彰的贡献者!