Web 開發未來會完全替代客戶端開發嗎?

語言: CN / TW / HK

大家好,我是 ConardLi

首先問大家一個問題,現在有一項業務需求,這個需求使用客戶端應用實現還是網頁來實現你會考慮哪些因素呢?可以在評論區告訴我。

曾幾何時,想到網頁可能我們第一時間想到的就是一些靜態頁面,但是經過數十年的蓬勃發展,網頁開始承接越來越複雜的需求,包括複雜的管理系統、網路直播、雲遊戲等能力。

但或許你仍然會認為相比可以和系統底層直接互動的原生客戶端應用還是太弱了,我們可能會因為瀏覽器“缺失” 了某一項能力而被迫選擇開發一個客戶端應用。

為此 Google 啟動了一個名為 Fugu 的專案,它的目標就是讓開發者能夠在 Web 生態中做任何事情,包括以前只有客戶端應用才能做的事情。

Fugu 專案中, GoogleChrome 規劃了數百項能力,如今進展已經過半,我們一起來看看瀏覽器現在擁有了哪些或許你還不知道的能力吧 ...

和藍芽裝置互動 【Chrome 56】

Web Bluetooth API 為瀏覽器提供了連線藍芽裝置並與之互動的能力。

這意味著:你的網站可以直接連線你的運動手錶,檢視步數、心率等資料,可以直接控制你的藍芽音響等等。而這些能力,之前你必須要下載一個 App 才能實現了 ...

獲取是否支援藍芽連線:

navigator.bluetooth.getAvailability().then((available) => {
  if (available) {
    console.log("裝置支援藍芽連線!");
  } else {
    console.log("裝置不支援藍芽");
  }
});

連線到藍芽裝置:

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
  // 獲取裝置名稱 [ConardLi]
  console.log(device.name);

  // 連線遠端 GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

瞭解更多:https://developer.chrome.com/articles/bluetooth/

和 USB 裝置互動【Chrome 61】

Web USB API 為瀏覽器提供了和 USB 裝置進行互動的能力。

說到 USB ,你很有可能會立即想到鍵盤、滑鼠、音訊、視訊和一些儲存裝置。

這些非標準化的 USB 裝置通常需要硬體供應商編寫特定於平臺的驅動程式和 SDK,開發非常繁瑣。

如果可以在 Web 上和 USB 進行互動,這意味著硬體製造商將能夠為其裝置構建跨平臺的 JavaScript SDK,這將極大簡化一個 SDK 的開發成本!另外,通過將 USB 引入 Web,也可以使得 USB 更安全、更易於使用。

下面是一段簡單的獲取 USB 裝置的程式碼:

navigator.usb.getDevices().then(devices => {
  devices.forEach(device => {
    console.log(device.productName);      // "[ConardLi] Arduino Micro "
    console.log(device.manufacturerName); // "[ConardLi] Arduino LLC"
  });
})

你還可以在 Chrome 的內部頁面 about://device-log 方便的除錯 USB 裝置:

瞭解更多:https://wicg.github.io/webusb/

非同步剪貼簿【Chrome 76】

在以前,我們一般使用 document.execCommand() 進行剪貼簿互動。雖然瀏覽器相容性還不錯,但這種剪下和貼上的方法有明顯的缺點:剪貼簿訪問是同步的,只能讀寫 DOM

對於少量文字的剪貼還好,但如果剪貼內容較大,在安全貼上內容之前,可能還需要進行耗時的清理或影象解碼,瀏覽器可能還需要從貼上的文件載入或內聯連結資源,這種情況下使用者體驗就比較糟糕了。

Asynchronous Clipboard API 的出現解決了這些問題,比如我們要將一段文字複製到剪貼簿,可以呼叫一個非同步的 writeText 函式:

async function copyPageUrl({
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('Page URL 已賦值到剪貼簿');
  } catch (err) {
    console.error('失敗了~);
  }
}

從剪貼簿讀取資料一樣也可以是非同步的:

async function getClipboardContents({
  try {
    const text = await navigator.clipboard.readText();
    console.log('貼上文字: ', text);
  } catch (err) {
    console.error('讀取剪貼簿文字失敗: ', err);
  }
}

另外,document.execCommand() 還有一個非常明顯的問題就是許可權控制過於寬鬆了,在很多情況下我們可能會擔心網站是否會私自讀取我們剪貼簿的資訊,Asynchronous Clipboard API 僅支援 HTTPS 頁面,另外在讀取剪貼簿是會向用戶傳送許可,這保證了網頁必須在使用者同意的情況下才能讀取剪貼簿:


瞭解更多:https://developer.mozilla.org/docs/Web/API/Clipboard_API

應用安裝【Chrome 80】

getInstalledRelatedApps 方法可以讓瀏覽器知道某些應用程式是否已在電腦上安裝了,當然目前僅限於 Android、WindowsPWA 應用。

const relatedApps = await navigator.getInstalledRelatedApps();
relatedApps.forEach((app) => {
  // 注意只能獲取到已經授權的應用,並不是所有應用
  console.log(app.id, app.platform, app.url);
});

想象一下你開發了一個產品的官網,在使用者下載頁面你可以根據應用的安裝狀態提示使用者是下載還是更新,甚至可以直接開啟應用...

瞭解更多:https://github.com/WICG/get-installed-related-apps

獲取聯絡人【Chrome 80】

在以前,能夠在移動裝置上訪問使用者的聯絡人一直是移動 Web 應用開發者最想要的功能之一,這也是促使他們必須選擇開發一個 App 的重要原因...

Contact Picker API  為瀏覽器提供了獲取手機聯絡人的能力。

假如我們有一個基於 Web 的電子郵件客戶端,可以直接使用 Contact Picker API 來選擇電子郵件的收件人。一個基於 WebIP 語音應用程式可以直接查詢要撥打的電話號碼。或者一些 Web 社交應用可以幫助使用者發現哪些朋友已經加入了。

比如,你開啟一個網頁遊戲,他可以直接告訴你,你的好兄弟 ConardLi 也在玩喲,快和他一起組隊來砍我吧...

下面是一個簡單的使用示例:

const props = ['name''email''tel''address''icon'];
const opts = {multipletrue};

try {
  const contacts = await navigator.contacts.select(props, opts);
  handleResults(contacts);
catch (ex) {
  // 一些錯誤...
}

瞭解更多:https://wicg.github.io/contact-api/spec/

編解碼能力【Chrome 80】

在以前,瀏覽器提供了諸如 HTMLMediaElement、WebAudio、WebRTC 等實現媒體編解碼器能力的 API,但是沒有通用的方法來靈活配置和使用這些媒體編解碼器。因此,在有效能差、耗電快等問題的情況下,許多 Web 應用還是會求助於在 JavaScriptWebAssembly 中實現媒體編解碼器。

WebCodecs 為網頁提供了對內建(軟體和硬體)媒體編碼器和解碼器的高效訪問能力。

這項能力為網頁直播、雲遊戲等對流媒體處理效能要求較高的場景下大地來了很大便利。

下面是一個從視訊渲染到 Canvas 來實現極低延遲流的示例:

function onDecoderError(error{ ... }

function streamEncodedChunks(decodeCallback{ ... }

const canvasElement = document.getElementById("canvas");
const canvasContext = canvas.getContext('2d', canvasOptions)

function paintFrameToCanvas(videoFrame{
  canvasContext.drawImage(frame, 00);

  frame.close();
}

const videoDecoder = new VideoDecoder({
  output: paintFrameToCanvas,
  error: onDecoderError
});

videoDecoder.configure({codec'vp8'}).then(() => {
  streamEncodedChunks(videoDecoder.decode.bind(videoDecoder));
}).catch(() => {});

瞭解更多:https://github.com/w3c/webcodecs/blob/main/explainer.md

為圖示新增徽章【Chrome 81】

App Badging API 可以讓 Web 應用為圖示新增一些徽章。

比如一個 Web 聊天室可以在徽章上顯示未讀的訊息數;一個 Web 象棋遊戲可以通過標記提醒輪到你下棋了;一些長耗時的後臺任務可以通過標記告訴你任務已經成功 ...

下面是一個簡單的程式碼示例:

// 設定徽章
const unreadCount = 17;
navigator.setAppBadge(unreadCount).catch((error) => {
  // 異常捕獲...
});

// 清除徽章
navigator.clearAppBadge().catch((error) => {
  // 異常捕獲...
});

瞭解更多:https://w3c.github.io/badging/

形狀檢測【Chrome 83】

在以前,我們想在 Web 上讀取一些圖片上的資料是相當困難的,比如開發者想在客戶端提取一些些特徵來構建一個二維碼閱讀器,必須要依賴一個龐大的外部 JavaScript 庫,而且效能可能很差。

但是,包括 Android、iOSmacOS 在內的作業系統,以及相機模組中的硬體晶片,通常已經具有高效能和高度優化的特徵檢測器,例如 AndroidFaceDetectoriOSCIDetector 通用特徵檢測器。

Shape Detection API 通過一組 JavaScript 介面公開了這些實現。目前支援的功能有人臉檢測、條碼檢測以及文字檢測,這意味著我們可以在 Web 上實現下面的功能:

  • 購物網站可以讓使用者直接掃描商品條碼查詢商品資訊;
  • 社交網站可以檢測人臉面部特徵,自動新增墨鏡、鬍子等道具;
  • 內容網站可以自動識別圖片上的文字,例如餐廳選單。

下面是一段簡單的人臉識別程式碼:

// [ConardLi]
const faceDetector = new FaceDetector({
  // 限制識別人臉的數量
  maxDetectedFaces5,
  // 降低精度提升速度
  fastModefalse
});
try {
  const faces = await faceDetector.detect(image);
  faces.forEach(face => drawMustache(face));
catch (e) {
  console.error('Face detection failed:', e);
}

瞭解更多:https://wicg.github.io/shape-detection-api/

獲取驗證碼【Chrome 84】

當我們在一些網站上進行註冊或登入時,可能需要驗證手機號。網頁一般會發送一個驗證碼,我們需要將驗證碼提交到網頁上來完成驗證。但是切到簡訊後複製驗證碼,再回來提交整個過程是比較繁瑣的。

WebOTP API 為瀏覽器提供了快捷讀取簡訊驗證碼的能力。

用法也非常簡單,首先我們可以為 input 新增一個 autocomplete 屬性:

<form>
  <input autocomplete="one-time-code" required/>
  <input type="submit">
</form>

然後呼叫 navigator.credentials 獲取驗證碼資訊:

if ('OTPCredential' in window) {
  window.addEventListener('DOMContentLoaded', e => {
    const input = document.querySelector('input[autocomplete="one-time-code"]');
    if (!input) return;
    const ac = new AbortController();
    const form = input.closest('form');
    if (form) {
      form.addEventListener('submit', e => {
        ac.abort();
      });
    }
    navigator.credentials.get({
      otp: { transport:['sms'] },
      signal: ac.signal
    }).then(otp => {
      input.value = otp.code;
      if (form) form.submit();
    }).catch(err => {
      console.log(err);
    });
  });
}

瞭解更多:https://wicg.github.io/WebOTP/

喚醒螢幕【Chrome 84】

試想一下,當你在某個網站上看著菜譜做飯,由於長時間未操作手機自動鎖屏了,你還需要去定期觸碰一下手機,是不是有點頭大。

Screen Wake Lock API 可體讓瀏覽器在網頁需要繼續執行時防止調暗或鎖定螢幕。

使用方式也很簡單,可以直接呼叫 navigator.wakeLock.request() 方法,返回一個 WakeLockSentinel 物件。

據說在實施了 Screen Wake Lock API 後,美國主要烹飪網站 Betty Crocker 的使用者購買意向指標增加了 300% ...

瞭解更多:https://w3c.github.io/screen-wake-lock

檔案訪問【Chrome 86】

在以前,我們只能通過 <input type="file"> 在瀏覽器上訪問檔案,需要寫出類似下面的程式碼:

const openFile = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

File System Access API 為瀏覽器提供了更好的和檔案系統互動的能力:

const openFile = async () => {
  try {
    // Always returns an array.
    const [handle] = await window.showOpenFilePicker();
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

瞭解更多:https://wicg.github.io/file-system-access/

Web NFC【Chrome 89】

NFC 代表 Near Field Communications,這是一種以 13.56 MHz 頻率執行的短距離無線技術,能夠在小於 10 釐米的距離內實現裝置之間的通訊,傳輸速率高達 424 kbit/s

Web NFC 為網站提供了在靠近使用者裝置時讀取和寫入 NFC 標籤的能力,這意味著你只需要開啟一個網站就可以刷地鐵進站了...

要掃描 NFC 標籤,首先需要例項化一個 NDEFReader 物件,並呼叫 scan 方法,下面是一個簡單的程式碼示例:

const ndef = new NDEFReader();
ndef.scan().then(() => {
  console.log("掃描開始");
  ndef.onreadingerror = () => {
    console.log("無法讀取NFC資料!");
  };
  ndef.onreading = event => {
    console.log("NFC資料讀取成功...");
  };
}).catch(error => {
  console.log(`Error! Scan failed to start: ${error}.`);
});

瞭解更多:https://wicg.github.io/file-system-access/

人機介面裝置【Chrome 89】

WebHID API 為瀏覽器提供了和人機介面裝置(簡稱 HID)互動的能力。

比如鍵盤、滑鼠、觸控板、遊戲手柄等都屬於 HID 裝置,WebHID API 提供了一系列 JavaScript API 來和這些裝置進行互動。而在以前,你必須要有一個特定的遊戲主機才可以...

想象一下,以後再也不用糾結於國行外行了,因為你直接可以在網頁裡開啟 Switch 遊戲了...

下面是一個簡單的程式碼示例:

// 在具有 Switch Joy-Con USB 供應商/產品id的裝置上進行篩選。
const filters = [
  {
    vendorId0x057e// Nintendo Co., Ltd
    productId0x2006 // Joy-Con Left
  },
  {
    vendorId0x057e// Nintendo Co., Ltd
    productId0x2007 // Joy-Con Right
  }
];

// 提示使用者選擇 Joy-Con 裝置。
const [device] = await navigator.hid.requestDevice({ filters });

瞭解更多:https://wicg.github.io/webhid/index.html

和串列埠裝置互動【Chrome 89】

序列介面(Serial port),也稱序列介面或串列埠,序列通訊介面,COM介面,簡稱串列埠。主要用於序列式逐位資料傳輸。

Web Serial API 為網站提供了一種使用 JavaScript 讀取和寫入序列裝置的方法。

這樣,我們的網站又能控制更多裝置了,比如印表機、路由器、交換機等等。

下面是一個簡單的程式碼示例:

document.querySelector('button').addEventListener('click'async () => {
  // 提示使用者選擇串列埠
  const port = await navigator.serial.requestPort();
});

從串列埠讀取資料:

const reader = port.readable.getReader();

// 監聽來自序列裝置的資料
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    reader.releaseLock();
    break;
  }
  console.log(value);
}

同樣的,你也可以在 Chromeabout://device-log 對串列埠裝置進行除錯。

空閒檢測【Chrome 94】

Idle Detection API 為網站提供了檢測使用者當前是否空閒(例如在一段時間內沒有與鍵盤、滑鼠、螢幕的互動)的能力。

例如,一個 Web 聊天室應用可以讓你知道你的好友當前是否線上,下面是一個空閒檢測的簡單示例:

// [ConardLi] 建立空閒探測器
const idleDetector = new IdleDetector();

// 設定一個在使用者空閒時觸發的監聽器
idleDetector.addEventListener('change', () => {
  const uState = idleDetector.userState;
  const sState = idleDetector.screenState;
  console.log(`Idle change: ${uState}${sState}.`);
});

// 開始監聽
await idleDetector.start({
  threshold60000,
  signal,
});

瞭解更多:https://wicg.github.io/idle-detection

WebTransport【Chrome 97】

WebTransport 是一種新的 API,使用 HTTP/3 協議作為雙向傳輸,為網站提供低延遲、雙向、客戶端-伺服器訊息傳遞能力。

你可能會問和 WebSockets 的區別是啥?

WebSockets 的訊息流特點是單一、可靠、有序,這對於某些場景的通訊需求來說是很好的;但是 WebTransport 的資料的特點是低延遲,但不保證可靠性或排序,因為它底層使用的 QUIC 握手比通過 TLS 啟動 TCP 更快。

如果你的資料通訊需要非常好的效能,但是對偶爾的丟包和排序可以容忍,比如一些網頁遊戲的場景,WebTransport 是一個更好的選擇。

下面是一個簡單的使用示例:

// 向伺服器傳送資料
const writer = transport.datagrams.writable.getWriter();
const data1 = new Uint8Array([656667]);
const data2 = new Uint8Array([686970]);
writer.write(data1);
writer.write(data2);

// 從伺服器讀取資料
const reader = transport.datagrams.readable.getReader();
while (true) {
  const {value, done} = await reader.read();
  if (done) {
    break;
  }
  // 值為 Uint8Array。
  console.log(value);
}

瞭解更多:https://wicg.github.io/web-transport/#web-transport

多屏視窗放置【Chrome 100】

Multi-Screen Window Placement API 為網頁了提供了列舉顯示器並將視窗放置在特定螢幕上的能力。

下面是一個簡單的監聽螢幕數量變化的示例:

// 獲取所有螢幕
const screenDetails = await window.getScreenDetails();
let cachedScreensLength = screenDetails.screens.length;
// 監聽螢幕變化
screenDetails.addEventListener('screenschange', (event) => {
  if (screenDetails.screens.length !== cachedScreensLength) {
    console.log(
      `螢幕數量從 ${cachedScreensLength} 變化到 ${screenDetails.screens.length}`,
    );
    cachedScreensLength = screenDetails.screens.length;
  }
});

瞭解更多:https://w3c.github.io/window-placement

最後

當然,以上我提到的只是 Fugu 專案的很小一部分,大家感興趣可以去 Fugu 官網檢視更多資訊:「Fugu API Tracker」(https://fugu-tracker.web.app/#)

你覺得上面哪些能力對你最有用呢?

你覺得未來網頁可以完全替代客戶端麼?

歡迎在評論區和我留言。

如果這篇文章幫助到了你,歡迎點贊和關注。

如果你想加入高質量前端交流群,或者你有任何其他事情想和我交流也可以新增我的個人微信 ConardLi

抖音安全團隊正在招聘前端,感興趣請檢視:抖音安全團隊招聘前端工程師

點贊在看是最大的支援⬇️❤️⬇️