現在可以讀取及寫入 NFC 標記。
什麼是網路 NFC?
NFC 代表近距離無線通訊 (近距離無線技術) 運作時間為 13.56 MHz,方便遠距離的裝置相互通訊 小於 10 公分,傳輸速率最高可達 424 kbit/秒。
網路 NFC 可讓網站讀取及寫入 NFC 標記 靠近使用者裝置的距離 (通常是 5-10 公分、2 至 4 英寸)。 目前範圍僅限於 NFC 資料交換格式 (NDEF), 支援各種代碼格式的二進位訊息格式。
建議用途
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,但
以及是否需要硬體。具體而言,如果硬體是
缺少的承諾,某些呼叫傳回的承諾就會遭到拒絕。我會提供
描述「NDEFReader
」時需注意哪些詳細資料
if ('NDEFReader' in window) { /* Scan and write NFC tags */ }
術語
NFC 標記是被動式 NFC 裝置,其功效為磁性 。NFC 標記 有多種形式和時尚,例如貼紙、信用卡、手臂手腕和 內容。
NDEFReader
物件是 Web NFC 中的進入點,用於公開相關功能
用於準備讀取和/或寫入會在 NDEF 標記時執行的動作
挑選鄰近裝置。NDEFReader
中的 NDEF
代表 NFC 資料交換
格式:一種輕量的二進位訊息格式,經 NFC 論壇標準化。
NDEFReader
物件是用來對來自 NFC 標記的 NDEF 訊息執行動作
以及用於將 NDEF 訊息寫入範圍內的 NFC 標記。
支援 NDEF 的 NFC 標記就像是便利貼。任何人都可以閱讀 除非處於唯讀狀態 否則任何人都能寫入其中包含一個 NDEF 訊息,封裝了一或多筆 NDEF 記錄。每筆 NDEF 記錄都是一筆記錄 包含資料酬載和相關類型資訊的二進位結構。 Web NFC 支援下列 NFC 論壇標準化記錄類型:空白、文字、 網址、智慧海報、MIME 類型、絕對網址、外部類型、不明和本機 類型。
掃描 NFC 標記
如要掃描 NFC 標記,請先將新的 NDEFReader
物件例項化。正在撥打 scan()
會傳回保證。如果使用者先前曾進行存取,系統可能會提示使用者
。只要符合下列條件,承諾就會解決:
- 只有在使用者手勢 (例如觸控手勢或 按一下滑鼠左鍵
- 使用者已允許網站與 NFC 裝置互動。
- 使用者的手機支援 NFC。
- 使用者已啟用手機的 NFC。
承諾解決後,傳入的 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()
會傳回承諾值。如果未授予存取權,系統可能會提示使用者
先前授予的權限此時,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()
會傳回承諾值。如果未授予存取權,系統可能會提示使用者
先前授予的權限如果符合下列所有條件,承諾產品就會解決
滿足:
- 只有在使用者手勢 (例如觸控手勢或 按一下滑鼠左鍵
- 使用者已允許網站與 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 Platform 存取權) 各項功能:包括使用者控制權、資訊公開以及人因工程作業。
因為 NFC 會擴大可能成為惡意來源的資訊領域 因此 NFC 只能提供給使用者,盡可能提高使用者品牌認知和 控管 NFC 使用情形
網路 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 標記上的移動時,系統會反映選擇工具 模式。
如要執行掃描或寫入作業,使用者必須輕觸螢幕時顯示網頁 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()
,然後搭配 AbortController
使用 scan()
基本功能可讓您在寫入訊息後讀取 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] });
讀取及寫入智慧型海報記錄
智慧海報紀錄 (用於雜誌廣告、傳單、看板廣告、
) 描述部分網路內容為包含 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
鍵設為
代表 (再一次) 包含 NDEF 訊息的物件
智慧海報記錄。
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] });
讀取及寫入外部類型記錄
如要建立應用程式定義的記錄,請使用外部類型記錄。這類
包含 NDEF 訊息做為酬載,可透過 toRecords()
存取。他們的
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] });
瀏覽器支援
搭載 Chrome 89 的 Android 裝置皆可使用網路 NFC。
開發人員秘訣
以下是我一開始使用 Web NFC 技術時,可以得知的資訊:
- 在 Web NFC 功能開始之前,Android 會在作業系統層級處理 NFC 標記。
- 您可以在 material.io 找到 NFC 圖示。
- 需要時,可以使用 NDEF 記錄
id
輕鬆識別記錄。 - 如果 NFC 標記 (支援 NDEF) 未設定格式,就會包含一筆空白類型的記錄。
- 寫入 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 示範模式:
,瞭解如何調查及移除這項存取權。意見回饋
網路 NFC 社群群組和 Chrome 團隊期待能瞭解你的網路 NFC 使用體驗和使用體驗。
請與我們分享 API 設計
是否有 API 未正常運作?或者在那裡
前往 Web NFC GitHub 存放區提交規格問題,或將您的想法新增至 現有的問題。
回報導入問題
您發現 Chrome 實作錯誤嗎?另一種是實作 該怎麼辦?
前往 https://new.crbug.com 回報錯誤。請務必盡量附上
請盡可能詳細說明,提供重現錯誤的簡單操作說明,並請
「元件」設為 Blink>NFC
。Glitch 適用於以下情境:
輕鬆快速地做出反應
顯示支援
您是否打算使用網路 NFC?你的公開支援能協助 Chrome 團隊 優先使用功能,並向其他瀏覽器廠商說明其重要性 對他們提供支援
使用主題標記將推文傳送至 @ChromiumDev
#WebNFC
敬上
,並說明你使用這項服務的位置和方式。
實用連結
特別銘謝
感謝 Intel 的福克斯實作 Web NFC。Google 瀏覽器 取決於多個委員會共同合作來移動 Chromium 並非所有 Chromium 修訂者都是 Google 員工,這些 貢獻者應受到特別表揚!