跳转到内容

能力 · API

Web NFC API

发布于 更新于

有限可用不支持的浏览器: Chrome (Desktop)、Edge (Desktop)、Firefox (Desktop)、Firefox (Android)、Safari (macOS)、Safari (iOS)社区草案

NDEFReader 与贴近手机的标签交换 NFC Data Exchange Format(NDEF)消息:scan() 开始监听,每当标签进入范围就触发带标签序列号和记录的 reading 事件;write() 把一条消息写入下一个靠近的标签;makeReadOnly() 把标签永久锁定。PWA 可以用它做库存标签、活动签到,或与贴有 NFC 贴纸的设备配对;NDEF 之外的底层标签协议不对外暴露。

Android 版 Chrome 89 是唯一的实现:它提供 NDEFReader、scan()、write() 以及 reading 与 readingerror 事件,Android 版 Chrome 100 加入 makeReadOnly()(BCD api.NDEFReader)。跟随 Chrome 的 Android Chromium 浏览器(如 Samsung Internet)继承该支持;桌面 Chrome、Firefox 与 Safari 均无支持,WebKit 已提交「反对」立场(standards-positions #584)。

const reader = new NDEFReader()
reader.scan()
reader.scan(options)
reader.write(message)
reader.write(message, options)
reader.makeReadOnly()
reader.makeReadOnly(options)

三个方法都返回 Promise<undefined>。scan() 在监听开始时即兑现,而不是在读到标签时;结果通过 reading(NDEFReadingEvent,带 serialNumber 与 message)和 readingerror(标签在范围内但无法读取时触发)送达。write() 与 makeReadOnly() 在标签写入或锁定完成后兑现,没有标签靠近时可能要等上数秒。接口标注为 [SecureContext, Exposed=Window],只能在顶层浏览上下文中使用,页面不可见时会被挂起。

scan() 与 makeReadOnly() 各接受一个可选字典;write() 接受一条消息和一个可选字典。

方法 参数 类型 必填 说明
scan() options NDEFScanOptions { signal } 否 signal(AbortSignal)停止监听,并把该 reader 从活动集合中移除。
write() message NDEFMessageSource:DOMString、BufferSource 或 NDEFMessageInit 是 字符串变成一条 "text" 记录,缓冲区变成一条类型为 application/octet-stream 的 "mime" 记录,NDEFMessageInit 则显式给出记录。
write() options NDEFWriteOptions { overwrite = true, signal } 否 overwrite: false 时,标签上已有 NDEF 记录则拒绝写入。signal 中止挂起的写入。
makeReadOnly() options NDEFMakeReadOnlyOptions { signal } 否 signal 中止挂起的锁定。

NDEFMessageInit 只有一个必填成员 records,类型为 sequence<NDEFRecordInit>。

NDEFRecordInit 成员 类型 必填 说明
recordType USVString 是 "empty"、"text"、"url"、"smart-poster"、"mime"、"absolute-url"、"unknown",形如 "example.com:shelf" 的外部类型,或智能海报内部形如 ":act" 的本地类型。
mediaType USVString 仅 "mime" 载荷的 MIME 类型;其他记录类型都不得携带。
id USVString 否 记录标识符;"empty" 记录不得携带。
encoding USVString 否 仅用于 "text":"utf-8"(默认)、"utf-16"、"utf-16be" 或 "utf-16le"。
lang USVString 否 "text" 记录的 BCP 47 语言标签,如 "zh";默认取文档语言。
data any 视类型而定 "text"、"url"、"absolute-url" 用 DOMString;"mime"、"unknown" 用 BufferSource;外部类型与 "smart-poster" 用 BufferSource 或 NDEFMessageInit;"empty" 省略。

NDEFMessageInit 的嵌套最多 32 层,外部类型名不得超过 255 字节。

在返回 Promise 之前执行的检查会立即拒绝;其余检查发生在用户授予 nfc 权限之后。

异常 方法 条件
InvalidStateError 全部 调用方不是活动的顶层浏览上下文(iframe 被拒),或对 scan() 而言该 reader 已在扫描中。
信号的 abort reason(默认 AbortError) 全部 调用时 options.signal 已中止,或操作挂起期间被中止。
TypeError write() 消息无效:records 为空;嵌套超过 32 层;禁止携带 mediaType 或 id 的记录类型带了它们;data 类型与 recordType 不匹配;"text" 的 encoding 不在允许的四种之内;"url" 无法解析;外部类型名超过 255 字节;或 "smart-poster" 没有恰好一条 URL 记录。
NotAllowedError 全部 用户拒绝了 nfc 权限,或(overwrite: false 的 write())标签上已有 NDEF 记录。
NotSupportedError 全部 设备没有 NFC 适配器或无法连接适配器,适配器不支持推送数据,或标签不暴露 NDEF 技术且无法格式化。
NotReadableError 全部 浏览器不被允许使用适配器,例如系统设置中已关闭 NFC。
NetworkError write()、makeReadOnly() 向标签传输或锁定操作失败,例如标签在写入途中被移开。

scan() 本身不会报告无法读取的标签:那会以不带载荷的 readingerror 事件到达 reader,处理函数应提示用户稳住标签再试一次。

每个示例都先检测 window 中是否有 NDEFReader;在桌面浏览器和 iOS 上会走回退分支,通常是携带同一标识符的二维码或手动输入。

扫描标签,缺少支持时回退到二维码

Section titled “扫描标签,缺少支持时回退到二维码”

从一次点击开始扫描,让权限提示与手势绑定;解码 "text" 与 "url" 记录,没有 Web NFC 时显示二维码扫描器。scan() 在监听开始时兑现,真正的读取稍后通过 reading 事件到达。

async function startTagScan(onTag, showQrFallback) {
if (!('NDEFReader' in window)) {
showQrFallback();
return;
}
const reader = new NDEFReader();
reader.addEventListener('reading', ({ serialNumber, message }) => {
const decoder = new TextDecoder();
for (const record of message.records) {
if (record.recordType === 'text') onTag({ serialNumber, text: decoder.decode(record.data) });
if (record.recordType === 'url') onTag({ serialNumber, url: decoder.decode(record.data) });
}
});
reader.addEventListener('readingerror', () => onTag({ error: '标签无法读取,请稳住标签再试一次。' }));
try {
await reader.scan();
} catch (err) {
if (err.name === 'NotAllowedError') showQrFallback();
else throw err;
}
}

record.data 是 DataView;"text" 记录在 record.encoding 中携带编码、在 record.lang 中携带语言,所以 UTF-16 标签要用 new TextDecoder(record.encoding) 而不是默认值。

只向空白标签写入 URL,并设置超时

Section titled “只向空白标签写入 URL,并设置超时”

overwrite: false 保护已有数据的标签,AbortSignal 在十秒内没有标签靠近时停止挂起的写入。没有 Web NFC 时函数返回 false,界面可以改为提供打印二维码标签的选项。

async function writeLabel(url) {
if (!('NDEFReader' in window)) return false;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10_000);
try {
await new NDEFReader().write(
{ records: [{ recordType: 'url', data: url }] },
{ overwrite: false, signal: controller.signal },
);
return true;
} catch (err) {
if (err.name === 'AbortError') return false; // 10 秒内没有标签
if (err.name === 'NotAllowedError') throw new Error('标签已有内容,或 NFC 权限被拒绝');
throw err;
} finally {
clearTimeout(timer);
}
}

标签在传输途中移开会以 NetworkError 拒绝;此时标签上的记录可能只写了一部分,重试应使用 overwrite: true。

外部记录类型让应用在自己的命名空间下存放结构化数据。写入后 makeReadOnly() 锁定标签,访客的手机就无法改动它;锁定不可逆,所以示例先请求确认。

async function provisionShelfTag(shelfId, confirmLock) {
if (!('NDEFReader' in window)) return 'unsupported';
const reader = new NDEFReader();
const payload = new TextEncoder().encode(JSON.stringify({ shelfId }));
await reader.write({ records: [{ recordType: 'example.com:shelf', data: payload }] });
if (!(await confirmLock())) return 'written';
try {
await reader.makeReadOnly();
return 'locked';
} catch (err) {
if (err.name === 'NotSupportedError') return 'written-not-lockable';
throw err;
}
}

外部类型 example.com:shelf 必须全小写、使用你控制的域名并保持在 255 字节以内;读回时 recordType === 'example.com:shelf',record.data 中是同样的字节。

规范

规范状态
Web NFC API社区草案
Web NFC: NDEFReader scan() method社区草案
Web NFC: NDEFReader write() method社区草案
Web NFC: NDEFReader makeReadOnly() method社区草案
  • 图例
  • 支持
  • 部分支持
  • 需开启标志
  • 不支持
  • 未知
浏览器 / 平台支持版本置信度来源备注
Chrome (Desktop)不支持—高来源1
Chrome (Android)支持89高来源—
Edge (Desktop)不支持—高来源23
Firefox (Desktop)不支持—高来源4
Firefox (Android)不支持—高来源56
Safari (macOS)不支持—高来源7
Safari (iOS)不支持—高来源89
Samsung Internet支持15.0高来源10
WebView (Android)支持89高来源11
  1. browser-compat-data 未记录 Chrome 的支持。
  2. browser-compat-data 未记录 Edge 的支持。
  3. 由 browser-compat-data 镜像自 Chrome 的数据推导。
  4. browser-compat-data 未记录 Firefox 的支持。
  5. browser-compat-data 未记录 Firefox for Android 的支持。
  6. 由 browser-compat-data 镜像自 Firefox 的数据推导。
  7. browser-compat-data 未记录 Safari 的支持。
  8. browser-compat-data 未记录 iOS 版 Safari 的支持。
  9. 由 browser-compat-data 镜像自 Safari 的数据推导。
  10. 由 browser-compat-data 镜像自 Chrome Android 的数据推导。
  11. 由 browser-compat-data 镜像自 Chrome Android 的数据推导。

源数据: /compatibility/web-nfc.json · 全球使用占比: 35 % (StatCounter 2026-05)

来源: 规范 · MDN · 最近核验 2026-10-03 · 置信度: 高 (由来源计算)