整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          “搖一搖”功能,微信到底是怎么捕捉到的?

          所周知,微信有一個非常好玩且神奇的功能——搖一搖。我們可以通過搖一搖來進行互動,可以與搖到的人打招呼,查看同時搖晃手機的人

          剛進入時,微信會給我們兩點提示:一個是告訴我們使用搖一搖的功能,可以找到這個世界上同樣在搖晃手機的朋友,另外一個就是不要輕易相信搖到的陌生人,加好友時要謹慎核實對方身份。如果對方發布的信息對我們造成了很大困擾,可以立即向微信官方進行投訴。

          那么“搖一搖”功能,微信到底是怎么捕捉到的?其實應該是用Accelerometer API模擬的。基本思想是利用傳感器每隔一小段時間記錄x,y,z加速度,和上一次記錄的值對比,看有沒有動,但具體怎么實現要試了才知道。iOS 4.2+ HTML5可直接調用此API,但是做的時候要注意比如限制查詢頻率等優化。

          這也意味著,使用搖一搖功能需要這幾個條件:手機本身支持安裝使用微信;手機有重力傳感器,并且能正常工作。

          其實,現在市面上大部門智能手機都有重力感應的功能,只要能微信與手機兼容基本上都可使用搖一搖功能。如果是非智能手機的話,則是無法使用微信的。

          微信的搖一搖還有一個神奇的功能,那就是可以識別正在你正在觀看的電視,并且可以參與實時互動,不同的電視臺的互動方式也不同的,每次我們都可以發現不同的驚喜。

          如果識別出來,微信會自動進入正在電視臺互動的頁面,比如我們可以同意登陸進行互動。如果識別失敗了,或者電視臺這時沒有互動的話,微信也會提示我們的。

          家好,很高興又見面了,我是"高級前端?進階?",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發!

          最近讀到一篇關于 PWA 的文章《WHAT WEB CAN DO TODAY?》,加上本身自己對 PWA 這個專題也比較感興趣,所以抽空梳理了 PWA 目前主流功能以及功能描述。

          文章從用戶體驗、Native 行為拉齊、App 生命周期、PWA 周邊功能、Camera & Microphone、設備特征、操作系統、用戶 Input 輸入、屏幕和輸出等眾多維度描述了 PWA 特征。

          1. PWA 與無縫體驗

          離線模式

          Web 應用程序可以使用兩種技術提供離線體驗,較舊的實現,即應用程序緩存(Application Cache)已在瀏覽器中廣泛實現,但由于各種概念和設計缺陷,現在正在棄用。

          現代替代方案稱為 Cache API,可在 Service Worker 中使用 ,在 HTTPS 上運行的 Web 應用程序可以請求瀏覽器安裝的獨立代碼單元。 然后,該單元與所屬的 Web 應用程序分開運行,并通過事件與其進行通信。 Service Worker 是漸進式 Web 應用程序 (PWA) 理念的基本構建,除了作為推送通知 (Push Notifications) 、后臺同步(Background Sync)或地理圍欄(Geofencing)等多個復雜 API 的推動者之外,還可以用作功能齊全的 Web 代理。 其可以攔截所有 HTTP 請求,改變其內容或行為甚至管理離線緩存。

          navigator.serviceWorker.register(path)
          navigator.serviceWorker.ready
          serviceWorkerRegistration.update()
          serviceWorkerRegistration.unregister()

          Background Sync API

          Background Sync API 允許授權的 Web 應用程序不依賴于穩定的互聯網連接,并將 Web 相關操作推遲到網絡連接可用時。 API 綁定到 Service Worker,它是與所屬 Web 應用程序分離的代碼執行模型,允許后臺同步在應用程序窗口關閉后也可以運行。

          Background Sync API 本身只是向應用程序發出有關已恢復連接的信號的一種方式。 它可以與任何離線存儲解決方案一起使用,以實現數據同步方案或應用程序離線時發出的網絡請求的重放機制。

          serviceWorkerRegistration.sync.register('syncTag')
          self.addEventListener('sync', listener)

          Payment Request API

          Payment Request API 允許 Web 應用程序將付款結帳流程委托給操作系統,從而允許其使用平臺本機可用并為用戶配置的任何方法和付款提供商。 這種方法消除了應用程序端處理復雜結賬流程的負擔,縮小了支付提供商集成的范圍,并確保用戶更好地熟悉。

          const request = new PaymentRequest(
            buildSupportedPaymentMethodData(),
            buildShoppingCartDetails(),
          );
          function buildSupportedPaymentMethodData() {
            return [{supportedMethods: "https://example.com/pay"}];
          }
          
          function buildShoppingCartDetails() {
            return {
              id: "order-123",
              displayItems: [
                {
                  label: "Example item",
                  amount: {currency: "USD", value: "1.00"},
                },
              ],
              total: {
                label: "Total",
                amount: {currency: "USD", value: "1.00"},
              },
            };
          }

          Credential Management API

          Credential Management API 允許授權的 Web 應用程序代表用戶以編程方式存儲和請求用戶憑證(例如:登錄名和密碼或聯合登錄數據)。 該 API 提供了瀏覽器內置或第三方密碼存儲的替代方案,允許 Web 應用程序檢測何時以及如何存儲和讀取憑證,例如:提供自動登錄功能。

          function storeCredential() {
            event.preventDefault();
            if (!navigator.credentials) {
              alert('Credential Management API not supported');
              return;
            }
            let credentialForm = document.getElementById('credential-form');
            let credential = new PasswordCredential(credentialForm);
            //  創建證書
            navigator.credentials.store(credential)
              .then(() => log('Storing credential for' + credential.id + '(result cannot be checked by the website).'))
              .catch((err) => log('Error storing credentials:' + err));
          }
          
          function requestCredential() {
            if (!navigator.credentials) {
              alert('Credential Management API not supported');
              return;
            }
          
            let mediationValue = document.getElementById('credential-form').mediation.value;
            navigator.credentials.get({password: true, mediation: mediationValue})
              .then(credential => {
                let result = 'none';
                if (credential) {
                  result = credential.id + ',' + credential.password.replace(/./g, '*');
                }
                log('Credential read:' + result + '');
              })
              .catch((err) => log('Error reading credentials:' + err));
          }
          
          function preventSilentAccess() {
            if (!navigator.credentials) {
              alert('Credential Management API not supported');
              return;
            }
            navigator.credentials.preventSilentAccess()
              .then(() => log('Silent access prevented (mediation will be required for next credentials.get() call).'))
              .catch((err) => log('Error preventing silent access:' + err));
          }
          function waitForSms() {
            if ('OTPCredential' in window) {
              log('Waiting for SMS. Try sending yourself a following message:\n\n' +
                  'Your verification code is: 123ABC\n\n' +
                  '@whatwebcando.today #123ABC');
          
              navigator.credentials.get({otp: {transport: ['sms']}})
                .then((code) => log('Code received:' + code))
                .catch((error) => log('SMS receiving error:' + error));
            } else {
              alert('Web OTP API not supported');
            }
          }
          function log(info) {
            var logTarget = document.getElementById('result');
            var timeBadge = new Date().toTimeString().split(' ')[0];
            var newInfo = document.createElement('p');
            newInfo.innerHTML = ''+ timeBadge +' ' + info;
            logTarget.appendChild(newInfo);
          }

          2.PWA 與 Native Behaviors

          Local Notifications

          通過 Notifications API 提供的通知,允許授權的 Web 應用程序以標準化的方式吸引用戶的注意力。 通知由在瀏覽器選項卡中運行的 Web 應用程序生成,并呈現給瀏覽器選項卡區域之外的用戶。

          Notification.requestPermission([callback])
          Notification.permission
          new Notification(title, [options])
          navigator.serviceWorker.getRegistration()
          .then((reg) => reg.showNotification(title, [options]))

          Push Messages

          Push Messages 是移動平臺上眾所周知的功能,其允許授權的 Web 應用程序向用戶訂閱遠程服務器發送的消息,即使 Web 應用程序當前沒有聚焦在瀏覽器中,這些消息也可以觸發向訂閱者顯示通知。 該消息可以傳送加密的有效 payload,并且可以請求顯示自定義操作按鈕。

          serviceWorkerRegistration.pushManager.subscribe()
          serviceWorkerRegistration.pushManager.getSubscription()
          serviceWorker.addEventListener('push', listener)

          Foreground Detection

          Page Visibility API 對于 Web 應用程序了解當前是否顯示在前臺非常有用,特別是在不需要時停止資源密集型 UI 動畫或數據刷新。 而在移動設備上,這樣做的主要原因是減少電池的使用。

          var target = document.getElementById('target');
          
          var hidden, visibilityChange;
          if (typeof document.hidden !== "undefined") {
            hidden = "hidden";
            visibilityChange = "visibilitychange";
          } else if (typeof document.mozHidden !== "undefined") {
            hidden = "mozHidden";
            visibilityChange = "mozvisibilitychange";
          } else if (typeof document.msHidden !== "undefined") {
            hidden = "msHidden";
            visibilityChange = "msvisibilitychange";
          } else if (typeof document.webkitHidden !== "undefined") {
            hidden = "webkitHidden";
            visibilityChange = "webkitvisibilitychange";
          } else {
            target.innerText = 'Page Visibility API not supported.';
          }
          function handleVisibilityChange() {
            var timeBadge = new Date().toTimeString().split(' ')[0];
            var newState = document.createElement('p');
            newState.innerHTML = ''+ timeBadge +' Page visibility changed to '+ (document[hidden] ?'hidden':'visible') +'.';
            target.appendChild(newState);
          }
          document.addEventListener(visibilityChange, handleVisibilityChange, false);
          if (hidden in document) {
            document.getElementById('status').innerHTML = document[hidden] ? 'hidden' : 'visible';
          }

          User Idle Detection

          User Idle Detection API 允許 Web 應用程序檢測用戶不活動時的狀態,即系統中沒有生成用戶驅動的事件或屏幕被鎖定。 與之前的前臺檢測功能相反,此 API 不依賴于當前選項卡活動 ,其會檢測用戶何時離開設備但未鎖定設備或已變為非活動狀態,無論哪個選項卡處于活動狀態。

          const idleDetector = new IdleDetector(options)
          idleDetector.start()
          const state = idleDetector.state
          idleDetector.addEventListener('change', listener)

          Permissions API

          Permissions API 為 Web 應用程序提供了統一的方式來查詢可能需要用戶同意的功能(如通知或地理位置)的權限狀態。 通過 Permissions API,應用程序可以列出用戶授予的權限,而無需實際觸發該功能本身。

          if ('permissions' in navigator) {
            var logTarget = document.getElementById('logTarget');
            function handleChange(permissionName, newState) {
              var timeBadge = new Date().toTimeString().split(' ')[0];
              var newStateInfo = document.createElement('p');
              newStateInfo.innerHTML = ''+ timeBadge +' State of '+ permissionName +' permission status changed to '+ newState +'.';
              logTarget.appendChild(newStateInfo);
            }
          
            function checkPermission(permissionName, descriptor) {
              try {
              navigator.permissions.query(Object.assign({name: permissionName}, descriptor))
                .then(function (permission) {
                  document.getElementById(permissionName + '-status').innerHTML = permission.state;
                  permission.addEventListener('change', function (e) {
                    document.getElementById(permissionName + '-status').innerHTML = permission.state;
                    handleChange(permissionName, permission.state);
                  });
                });
              } catch (e) {
              }
            }
          
            checkPermission('geolocation');
            checkPermission('notifications');
            checkPermission('push', {userVisibleOnly: true});
            checkPermission('midi', {sysex: true});
            checkPermission('camera');
            checkPermission('microphone');
            checkPermission('background-sync');
            checkPermission('ambient-light-sensor');
            checkPermission('accelerometer');
            checkPermission('gyroscope');
            checkPermission('magnetometer');
          
            var noop = function () {};
            navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
          
            function requestGeolocation() {
              navigator.geolocation.getCurrentPosition(noop);
            }
          
            function requestNotifications() {
              Notification.requestPermission();
            }
          
            function requestPush() {
              navigator.serviceWorker.getRegistration()
                .then(function (serviceWorkerRegistration) {
                  serviceWorkerRegistration.pushManager.subscribe();
                });
            }
          
            function requestMidi() {
              navigator.requestMIDIAccess({sysex: true});
            }
          
            function requestCamera() {
              navigator.getUserMedia({video: true}, noop, noop)
            }
          
            function requestMicrophone() {
              navigator.getUserMedia({audio: true}, noop, noop)
            }
          }

          Task API

          第一個提案稱為定期后臺同步 API,它解決了后臺數據同步用例,補充了后臺同步功能。 其允許 Web 應用程序注冊周期性事件,從而喚醒 Service Worker,并無需用戶交互即可執行 HTTP 請求。

          截至 2020 年初,該 API 僅在 Google Chrome 80+ 中進行實驗性使用,并且其使用僅限于具有足夠高參與度的已安裝應用程序。 API 不保證同步的間隔 , 但允許通過 minInterval 參數請求最小間隔,可為了避免濫用,實際間隔取決于網絡可信度和用戶使用應用程序的頻率等諸多因素。

          function scheduleNotification() {
            if (!('Notification' in window)) {
              alert('Notification API not supported');
              return;
            }
            if (!('showTrigger' in Notification.prototype)) {
              alert('Notification Trigger API not supported');
              return;
            }
            Notification.requestPermission()
              .then(() => {
                if (Notification.permission !== 'granted') {
                  throw 'Notification permission is not granted';
                }
              })
              .then(() => navigator.serviceWorker.getRegistration())
              .then((reg) => {
                reg.showNotification("Hi there from the past!", {
                    showTrigger: new TimestampTrigger(new Date().getTime() + 10 * 1000)
                })
              })
              .catch((err) => {
                alert('Notification Trigger API error:' + err);
              });
          }

          3.PWA 與 App Lifecycle

          Home Screen Installation

          Web 應用程序可以通過提供 manifest.json 文件標準化為 Web Manifest,指定將應用程序視為目標平臺上的一等公民所需的功能和行為,即添加(“install”)到主屏幕, 具有相關圖標、全屏行為、主題、無瀏覽器欄的獨立外觀等,同時還可以作為放置與 Web 應用程序關聯的所有元數據的集中位置。

          {
            "short_name": "Example App",
            "name": "The Example Application",
            "icons": [
              {
                "src": "launcher-icon-1x.png",
                "sizes": "48x48"
              },
              {
                "src": "launcher-icon-2x.png",
                "sizes": "96x96"
              }
            ],
            "theme_color": "#ff0000",
            "background_color": "#ff0000",
            "start_url": "index.html",
            "display": "standalone"
          }

          Freeze/Resume Detection

          Page Lifecycle API 是對先前存在的頁面狀態更改事件的補充,包括:前臺檢測和焦點信息。 當非活動應用程序的選項卡(inactive application's tab )將被凍結以優化 CPU 和電池使用以及在后續激活時恢復時,其允許 Web 應用程序注冊瀏覽器生成的事件。

          該 API 還提供了 wasDiscarded 標志,可以檢測凍結選項卡已被丟棄(從內存中刪除)并在恢復時需要加載新頁面的情況。 對于這種頁面加載,該標志將設置為 true。

          截至 2020 年春季,該 API 僅在基于 Chromium 的瀏覽器中實現。

          var target = document.getElementById('target');
          
          if ('wasDiscarded' in document) {
            document.getElementById('wasDiscarded').innerText = document.wasDiscarded.toString();
          }
          function getState() {
            if (document.visibilityState === 'hidden') {
              return 'hidden';
            }
            if (document.hasFocus()) {
              return 'focused';
            }
            return 'not focused';
          };
          
          var state = getState();
          function logStateChange(nextState) {
            var prevState = state;
            if (nextState !== prevState) {
              var timeBadge = new Date().toTimeString().split(' ')[0];
              var newLog = document.createElement('p');
              newLog.innerHTML = ''+ timeBadge +' State changed from '+ prevState +' to '+ nextState +'.';
              target.appendChild(newLog);
              state = nextState;
            }
          };
          
          function onPageStateChange() {
            logStateChange(getState())
          }
          
          ['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach(function (type) {
            window.addEventListener(type, onPageStateChange, {capture: true});
          });
          
          function onFreeze() {
            logStateChange('frozen');
          }
          
          window.addEventListener('freeze', onFreeze, {capture: true});
          
          function onPageHide(event) {
            if (event.persisted) {
              // If the event's persisted property is `true` the page is about
              // to enter the page navigation cache, which is also in the frozen state.
              logStateChange('frozen');
            } else {
              // If the event's persisted property is not `true` the page is about to be unloaded.
              logStateChange('terminated');
            }
          }
          
          window.addEventListener('pagehide', onPageHide, {capture: true});

          4.PWA 與 Surroundings

          Bluetooth

          Web Bluetooth API 是一個底層 API,允許 Web 應用程序與附近支持低功耗藍牙的外圍設備配對并訪問其公開的服務。

          function readBatteryLevel() {
            var $target = document.getElementById('target');
            if (!('bluetooth' in navigator)) {
              $target.innerText = 'Bluetooth API not supported.';
              return;
            }
            navigator.bluetooth.requestDevice({
                filters: [{
                  services: ['battery_service']
                }]
              })
              .then(function (device) {
                return device.gatt.connect();
              })
              .then(function (server) {
                return server.getPrimaryService('battery_service');
              })
              .then(function (service) {
                return service.getCharacteristic('battery_level');
              })
              .then(function (characteristic) {
                return characteristic.readValue();
              })
              .then(function (value) {
                $target.innerHTML = 'Battery percentage is' + value.getUint8(0) + '.';
              })
              .catch(function (error) {
                $target.innerText = error;
              });
          }

          USB

          WebUSB API 允許 Web 應用程序與系統中可用的通用串行總線兼容設備(Universal Serial Bus-compatible devices )進行交互。 為了授權應用程序訪問設備,用戶需要在瀏覽器的 UI 中確認意圖,而該意圖只能通過手勢啟動(例如,單擊按鈕,但不能通過任意 JavaScript 自動啟動)。

          document.getElementById('arduinoButton').addEventListener('click', function () {
            if (navigator.usb) {
              talkToArduino();
            } else {
              alert('WebUSB not supported.');
            }
          });
          
          async function talkToArduino() {
            try {
              let device = await navigator.usb.requestDevice({filters: [{ vendorId: 0x2341}] });
              await device.open();
              await device.selectConfiguration(1);
              await device.claimInterface(2);
              await device.controlTransferOut({
                requestType: 'class',
                recipient: 'interface',
                request: 0x22,
                value: 0x01,
                index: 0x02
              });
          
              // Ready to receive data
              let result = device.transferIn(5, 64); // Waiting for 64 bytes of data from endpoint #5.
              let decoder = new TextDecoder();
              document.getElementById('target').innerHTML = 'Received:' + decoder.decode(result.data);
            } catch (error) {
              document.getElementById('target').innerHTML = error;
            }
          }

          Web Serial API

          Web Serial API 允許 Web 應用程序與通過串行端口(Serial Port)連接到系統的設備進行交互。 為了授權應用程序訪問設備,用戶需要在瀏覽器的 UI 中確認意圖,而該意圖只能通過手勢啟動(例如,單擊按鈕,但不能通過任意 JavaScript 自動啟動)。 API 通過一對流公開連接 , 一個用于讀取,一個用于寫入 Serial Port。

          document.getElementById('connectButton').addEventListener('click', () => {
            if (navigator.serial) {
              connectSerial();
            } else {
              alert('Web Serial API not supported.');
            }
          });
          async function connectSerial() {
            const log = document.getElementById('target');
            try {
              const port = await navigator.serial.requestPort();
              await port.open({baudRate: 9600});
          
              const decoder = new TextDecoderStream();
          
              port.readable.pipeTo(decoder.writable);
          
              const inputStream = decoder.readable;
              const reader = inputStream.getReader();
          
              while (true) {
                const {value, done} = await reader.read();
                if (value) {
                  log.textContent += value + '\n';
                }
                if (done) {
                  console.log('[readLoop] DONE', done);
                  reader.releaseLock();
                  break;
                }
              }
          
            } catch (error) {
              log.innerHTML = error;
            }
          }

          5.PWA 與 Camera & Microphone

          Audio & Video Capture

          Audio & Video Capture API 允許授權的 Web 應用程序訪問來自設備的音頻和視頻捕獲接口的流,包括用來自攝像頭和麥克風的可用數據。 API 公開的流可以直接綁定到 HTML <audio> 或 <video> 元素,或者在代碼中讀取和操作,包括通過 Image Capture API, Media Recorder API 或者 Real-Time Communication。

          function getUserMedia(constraints) {
            if (navigator.mediaDevices) {
              return navigator.mediaDevices.getUserMedia(constraints);
            }
            var legacyApi = navigator.getUserMedia || navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia || navigator.msGetUserMedia;
            if (legacyApi) {
              return new Promise(function (resolve, reject) {
                legacyApi.bind(navigator)(constraints, resolve, reject);
              });
            }
          }
          
          function getStream (type) {
            if (!navigator.mediaDevices && !navigator.getUserMedia && !navigator.webkitGetUserMedia &&
              !navigator.mozGetUserMedia && !navigator.msGetUserMedia) {
              alert('User Media API not supported.');
              return;
            }
            var constraints = {};
            constraints[type] = true;
            getUserMedia(constraints)
              .then(function (stream) {
                var mediaControl = document.querySelector(type);
                if ('srcObject' in mediaControl) {
                  mediaControl.srcObject = stream;
                } else if (navigator.mozGetUserMedia) {
                  mediaControl.mozSrcObject = stream;
                } else {
                  mediaControl.src = (window.URL || window.webkitURL).createObjectURL(stream);
                }
          
                mediaControl.play();
              })
              .catch(function (err) {
                alert('Error:' + err);
              });
          }

          Advanced Camera Controls

          Image Capture API 允許 Web 應用程序控制設備相機的高級設置,例如: 變焦、白平衡、ISO 或焦點,并根據這些設置拍照,其依賴于可能從流中獲取的 streamVideoTrack 對象 。

          function getUserMedia(options, successCallback, failureCallback) {
            var api = navigator.getUserMedia || navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia || navigator.msGetUserMedia;
            if (api) {
              return api.bind(navigator)(options, successCallback, failureCallback);
            }
          }
          
          var theStream;
          
          function getStream() {
            if (!navigator.getUserMedia && !navigator.webkitGetUserMedia &&
              !navigator.mozGetUserMedia && !navigator.msGetUserMedia) {
              alert('User Media API not supported.');
              return;
            }
          
            var constraints = {
              video: true
            };
          
            getUserMedia(constraints, function (stream) {
              var mediaControl = document.querySelector('video');
              if ('srcObject' in mediaControl) {
                mediaControl.srcObject = stream;
              } else if (navigator.mozGetUserMedia) {
                mediaControl.mozSrcObject = stream;
              } else {
                mediaControl.src = (window.URL || window.webkitURL).createObjectURL(stream);
              }
              theStream = stream;
            }, function (err) {
              alert('Error:' + err);
            });
          }
          
          function takePhoto() {
            if (!('ImageCapture' in window)) {
              alert('ImageCapture is not available');
              return;
            }
          
            if (!theStream) {
              alert('Grab the video stream first!');
              return;
            }
          
            var theImageCapturer = new ImageCapture(theStream.getVideoTracks()[0]);
          
            theImageCapturer.takePhoto()
              .then(blob => {
                var theImageTag = document.getElementById("imageTag");
                theImageTag.src = URL.createObjectURL(blob);
              })
              .catch(err => alert('Error:' + err));
          }

          Recording Media

          Media Recorder API 是一個 Web API,允許 Web 應用程序錄制本地或遠程音頻和視頻媒體流,它依賴于 mediaStream 對象 。

          recorder = new MediaRecorder(mediaStream, options)
          MediaRecorder.isMimeTypeSupported(mimeType)
          recorder.start(interval)

          Real-Time Communication

          Web 中的實時通信(簡稱 WebRTC)是一組 API,允許 Web 應用程序向遠程對等方發送和接收流式實時視頻、音頻和數據,而無需通過集中式服務器進行依賴。 不過,初始發現和連接握手需要實現特定信令協議之一的服務器。 API 依賴于 mediaStream 對象 。

          function getUserMedia(options, successCallback, failureCallback) {
            var api = navigator.getUserMedia || navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia || navigator.msGetUserMedia;
            if (api) {
              return api.bind(navigator)(options, successCallback, failureCallback);
            }
          }
          var pc1;
          var pc2;
          var theStreamB;
          
          function getStream() {
            if (!navigator.getUserMedia && !navigator.webkitGetUserMedia &&
              !navigator.mozGetUserMedia && !navigator.msGetUserMedia) {
              alert('User Media API not supported.');
              return;
            }
            var constraints = {
              video: true
            };
            getUserMedia(constraints, function (stream) {
              addStreamToVideoTag(stream, 'localVideo');
              // RTCPeerConnection is prefixed in Blink-based browsers.
              window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection;
              pc1 = new RTCPeerConnection(null);
              pc1.addStream(stream);
              pc1.onicecandidate = event => {
                if (event.candidate == null) return;
                pc2.addIceCandidate(new RTCIceCandidate(event.candidate));
              };
              pc2 = new RTCPeerConnection(null);
              pc2.onaddstream = event => {
                theStreamB = event.stream;
                addStreamToVideoTag(event.stream, 'remoteVideo');
              };
              pc2.onicecandidate = event => {
                if (event.candidate == null) return;
                pc1.addIceCandidate(new RTCIceCandidate(event.candidate));
              };
          
              pc1.createOffer({offerToReceiveVideo: 1})
                .then(desc => {
                  pc1.setLocalDescription(desc);
                  pc2.setRemoteDescription(desc);
                  return pc2.createAnswer({offerToReceiveVideo: 1});
                })
                .then(desc => {
                  pc1.setRemoteDescription(desc);
                  pc2.setLocalDescription(desc);
                })
                .catch(err => {
                  console.error('createOffer()/createAnswer() failed' + err);
                });
            }, function (err) {
              alert('Error:' + err);
            });
          }
          
          function addStreamToVideoTag(stream, tag) {
            var mediaControl = document.getElementById(tag);
            if ('srcObject' in mediaControl) {
              mediaControl.srcObject = stream;
            } else if (navigator.mozGetUserMedia) {
              mediaControl.mozSrcObject = stream;
            } else {
              mediaControl.src = (window.URL || window.webkitURL).createObjectURL(stream);
            }
          }

          Shape Detection API

          Shape Detection API 是一組向 Web 應用程序公開底層系統的圖像處理(如 OCR(文本檢測)、條形碼 / QR 掃描或人臉檢測功能)的服務。 檢測的可用性和質量因操作系統和硬件而異,API 按原樣公開這些服務。

          function writeLog(message) {
            const newState = document.createElement('p');
            newState.innerHTML = message;
            document.getElementById('target').appendChild(newState);
          }
          
          function detectText() {
            if (!('TextDetector' in window)) {
              alert('TextDetector is not available');
              return;
            }
            const file = document.getElementById('file').files[0]
            if (!file) {
              alert('No image - upload a file first.');
              return;
            }
          
            document.getElementById('target').innerHTML = '';
            const detector = new TextDetector();
            createImageBitmap(file)
              .then((image) => detector.detect(image))
              .then((results) => {
                if (results.length) {
                  results.forEach((result) => {
                    writeLog(`Detected text "${result.rawValue}" at (${Math.round(result.boundingBox.x)},${Math.round(result.boundingBox.y)})`);
                  })
                } else {
                  writeLog('No texts detected.');
                }
              })
              .catch((err) => writeLog('Text detection error:' + err));
          }
          
          function detectBarcode() {
            if (!('BarcodeDetector' in window)) {
              alert('BarcodeDetector is not available');
              return;
            }
            const file = document.getElementById('file').files[0]
            if (!file) {
              alert('No image - upload a file first.');
              return;
            }
          
            document.getElementById('target').innerHTML = '';
            const detector = new BarcodeDetector();
          
            createImageBitmap(file)
              .then((image) => detector.detect(image))
              .then((results) => {
                if (results.length) {
                  results.forEach((result) => {
                    writeLog(`Detected text "${result.rawValue}" at (${Math.round(result.boundingBox.x)},${Math.round(result.boundingBox.y)})`);
                  })
                } else {
                  writeLog('No barcodes detected.');
                }
              })
              .catch((err) => writeLog('Barcode detection error:' + err));
          }
          
          function detectFace() {
            if (!('FaceDetector' in window)) {
              alert('FaceDetector is not available');
              return;
            }
          
            const file = document.getElementById('file').files[0]
            if (!file) {
              alert('No image - upload a file first.');
              return;
            }
          
            document.getElementById('target').innerHTML = '';
            const detector = new FaceDetector();
          
            createImageBitmap(file)
              .then((image) => detector.detect(image))
              .then((results) => {
                if (results.length) {
                  results.forEach((result) => {
                    writeLog(`Detected face with ${result.landmarks.map((l) => l.type).join()} at (${Math.round(result.boundingBox.x)},${Math.round(result.boundingBox.y)})`);
                  })
                } else {
                  writeLog('No faces detected.');
                }
              })
              .catch((err) => writeLog('Face detection error:' + err));
          }

          6.PWA 與 Device Features

          Network Type & Speed

          Network Information API 允許 Web 應用程序讀取當前網絡類型以及基于客戶端使用的底層連接技術假定的最大下行鏈路速度,同時還允許在網絡類型發生更改時訂閱通知。

          function getConnection() {
            return navigator.connection || navigator.mozConnection ||
              navigator.webkitConnection || navigator.msConnection;
          }
          function updateNetworkInfo(info) {
            document.getElementById('networkType').innerHTML = info.type;
            document.getElementById('effectiveNetworkType').innerHTML = info.effectiveType;
            document.getElementById('downlinkMax').innerHTML = info.downlinkMax;
          }
          var info = getConnection();
          if (info) {
            info.onchange = function (event) {
              updateNetworkInfo(event.target);
            }
            updateNetworkInfo(info);
          }

          Online State

          瀏覽器向 Web 應用程序公開網絡連接可用性信息,以便應用程序可以做出正確反應,即在檢測到離線情況時停止所有利用網絡的操作并切換到緩存數據。

          navigator.onLine
          window.addEventListener('online', listener)
          window.addEventListener('offline', listener)

          Vibration

          Vibration API 允許 Web 應用程序使用設備的內置振動(如果存在)。

          function vibrateSimple() {
            navigator.vibrate(200);
          }
          function vibratePattern() {
            navigator.vibrate([100, 200, 200, 200, 500]);
          }

          Battery Status API

          Battery Status API 允許 Web 應用程序獲取有關設備電源、電池電量、預期充電或放電時間的信息。 每當任何可用信息發生變化時,它還會公開事件。 API 允許應用程序根據功率級別打開或者關閉其低能效操作。

          if ('getBattery' in navigator || ('battery' in navigator && 'Promise' in window)) {
            var target = document.getElementById('target');
            function handleChange(change) {
              var timeBadge = new Date().toTimeString().split(' ')[0];
              var newState = document.createElement('p');
              newState.innerHTML = ''+ timeBadge +' '+ change +'.';
              target.appendChild(newState);
            }
          
            function onChargingChange() {
              handleChange('Battery charging changed to' + (this.charging ? 'charging' : 'discharging') + '')
            }
            function onChargingTimeChange() {
              handleChange('Battery charging time changed to' + this.chargingTime + 's');
            }
            function onDischargingTimeChange() {
              handleChange('Battery discharging time changed to' + this.dischargingTime + 's');
            }
            function onLevelChange() {
              handleChange('Battery level changed to' + this.level + '');
            }
          
            var batteryPromise;
          
            if ('getBattery' in navigator) {
              batteryPromise = navigator.getBattery();
            } else {
              batteryPromise = Promise.resolve(navigator.battery);
            }
            batteryPromise.then(function (battery) {
              document.getElementById('charging').innerHTML = battery.charging ? 'charging' : 'discharging';
              document.getElementById('chargingTime').innerHTML = battery.chargingTime + 's';
              document.getElementById('dischargingTime').innerHTML = battery.dischargingTime + 's';
              document.getElementById('level').innerHTML = battery.level;
          
              battery.addEventListener('chargingchange', onChargingChange);
              battery.addEventListener('chargingtimechange', onChargingTimeChange);
              battery.addEventListener('dischargingtimechange', onDischargingTimeChange);
              battery.addEventListener('levelchange', onLevelChange);
            });
          }

          Device Memory

          Device Memory API 允許 Web 應用程序根據安裝的 RAM 內存的大小來評估設備的類別。 出于性能原因,它可用于識別低端設備以提供精簡、輕量級的網站體驗。 API 提供的值并不暗示有多少內存實際可供應用程序使用 ,其目的僅用作設備類別指示。

          document.getElementById('result').innerHTML = navigator.deviceMemory || 'unknown'

          7.PWA 與 Operating System(操作系統)

          Offline Storage

          Web 應用程序的離線存儲功能的原型和標準化技術進行了多次迭代。 第一次嘗試要么只是一些簡單的解決方法(例如:將數據存儲在 cookie 中),要么需要額外的軟件(例如: Flash 或 Google Gears)。 后來,Web SQL 的想法(基本上是在瀏覽器中原生包含 SQLite)被創造并在某些瀏覽器中實現,但后來由于標準化困難而被棄用。

          目前至少有三種不同且獨立的技術已標準化并可用。 最簡單的是 Web Storage ,一種鍵值字符串存儲,允許 Web 應用程序持久地跨窗口存儲數據 (localStorage) 或在單個瀏覽器選項卡中存儲單個會話的數據 (sessionStorage)。 更復雜的 IndexedDB 是一個基于類似數據庫結構的底層 API,其中事務和游標通過索引進行迭代。 而 最新的 Cache API 是一個專門的解決方案,用于保存請求 、 響應對,主要在 Service Worker 實現中有用。

          任何持久性存儲(無論是 localStorage、IndexedDB 還是 Cache API)中存儲的數據的實際持久性都是由瀏覽器管理的,默認在內存壓力情況下,可能會在未經最終用戶同意的情況下被刪除。 為了解決這個問題,引入了 Storage API , 它為 Web 應用程序提供了一種在用戶允許的情況下以完全可靠的方式存儲數據的方法。

          if ('localStorage' in window || 'sessionStorage' in window) {
            var selectedEngine;
          
            var logTarget = document.getElementById('target');
            var valueInput = document.getElementById('value');
          
            var reloadInputValue = function () {
            console.log(selectedEngine, window[selectedEngine].getItem('myKey'))
              valueInput.value = window[selectedEngine].getItem('myKey') || '';
            }
          
            var selectEngine = function (engine) {
              selectedEngine = engine;
              reloadInputValue();
            };
          
            function handleChange(change) {
              var timeBadge = new Date().toTimeString().split(' ')[0];
              var newState = document.createElement('p');
              newState.innerHTML = ''+ timeBadge +' '+ change +'.';
              logTarget.appendChild(newState);
            }
          
            var radios = document.querySelectorAll('#selectEngine input');
            for (var i = 0; i < radios.length; ++i) {
              radios[i].addEventListener('change', function () {
                selectEngine(this.value)
              });
            }
            selectEngine('localStorage');
            valueInput.addEventListener('keyup', function () {
              window[selectedEngine].setItem('myKey', this.value);
            });
          
            var onStorageChanged = function (change) {
              var engine = change.storageArea === window.localStorage ? 'localStorage' : 'sessionStorage';
              handleChange('External change in' + engine + ': key' + change.key + 'changed from' + change.oldValue + 'to' + change.newValue + '');
              if (engine === selectedEngine) {
                reloadInputValue();
              }
            }
          
            window.addEventListener('storage', onStorageChanged);
          }

          File Access

          File Access API 使 Web 應用程序能夠訪問有關用戶決定與應用程序共享的文件的文件系統級只讀信息,即大小、MIME 類型、修改日期、內容,而無需將文件發送到服務器。

          function getReadFile(reader, i) {
            return function () {
              var li = document.querySelector('[data-idx="' + i + '"]');
          
              li.innerHTML += 'File starts with"' + reader.result.substr(0, 25) + '"';
            }
          }
          
          function readFiles(files) {
            document.getElementById('count').innerHTML = files.length;
          
            var target = document.getElementById('target');
            target.innerHTML = '';
          
            for (var i = 0; i < files.length; ++i) {
              var item = document.createElement('li');
              item.setAttribute('data-idx', i);
              var file = files[i];
          
              var reader = new FileReader();
              reader.addEventListener('load', getReadFile(reader, i));
              reader.readAsText(file);
          
              item.innerHTML = ''+ file.name +', '+ file.type +', '+ file.size +' bytes, last modified '+ file.lastModifiedDate +'';
              target.appendChild(item);
            };
          }
          async function writeFile() {
            if (!window.chooseFileSystemEntries) {
              alert('Native File System API not supported');
              return;
            }
            const target = document.getElementById('target');
            target.innerHTML = 'Opening file handle...';
          
            const handle = await window.chooseFileSystemEntries({
              type: 'save-file',
            });
          
            const file = await handle.getFile()
            const writer = await handle.createWriter();
            await writer.write(0, 'Hello world from What Web Can Do!');
            await writer.close()
          
            target.innerHTML = 'Test content written to' + file.name + '.';
          }
          HTML
          <div class="columns">
            <div class="column">
              <button class="btn-file">
                Choose some files to read<br>(File API) <input type="file" onchange="readFiles(this.files)" multiple>
              </button>
          
              <p>Number of selected files: <b id="count">N/A</b></p>
            </div>
            <div class="column">
              <button class="btn-file" onclick="writeFile()">
                Choose file to create or overwrite<br>(Native File System API)
              </button>
            </div>
          </div>
          
          <ul id="target"></ul>
          CSS
          .btn-file {
              position: relative;
              overflow: hidden;
              margin: 10px;
          }
          .btn-file input[type=file] {
              position: absolute;
              top: 0;
              right: 0;
              min-width: 100%;
              min-height: 100%;
              opacity: 0;
              outline: none;
              background: #fff;
              cursor: inherit;
              display: block;
          }

          Storage Quotas

          Storage Quotas 用于通過 Google Chrome 進行的,以允許 Web 應用程序查詢系統當前使用的和可供應用程序使用的存儲空間的大小。

          最新的 Quota Estimation API 還包括一種請求瀏覽器保留所存儲數據的方法,否則這些數據將在系統發出內存壓力信號時被清除, 請求此持久存儲功能的權限可能由瀏覽器基于啟發式授予(即 Google Chrome),或者可能需要明確的用戶同意(即 Firefox)。

          舊的實現僅在帶有 webkit- 前綴的 Chrome 中受支持,用于保持臨時存儲和持久存儲之間的分離,并允許 Web 應用程序在需要時請求更多存儲空間。

          if ('storage' in navigator && 'estimate' in navigator.storage) {
            navigator.storage.estimate()
              .then(estimate => {
                document.getElementById('usage').innerHTML = estimate.usage;
                document.getElementById('quota').innerHTML = estimate.quota;
                document.getElementById('percent').innerHTML = (estimate.usage * 100 / estimate.quota).toFixed(0);
              });
          }
          
          if ('storage' in navigator && 'persisted' in navigator.storage) {
            navigator.storage.persisted()
              .then(persisted => {
                document.getElementById('persisted').innerHTML = persisted ? 'persisted' : 'not persisted';
              });
          }
          
          function requestPersistence() {
            if ('storage' in navigator && 'persist' in navigator.storage) {
              navigator.storage.persist()
                .then(persisted => {
                  document.getElementById('persisted').innerHTML = persisted ? 'persisted' : 'not persisted';
                });
            }
          }

          8.PWA 與用戶 Input 輸入

          Touch Gestures

          傳統意義上,Web 依賴鼠標和鍵盤作為唯一的輸入設備,而移動設備主要通過觸摸控制。 移動 Web 從一個有點棘手的問題開始,即將觸摸事件轉換為鼠標事件(例如 mousedown)。

          較新的 HTML5 方法是將 touch 作為一流的輸入方式,允許 Web 應用程序攔截和識別復雜的多點觸摸手勢、徒手繪圖等。不幸的是,目前要么通過觸摸事件,例如 touchstart,這是供應商走的路線,或者通過由微軟發起的更新、更通用的指針事件規范時,蘋果公司后來將其標準化為事實上的解決方案。

          function startDrag(e) {
            this.ontouchmove = this.onmspointermove = moveDrag;
          
            this.ontouchend = this.onmspointerup = function () {
              this.ontouchmove = this.onmspointermove = null;
              this.ontouchend = this.onmspointerup = null;
            }
          
            var pos = [this.offsetLeft, this.offsetTop];
            var that = this;
            var origin = getCoors(e);
          
            function moveDrag(e) {
              var currentPos = getCoors(e);
              var deltaX = currentPos[0] - origin[0];
              var deltaY = currentPos[1] - origin[1];
              this.style.left = (pos[0] + deltaX) + 'px';
              this.style.top = (pos[1] + deltaY) + 'px';
              return false; // cancels scrolling
            }
          
            function getCoors(e) {
              var coors = [];
              if (e.targetTouches && e.targetTouches.length) {
                var thisTouch = e.targetTouches[0];
                coors[0] = thisTouch.clientX;
                coors[1] = thisTouch.clientY;
              } else {
                coors[0] = e.clientX;
                coors[1] = e.clientY;
              }
              return coors;
            }
          }
          
          var elements = document.querySelectorAll('.test-element');
          [].forEach.call(elements, function (element) {
            element.ontouchstart = element.onmspointerdown = startDrag;
          });
          
          document.ongesturechange = function () {
            return false;
          }

          Speech Recognition

          Web Speech API 的語音識別部分允許授權的 Web 應用程序訪問設備的麥克風并生成所錄制語音的文字記錄,從而使得 Web 應用程序可以使用語音作為輸入和控制方法之一,類似于觸摸或鍵盤。

          從技術上講,語音識別功能也可以通過訪問麥克風并使用 Web Audio API 處理音頻流來實現,采用這種方法的典型示例庫是 pocketsphinx.js。

          let recognition = new SpeechRecognition()

          Clipboard (Copy & Paste)

          Clipboard API 為 Web 應用程序提供了一種對用戶執行的剪切、復制和粘貼操作做出反應以及代表用戶直接讀取或寫入系統剪貼板的方法。

          有兩種類型的剪貼板 API 可用 ,比如:較舊的同步式和較新的異步式。 較新的 API 僅限于 HTTPS,并且需要明確的用戶權限才能進行粘貼操作 ,但截至 2020 年初在 Safari 中依然不可用。 舊的 API 沒有正確解決隱私問題,因此粘貼功能在大多數瀏覽器中不再起作用。

          var logTarget = document.getElementById('logTarget');
          
          function useAsyncApi() {
            return document.querySelector('input[value=async]').checked;
          }
          
          function log(event) {
            var timeBadge = new Date().toTimeString().split(' ')[0];
            var newInfo = document.createElement('p');
            newInfo.innerHTML = ''+ timeBadge +' '+ event +'.';
            logTarget.appendChild(newInfo);
          }
          
          function performCopyEmail() {
            var selection = window.getSelection();
            var emailLink = document.querySelector('.js-emaillink');
          
            if (useAsyncApi()) {
              // 剪切板
              navigator.clipboard.writeText(emailLink.textContent)
                .then(() => log('Async writeText successful,"' + emailLink.textContent + '"written'))
                .catch(err => log('Async writeText failed with error:"' + err + '"'));
            } else {
              selection.removeAllRanges();
              var range = document.createRange();
              range.selectNode(emailLink);
              selection.addRange(range);
          
              try {
                var successful = document.execCommand('copy');
                var msg = successful ? 'successful' : 'unsuccessful';
                log('Copy email command was' + msg);
              } catch (err) {
                log('execCommand Error', err);
              }
          
              selection.removeAllRanges();
            }
          }
          
          function performCutTextarea() {
            var cutTextarea = document.querySelector('.js-cuttextarea');
          
            if (useAsyncApi()) {
              navigator.clipboard.writeText(cutTextarea.textContent)
                .then(() => {
                  log('Async writeText successful,"' + cutTextarea.textContent + '"written');
                  cutTextarea.textContent = '';
                })
                .catch(err => log('Async writeText failed with error:"' + err + '"'));
            } else {
              var hasSelection = document.queryCommandEnabled('cut');
              cutTextarea.select();
          
              try {
                var successful = document.execCommand('cut');
                var msg = successful ? 'successful' : 'unsuccessful';
                log('Cutting text command was' + msg);
              } catch (err) {
                log('execCommand Error', err);
              }
            }
          }
          
          function performPaste() {
            var pasteTextarea = document.querySelector('.js-cuttextarea');
          
            if (useAsyncApi()) {
              navigator.clipboard.readText()
                .then((text) => {
                  pasteTextarea.textContent = text;
                  log('Async readText successful,"' + text + '"written');
                })
                .catch((err) => log('Async readText failed with error:"' + err + '"'));
            } else {
              pasteTextarea.focus();
              try {
                var successful = document.execCommand('paste');
                var msg = successful ? 'successful' : 'unsuccessful';
                log('Pasting text command was' + msg);
              } catch (err) {
                log('execCommand Error', err);
              }
            }
          }
          
          // Get the buttons
          var cutTextareaBtn = document.querySelector('.js-textareacutbtn');
          var copyEmailBtn = document.querySelector('.js-emailcopybtn');
          var pasteTextareaBtn = document.querySelector('.js-textareapastebtn');
          
          // Add click event listeners
          copyEmailBtn.addEventListener('click', performCopyEmail);
          cutTextareaBtn.addEventListener('click', performCutTextarea);
          pasteTextareaBtn.addEventListener('click', performPaste);
          
          function logUserOperation(event) {
            log('User performed' + event.type + 'operation. Payload is:' + event.clipboardData.getData('text/plain') + '');
          }
          
          document.addEventListener('cut', logUserOperation);
          document.addEventListener('copy', logUserOperation);

          Pointing Device Adaptation

          CSS4 規范的交互媒體部分定義了媒體查詢,允許 Web 應用程序根據用戶與應用程序交互的方式更改其布局和用戶界面。 其允許識別瀏覽器的主指針(即鼠標、觸摸、鍵盤),并決定它是細還是粗,以及是否可以使用 “經典” 界面(如平板電腦上的觸摸)將鼠標懸停在元素上,以便界面可以縮小或放大,并啟用懸停交互或相應地用替代方案替換。

          @media (hover: hover) {
            #tooltip {
              display: none;
            }
            #button:hover ~ #tooltip {
              display: block;
            }
          }
          
          @media (pointer: fine) {
            #button {
              font-size: x-small;
            }
          }
          @media (pointer: coarse) {
            #button {
              font-size: x-large;
            }
          }

          Eye Dropper

          EyeDropper API 允許用戶使用吸管工具從屏幕上捕獲樣本顏色。

          與基于 Chromium 的桌面瀏覽器上的 <input type="color"> 不同,此 API 提供了一個簡單的界面,可以使用標準 API 選擇整個設備屏幕的顏色。

          // Create an EyeDropper object
          let eyeDropper = new EyeDropper();
          
          // Enter eyedropper mode
          let icon = document.getElementById("eyeDropperIcon")
          let color = document.getElementById("colorCode")
          // You may use the dropper only on the cat!
          icon.addEventListener('click', e => {
              eyeDropper.open()
              .then(colorSelectionResult => {
                  // returns hex color value (#RRGGBB) of the selected pixel
                  color.innerText = colorSelectionResult.sRGBHex;
              })
              .catch(error => {
                  // handle the user choosing to exit eyedropper mode without a selection
              });
          });

          下面是 HTML 內容:

          <div class="column">
            <p>Click on the image below to activate the dropper</p>
            <img id="eyeDropperIcon" src="/images/cat.jpg"/>
            <p>The hex color of the selected pixel is <b><span id="colorCode">???</span></b></p>
          </div>

          9.PWA 與 Location & Position

          Geolocation

          Geolocation API 允許授權的 Web 應用程序訪問設備提供的位置數據,其本身是使用 GPS 或從網絡環境獲得。 除了一次性位置查詢之外,還為應用程序提供了一種通知位置更改的方式。

          var target = document.getElementById('target');
          var watchId;
          
          function appendLocation(location, verb) {
            verb = verb || 'updated';
            var newLocation = document.createElement('p');
            newLocation.innerHTML = 'Location' + verb + ':' + location.coords.latitude + ',' + location.coords.longitude + '';
            target.appendChild(newLocation);
          }
          
          if ('geolocation' in navigator) {
            document.getElementById('askButton').addEventListener('click', function () {
              // 獲取當前位置
              navigator.geolocation.getCurrentPosition(function (location) {
                appendLocation(location, 'fetched');
              });
              // 更新位置
              watchId = navigator.geolocation.watchPosition(appendLocation);
            });
          } else {
            target.innerText = 'Geolocation API not supported.';
          }

          Device Position

          第一代設備位置支持是 Device Orientation API 的一部分,其允許 Web 應用程序訪問陀螺儀和指南針數據,以確定用戶設備在所有三個維度上的靜態方向。

          基于 Generic Sensor API 的新規范也存在方向傳感器 API(絕對和相對變體)。 與之前的規范相反,它提供了以四元數表示的讀數,這使得它直接與 WebGL 等繪圖環境兼容。

          if ('DeviceOrientationEvent' in window) {
            window.addEventListener('deviceorientation', deviceOrientationHandler, false);
          } else {
            document.getElementById('logoContainer').innerText = 'Device Orientation API not supported.';
          }
          
          function deviceOrientationHandler (eventData) {
            var tiltLR = eventData.gamma;
            var tiltFB = eventData.beta;
            var dir = eventData.alpha;
          
            document.getElementById("doTiltLR").innerHTML = Math.round(tiltLR);
            document.getElementById("doTiltFB").innerHTML = Math.round(tiltFB);
            document.getElementById("doDirection").innerHTML = Math.round(dir);
          
            var logo = document.getElementById("imgLogo");
            logo.style.webkitTransform = "rotate(" + tiltLR + "deg) rotate3d(1,0,0," + (tiltFB * -1) + "deg)";
            logo.style.MozTransform = "rotate(" + tiltLR + "deg)";
            logo.style.transform = "rotate(" + tiltLR + "deg) rotate3d(1,0,0," + (tiltFB * -1) + "deg)";
          }

          Device Motion

          第一代設備運動支持是 Device Orientation API 的一部分,其允許 Web 應用程序訪問以加速度(以 m/s2 為單位)表示的加速度計數據和以事件形式提供的三個維度中每個維度的以旋轉角度變化(以 °/s 為單位)表示的陀螺儀數據。

          自 2018 年中期以來,針對每種傳感器類型推出了基于通用傳感器 API 的更新的單獨規范。 這些 API 可直接訪問物理設備(加速計 API、陀螺儀 API 和磁力計 API)的讀數以及通過組合物理傳感器(線性加速傳感器 API 和重力傳感器 API)的讀數組成的高級融合傳感器。

          if ('LinearAccelerationSensor' in window && 'Gyroscope' in window) {
            document.getElementById('moApi').innerHTML = 'Generic Sensor API';
          
            let lastReadingTimestamp;
            let accelerometer = new LinearAccelerationSensor();
            accelerometer.addEventListener('reading', e => {
              if (lastReadingTimestamp) {
                intervalHandler(Math.round(accelerometer.timestamp - lastReadingTimestamp));
              }
              lastReadingTimestamp = accelerometer.timestamp
              accelerationHandler(accelerometer, 'moAccel');
            });
            accelerometer.start();
          
            if ('GravitySensor' in window) {
              let gravity = new GravitySensor();
              gravity.addEventListener('reading', e => accelerationHandler(gravity, 'moAccelGrav'));
              gravity.start();
            }
          
            let gyroscope = new Gyroscope();
            gyroscope.addEventListener('reading', e => rotationHandler({
              alpha: gyroscope.x,
              beta: gyroscope.y,
              gamma: gyroscope.z
            }));
            gyroscope.start();
          
          } else if ('DeviceMotionEvent' in window) {
            document.getElementById('moApi').innerHTML = 'Device Motion API';
          
            var onDeviceMotion = function (eventData) {
              accelerationHandler(eventData.acceleration, 'moAccel');
              accelerationHandler(eventData.accelerationIncludingGravity, 'moAccelGrav');
              rotationHandler(eventData.rotationRate);
              intervalHandler(eventData.interval);
            }
          
            window.addEventListener('devicemotion', onDeviceMotion, false);
          } else {
            document.getElementById('moApi').innerHTML = 'No Accelerometer & Gyroscope API available';
          }
          
          function accelerationHandler(acceleration, targetId) {
            var info, xyz = "[X, Y, Z]";
          
            info = xyz.replace("X", acceleration.x && acceleration.x.toFixed(3));
            info = info.replace("Y", acceleration.y && acceleration.y.toFixed(3));
            info = info.replace("Z", acceleration.z && acceleration.z.toFixed(3));
            document.getElementById(targetId).innerHTML = info;
          }
          
          function rotationHandler(rotation) {
            var info, xyz = "[X, Y, Z]";
          
            info = xyz.replace("X", rotation.alpha && rotation.alpha.toFixed(3));
            info = info.replace("Y", rotation.beta && rotation.beta.toFixed(3));
            info = info.replace("Z", rotation.gamma && rotation.gamma.toFixed(3));
            document.getElementById("moRotation").innerHTML = info;
          }
          
          function intervalHandler(interval) {
            document.getElementById("moInterval").innerHTML = interval;
          }

          10.Screen & Output

          Virtual & Augmented Reality

          截至 2020 年初,對 Web 應用程序的虛擬和增強現實的支持有限且不一致,有兩個可用的 API, 較舊的 WebVR API 可在某些瀏覽器中用于某些特定的 VR 環境,而較新的 WebXR 設備 API 試圖以更通用的方式處理該主題,包括 AR 或混合現實設備,從 2019 年底開始將部署在基于 Chromium 的瀏覽器中。

          兩個 API 共享相同的基本概念,范圍是允許授權的 Web 應用程序發現可用的 VR/AR 設備,與設備建立會話,讀取準備正確渲染所需的特定于設備的幾何數據,并將 <canvas> 元素作為可視層綁定到設備上 。

          通過這種方式,渲染細節由現有的畫布接口(如 WebGL 上下文)處理,并且實現者通常將渲染本身委托給專門的庫(如 A-Frame)。

          document.getElementById('startVRButton').addEventListener('click', function () {
            if (navigator.xr) {
              checkForXR();
            } else if (navigator.getVRDisplays) {
              checkForVR();
            } else {
              alert('WebXR/WebVR APIs are not supported.');
            }
          });
          
          async function checkForXR() {
              if (!await navigator.xr.isSessionSupported('immersive-vr')) {
                  alert('No immersive VR device detected');
                  return;
              }
          
              const session = await navigator.xr.requestSession('immersive-vr');
          
              if (!session.inputSources.length) {
                throw 'VR supported, but no VR input sources available';
              }
          
              const result = document.getElementById('result');
              result.innerHTML = session.inputSources.length + 'input sources detected';
          }
          
          async function checkForVR() {
            try {
              const displays = await navigator.getVRDisplays()
          
              if (!displays.length) {
                throw 'VR supported, but no VR displays available';
              }
          
              const result = document.getElementById('result');
          
              displays.forEach(function (display) {
                let li = document.createElement('li');
                li.innerHTML = display.displayName + '(' + display.displayId + ')';
                result.appendChild(li);
              })
          
            } catch (err) {
              alert(err);
            }
          }

          Fullscreen

          Fullscreen API 允許 Web 應用程序以全屏模式顯示自身或自身的一部分,而瀏覽器 UI 元素不可見,也是方向鎖定的先決條件狀態。

          var $ = document.querySelector.bind(document);
          var $$ = function (selector) {
            return [].slice.call(document.querySelectorAll(selector), 0);
          }
          var target = $('#logTarget');
          
          function logChange (event) {
            var timeBadge = new Date().toTimeString().split(' ')[0];
            var newState = document.createElement('p');
            newState.innerHTML = ''+ timeBadge +' '+ event +'.';
            target.appendChild(newState);
          }

          Screen Orientation & Lock

          Screen Orientation API 允許 Web 應用程序獲取有關文檔當前方向(縱向或橫向)的信息,以及將屏幕方向鎖定在請求的狀態。

          當前版本的規范在 window.screen.orientation 對象中完全定義了此功能。 以前的版本在 Microsoft Edge 中實現過一次,將方向鎖定分離為 window.screen.lockOrientation。

          var $ = document.getElementById.bind(document);
          
          var orientKey = 'orientation';
          if ('mozOrientation' in screen) {
            orientKey = 'mozOrientation';
          } else if ('msOrientation' in screen) {
            orientKey = 'msOrientation';
          }
          
          var target = $('logTarget');
          var device = $('device');
          var orientationTypeLabel = $('orientationType');
          
          function logChange (event) {
            var timeBadge = new Date().toTimeString().split(' ')[0];
            var newState = document.createElement('p');
            newState.innerHTML = ''+ timeBadge +' '+ event +'.';
            target.appendChild(newState);
          }
          
          if (screen[orientKey]) {
            function update() {
              var type = screen[orientKey].type || screen[orientKey];
              orientationTypeLabel.innerHTML = type;
          
              var landscape = type.indexOf('landscape') !== -1;
          
              if (landscape) {
                device.style.width = '180px';
                device.style.height = '100px';
              } else {
                device.style.width = '100px';
                device.style.height = '180px';
              }
          
              var rotate = type.indexOf('secondary') === -1 ? 0 : 180;
              var rotateStr = 'rotate(' + rotate + 'deg)';
          
              device.style.webkitTransform = rotateStr;
              device.style.MozTransform = rotateStr;
              device.style.transform = rotateStr;
            }
          
            update();
          
            var onOrientationChange = null;
          
            if ('onchange' in screen[orientKey]) { // newer API
              onOrientationChange = function () {
                logChange('Orientation changed to' + screen[orientKey].type + '');
                update();
              };
          
              screen[orientKey].addEventListener('change', onOrientationChange);
            } else if ('onorientationchange' in screen) { // older API
              onOrientationChange = function () {
                logChange('Orientation changed to' + screen[orientKey] + '');
                update();
              };
          
              screen.addEventListener('orientationchange', onOrientationChange);
            }
          
            // browsers require full screen mode in order to obtain the orientation lock
            var goFullScreen = null;
            var exitFullScreen = null;
            if ('requestFullscreen' in document.documentElement) {
              goFullScreen = 'requestFullscreen';
              exitFullScreen = 'exitFullscreen';
            } else if ('mozRequestFullScreen' in document.documentElement) {
              goFullScreen = 'mozRequestFullScreen';
              exitFullScreen = 'mozCancelFullScreen';
            } else if ('webkitRequestFullscreen' in document.documentElement) {
              goFullScreen = 'webkitRequestFullscreen';
              exitFullScreen = 'webkitExitFullscreen';
            } else if ('msRequestFullscreen') {
              goFullScreen = 'msRequestFullscreen';
              exitFullScreen = 'msExitFullscreen';
            }

          Wake Lock

          只要應用程序持有該資源的鎖,Wake Lock API 就允許 Web 應用程序防止屏幕或系統等資源變得不可用。 API 的目的是讓用戶或應用程序不間斷地完成正在進行的長時間活動(例如導航或閱讀)。

          在某些瀏覽器中實驗性的初始實現嘗試只是一個可由應用程序控制的布爾標志,被認為過于公開而容易被濫用,而且過于含蓄。

          自 2019 年中期起,提出了更明確的方法,并可以在 “實驗性 Web 平臺功能” 標志后面以及通過 Google Chrome 中的 Origin Trial 來使用。 它允許指定請求鎖定的資源,盡管目前只有屏幕選項可用。 當外部因素中斷鎖定時,API 還允許訂閱事件。

          function printStatus(status) {
            document.getElementById("status").innerHTML = status;
          }
          
          let wakeLockObj = null;
          
          function toggle() {
            if ("keepAwake" in screen) {
              screen.keepAwake = !screen.keepAwake;
              printStatus(screen.keepAwake ? 'acquired' : 'not acquired');
            } else if ("wakeLock" in navigator) {
              if (wakeLockObj) {
                wakeLockObj.release();
                wakeLockObj = null;
                printStatus('released');
              } else {
                printStatus('acquiring...');
                navigator.wakeLock.request('screen')
                  .then((wakeLock) => {
                    wakeLockObj = wakeLock;
          
                    wakeLockObj.addEventListener('release', () => {
                      printStatus('released externally');
                      wakeLockObj = null;
                    })
          
                    printStatus('acquired');
                  })
                  .catch((err) => {
                    console.error(err);
                    printStatus('failed to acquire:' + err.message);
                  })
              }
            }
          }
          
          if ("keepAwake" in screen) {
            document.getElementById("api").innerHTML = 'screen.keepAwake';
            printStatus('not acquired');
          } else if ("wakeLock" in navigator) {
            document.getElementById("api").innerHTML = 'navigator.wakeLock';
            printStatus('not acquired');
          }

          Presentation Features

          Presentation API 的目的是讓 Web 應用程序可以使用演示顯示模式,用于呈現的顯示器可以與瀏覽器正在使用的顯示器相同,但也可以是外部顯示設備。 瀏覽器可以充當演示的發起者以及接收在演示顯示器上外部發起的到演示的連接。

          目前該 API 僅在 Chrome 和 Opera、桌面版和 Android 上受支持。

          navigator.presentation.defaultRequest = new PresentationRequest(presentationUrl)
          request.getAvailability()
          availability.addEventListener('change', listener)

          參考資料

          https://whatwebcando.today/offline.html

          https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API/Using_the_Payment_Request_API

          https://whatwebcando.today/

          https://www.emclient.com/blog/em-client-features--offline-mode-157

          章首先介紹了JavaScript Web API的概念,解釋了它們是如何擴展網站功能并提供豐富用戶體驗的。接著,文章列舉了14個令人興奮的API,并詳細描述了它們的特點和用法。

          這些API包括:

          Web Speech API:允許網站實現語音識別和語音合成功能。 Web Bluetooth API:通過藍牙技術連接和控制外部設備。 WebVR API:為虛擬現實(VR)提供支持,使網站能夠與VR設備進行交互。 WebUSB API:允許網站與USB設備進行通信和交互。 WebRTC API:提供實時音視頻通信功能,支持網頁間的實時數據傳輸。 Web Animations API:用于創建復雜和流暢的動畫效果。 Web Speech Synthesis API:提供語音合成功能,讓網站能夠生成語音輸出。

          1. Screen Capture API

          屏幕捕獲API正如其名,允許我們捕獲屏幕內容,使構建屏幕錄制器的過程變得輕而易舉。我們需要一個視頻元素來顯示捕獲的屏幕。開始按鈕將啟動屏幕捕獲。

          <video id="preview" autoplay>
            Your browser doesn't support HTML5.
          </video>
          <button id="start" class="btn">Start</button>
          
          const previewElem = document.getElementById("preview");
          const startBtn = document.getElementById("start");
          
          async function startRecording() {
            previewElem.srcObject =
              await navigator.mediaDevices.getDisplayMedia({
                video: true,
                audio: true,
              });
          }
          
          startBtn.addEventListener("click", startRecording);
          

          2. Web Share API

          Web Share API允許我們將文本、鏈接甚至文件從網頁分享到設備上安裝的其他應用程序。

          async function shareHandler() {
            navigator.share({
              title: "Tapajyoti Bose | Portfolio",
              text: "Check out my website",
              url: "https://tapajyoti-bose.vercel.app/",
            });
          }
          

          注意:要使用Web Share API,需要用戶的交互。例如,按鈕點擊或觸摸事件。

          3. Intersection Observer API

          Intersection Observer API 檢測元素何時進入或離開視口,這對于實現無限滾動非常有用。

          4. Clipboard API

          剪貼板 API 允許我們讀取和寫入剪貼板中的數據。這對于實現復制到剪貼板的功能非常有用。

          async function copyHandler() {
            const text = "https://tapajyoti-bose.vercel.app/";
            navigator.clipboard.writeText(text);
          }
          

          5. Screen Wake Lock API

          你是否曾經想過YouTube是如何在播放視頻時防止屏幕關閉的?這是因為使用了屏幕保持喚醒(Screen Wake Lock)API。

          let wakeLock = null;
          
          async function lockHandler() {
            wakeLock = await navigator.wakeLock.request("screen");
          }
          
          async function releaseHandler() {
            await wakeLock.release();
            wakeLock = null;
          }
          

          注意:只有在頁面已經在屏幕上可見的情況下,才能使用屏幕喚醒鎖定API。否則,會拋出錯誤。

          6. Screen Orientation API

          Screen Orientation API 檢查當前屏幕的方向,甚至將其鎖定為特定的方向。

          async function lockHandler() {
            await screen.orientation.lock("portrait");
          }
          
          function releaseHandler() {
            screen.orientation.unlock();
          }
          
          function getOrientation() {
            return screen.orientation.type;
          }
          

          7. Fullscreen API

          Fullscreen API 在全屏模式下顯示一個元素或整個頁面。

          async function enterFullscreen() {
            await document.documentElement.requestFullscreen();
          }
          
          async function exitFullscreen() {
            await document.exitFullscreen();
          }
          

          注意:要使用全屏API,需要用戶的交互。

          8.Web Speech

          Web Speech API 可以讓你將語音數據整合到網絡應用中。Web Speech API 由兩個部分組成: SpeechSynthesis (文本轉語音)和 SpeechRecognition (異步語音識別)。

          // Speech Synthesis
          const synth = window.speechSynthesis;
          const utterance = new SpeechSynthesisUtterance("Hello World");
          synth.speak(utterance);
          
          // Speech Recognition
          const SpeechRecognition =
            window.SpeechRecognition ?? window.webkitSpeechRecognition;
          
          const recognition = new SpeechRecognition();
          recognition.start();
          recognition.onresult = (event) => {
            const speechToText = event.results[0][0].transcript;
            console.log(speechToText);
          };
          
          1. 盡管語音合成在所有主要瀏覽器上都有96%的覆蓋率,但語音識別在生產中的使用還為時尚早,只有86%的覆蓋率。
          2. API 不能在沒有用戶交互的情況下使用(例如: click , keypress 等)

          9.Page Visibility

          頁面可見性 API 允許我們檢查頁面對用戶是否可見。當你想要暫停視頻時,這非常有用。有兩種方法來進行此檢查:

          // Method 1
          document.addEventListener("visibilitychange", () => {
            if (document.visibilityState === "visible") {
              document.title = "Visible";
              return;
            }
            document.title = "Not Visible";
          });
          
          // Method 2
          window.addEventListener("blur", () => {
            document.title = "Not Visible";
          });
          window.addEventListener("focus", () => {
            document.title = "Visible";
          });
          

          兩種方法的區別在于,第二種方法將在您切換到另一個應用程序或不同的標簽時觸發,而第一種方法只會在我們切換到另一個標簽時觸發。

          10. Accelerometer

          加速度計API允許我們訪問設備的加速度數據。這可以用來創建使用設備的動作控制或者在用戶搖動設備時添加交互的游戲,可能性無限!

          const acl = new Accelerometer({ frequency: 60 });
          
          acl.addEventListener("reading", () => {
            const vector = [acl.x, acl.y, acl.z];
            const magnitude = Math.sqrt(vector.reduce((s, v) => s + v * v, 0));
            if (magnitude > THRESHOLD) {
              console.log("I feel dizzy!");
            }
          });
          
          acl.start();
          

          可以使用以下方式請求加速度計權限:

          navigator.permissions.query({ name: "accelerometer" }).then((result) => {
              if (result.state === "granted") {
                // now you can use accelerometer api
              } 
            });
          

          11. Geo-location

          地理定位 API 允許我們訪問用戶的位置。如果你正在構建與地圖或基于位置的服務相關的任何內容,這將非常有用。

          navigator.geolocation.getCurrentPosition(({ coords }) => {
            console.log(coords.latitude, coords.longitude);
          });
          

          可以使用以下方式請求地理位置權限:

          navigator.permissions.query({ name: "geolocation" }).then((result) => {
              if (result.state === "granted") {
                // now you can use geolocation api
              } 
            });
          

          12. Web worker

          Web Workers 使得在與Web應用程序的主執行線程分離的后臺線程中運行腳本操作成為可能。這樣做的好處是可以在一個獨立的線程中執行繁重的處理,使得主線程(通常是UI線程)能夠在沒有被阻塞/減慢的情況下運行。

          // main.js
          const worker = new Worker("worker.js");
          worker.onmessage = (e) => console.log(e.data);
          worker.postMessage([5, 3]);
          
          // worker.js
          onmessage = (e) => {
            const [a, b] = e.data;
            postMessage(a + b);
          };
          

          13. Resize Observer

          Resize Observer API 允許我們輕松觀察元素的大小并處理其變化。當你擁有一個可調整大小的側邊欄時,它非常有用。

          const sidebar = document.querySelector(".sidebar");
          const observer = new ResizeObserver((entries) => {
            const sidebar = entries[0];
            //Do something with the element's new dimensions
          });
          observer.observe(sidebar);
          

          14.Notification

          Notification API,顧名思義,允許您發送通知以打擾用戶(與頁面可見性 API 捆綁在一起,以更加打擾他們 )

          Notification.requestPermission().then((permission) => {
            if (permission === "granted") {
              new Notification("Hi there!", {
                body: "Notification body",
                icon: "https://tapajyoti-bose.vercel.app/img/logo.png",
              });
            }
          });
          

          上述提到的一些API仍處于實驗階段,并不被所有瀏覽器支持。因此,如果您想在生產環境中使用它們,應該先檢查瀏覽器是否支持。


          主站蜘蛛池模板: 爆乳熟妇一区二区三区霸乳| 亚洲美女视频一区二区三区| 国产vr一区二区在线观看| AV无码精品一区二区三区宅噜噜 | 日韩精品一区二区午夜成人版 | 国内精品视频一区二区三区 | 无码人妻精品一区二区三| 国内精自品线一区91| 久久福利一区二区| 内射少妇一区27P| 日韩好片一区二区在线看| 狠狠做深爱婷婷综合一区| 亚洲日韩精品国产一区二区三区| 2014AV天堂无码一区| 无码AV一区二区三区无码| 精品一区二区三区四区在线播放| 亚洲综合av永久无码精品一区二区| 国产一区二区三区在线| 亚洲AV无码一区二区三区性色| 亚洲天堂一区二区| 亚洲一区无码中文字幕| 国产精品主播一区二区| 天堂一区人妻无码| 海角国精产品一区一区三区糖心 | 日本不卡一区二区三区| 亚洲视频在线一区| 久久精品一区二区国产| 国产无套精品一区二区| 精品成人av一区二区三区| 日韩精品电影一区亚洲| 精品一区二区三区无码免费直播| 日韩精品一区二区三区国语自制| 乱码人妻一区二区三区| 亚洲日韩AV无码一区二区三区人| 亚洲一区中文字幕在线电影网| 日韩AV无码久久一区二区| 久久国产精品无码一区二区三区| 日本免费一区二区久久人人澡| 色狠狠色噜噜Av天堂一区| 久久国产精品一区二区| 精品视频一区二区三区在线观看|