Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
家好,很高興又見面了,我是"高級前端?進階?",由我?guī)е蠹乙黄痍P(guān)注前端前沿、深入前端底層技術(shù),大家一起進步,也歡迎大家關(guān)注、點贊、收藏、轉(zhuǎn)發(fā)!
最近讀到一篇關(guān)于 PWA 的文章《WHAT WEB CAN DO TODAY?》,加上本身自己對 PWA 這個專題也比較感興趣,所以抽空梳理了 PWA 目前主流功能以及功能描述。
文章從用戶體驗、Native 行為拉齊、App 生命周期、PWA 周邊功能、Camera & Microphone、設(shè)備特征、操作系統(tǒng)、用戶 Input 輸入、屏幕和輸出等眾多維度描述了 PWA 特征。
Web 應(yīng)用程序可以使用兩種技術(shù)提供離線體驗,較舊的實現(xiàn),即應(yīng)用程序緩存(Application Cache)已在瀏覽器中廣泛實現(xiàn),但由于各種概念和設(shè)計缺陷,現(xiàn)在正在棄用。
現(xiàn)代替代方案稱為 Cache API,可在 Service Worker 中使用 ,在 HTTPS 上運行的 Web 應(yīng)用程序可以請求瀏覽器安裝的獨立代碼單元。 然后,該單元與所屬的 Web 應(yīng)用程序分開運行,并通過事件與其進行通信。 Service Worker 是漸進式 Web 應(yīng)用程序 (PWA) 理念的基本構(gòu)建,除了作為推送通知 (Push Notifications) 、后臺同步(Background Sync)或地理圍欄(Geofencing)等多個復(fù)雜 API 的推動者之外,還可以用作功能齊全的 Web 代理。 其可以攔截所有 HTTP 請求,改變其內(nèi)容或行為甚至管理離線緩存。
navigator.serviceWorker.register(path)
navigator.serviceWorker.ready
serviceWorkerRegistration.update()
serviceWorkerRegistration.unregister()
Background Sync API 允許授權(quán)的 Web 應(yīng)用程序不依賴于穩(wěn)定的互聯(lián)網(wǎng)連接,并將 Web 相關(guān)操作推遲到網(wǎng)絡(luò)連接可用時。 API 綁定到 Service Worker,它是與所屬 Web 應(yīng)用程序分離的代碼執(zhí)行模型,允許后臺同步在應(yīng)用程序窗口關(guān)閉后也可以運行。
Background Sync API 本身只是向應(yīng)用程序發(fā)出有關(guān)已恢復(fù)連接的信號的一種方式。 它可以與任何離線存儲解決方案一起使用,以實現(xiàn)數(shù)據(jù)同步方案或應(yīng)用程序離線時發(fā)出的網(wǎng)絡(luò)請求的重放機制。
serviceWorkerRegistration.sync.register('syncTag')
self.addEventListener('sync', listener)
Payment Request API 允許 Web 應(yīng)用程序?qū)⒏犊罱Y(jié)帳流程委托給操作系統(tǒng),從而允許其使用平臺本機可用并為用戶配置的任何方法和付款提供商。 這種方法消除了應(yīng)用程序端處理復(fù)雜結(jié)賬流程的負擔(dān),縮小了支付提供商集成的范圍,并確保用戶更好地熟悉。
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 允許授權(quán)的 Web 應(yīng)用程序代表用戶以編程方式存儲和請求用戶憑證(例如:登錄名和密碼或聯(lián)合登錄數(shù)據(jù))。 該 API 提供了瀏覽器內(nèi)置或第三方密碼存儲的替代方案,允許 Web 應(yīng)用程序檢測何時以及如何存儲和讀取憑證,例如:提供自動登錄功能。
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);
// 創(chuàng)建證書
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);
}
通過 Notifications API 提供的通知,允許授權(quán)的 Web 應(yīng)用程序以標(biāo)準(zhǔn)化的方式吸引用戶的注意力。 通知由在瀏覽器選項卡中運行的 Web 應(yīng)用程序生成,并呈現(xiàn)給瀏覽器選項卡區(qū)域之外的用戶。
Notification.requestPermission([callback])
Notification.permission
new Notification(title, [options])
navigator.serviceWorker.getRegistration()
.then((reg) => reg.showNotification(title, [options]))
Push Messages 是移動平臺上眾所周知的功能,其允許授權(quán)的 Web 應(yīng)用程序向用戶訂閱遠程服務(wù)器發(fā)送的消息,即使 Web 應(yīng)用程序當(dāng)前沒有聚焦在瀏覽器中,這些消息也可以觸發(fā)向訂閱者顯示通知。 該消息可以傳送加密的有效 payload,并且可以請求顯示自定義操作按鈕。
serviceWorkerRegistration.pushManager.subscribe()
serviceWorkerRegistration.pushManager.getSubscription()
serviceWorker.addEventListener('push', listener)
Page Visibility API 對于 Web 應(yīng)用程序了解當(dāng)前是否顯示在前臺非常有用,特別是在不需要時停止資源密集型 UI 動畫或數(shù)據(jù)刷新。 而在移動設(shè)備上,這樣做的主要原因是減少電池的使用。
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 API 允許 Web 應(yīng)用程序檢測用戶不活動時的狀態(tài),即系統(tǒng)中沒有生成用戶驅(qū)動的事件或屏幕被鎖定。 與之前的前臺檢測功能相反,此 API 不依賴于當(dāng)前選項卡活動 ,其會檢測用戶何時離開設(shè)備但未鎖定設(shè)備或已變?yōu)榉腔顒訝顟B(tài),無論哪個選項卡處于活動狀態(tài)。
const idleDetector = new IdleDetector(options)
idleDetector.start()
const state = idleDetector.state
idleDetector.addEventListener('change', listener)
Permissions API 為 Web 應(yīng)用程序提供了統(tǒng)一的方式來查詢可能需要用戶同意的功能(如通知或地理位置)的權(quán)限狀態(tài)。 通過 Permissions API,應(yīng)用程序可以列出用戶授予的權(quán)限,而無需實際觸發(fā)該功能本身。
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)
}
}
第一個提案稱為定期后臺同步 API,它解決了后臺數(shù)據(jù)同步用例,補充了后臺同步功能。 其允許 Web 應(yīng)用程序注冊周期性事件,從而喚醒 Service Worker,并無需用戶交互即可執(zhí)行 HTTP 請求。
截至 2020 年初,該 API 僅在 Google Chrome 80+ 中進行實驗性使用,并且其使用僅限于具有足夠高參與度的已安裝應(yīng)用程序。 API 不保證同步的間隔 , 但允許通過 minInterval 參數(shù)請求最小間隔,可為了避免濫用,實際間隔取決于網(wǎng)絡(luò)可信度和用戶使用應(yīng)用程序的頻率等諸多因素。
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);
});
}
Web 應(yīng)用程序可以通過提供 manifest.json 文件標(biāo)準(zhǔn)化為 Web Manifest,指定將應(yīng)用程序視為目標(biāo)平臺上的一等公民所需的功能和行為,即添加(“install”)到主屏幕, 具有相關(guān)圖標(biāo)、全屏行為、主題、無瀏覽器欄的獨立外觀等,同時還可以作為放置與 Web 應(yīng)用程序關(guān)聯(lián)的所有元數(shù)據(jù)的集中位置。
{
"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"
}
Page Lifecycle API 是對先前存在的頁面狀態(tài)更改事件的補充,包括:前臺檢測和焦點信息。 當(dāng)非活動應(yīng)用程序的選項卡(inactive application's tab )將被凍結(jié)以優(yōu)化 CPU 和電池使用以及在后續(xù)激活時恢復(fù)時,其允許 Web 應(yīng)用程序注冊瀏覽器生成的事件。
該 API 還提供了 wasDiscarded 標(biāo)志,可以檢測凍結(jié)選項卡已被丟棄(從內(nèi)存中刪除)并在恢復(fù)時需要加載新頁面的情況。 對于這種頁面加載,該標(biāo)志將設(shè)置為 true。
截至 2020 年春季,該 API 僅在基于 Chromium 的瀏覽器中實現(xiàn)。
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});
Web Bluetooth API 是一個底層 API,允許 Web 應(yīng)用程序與附近支持低功耗藍牙的外圍設(shè)備配對并訪問其公開的服務(wù)。
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;
});
}
WebUSB API 允許 Web 應(yīng)用程序與系統(tǒng)中可用的通用串行總線兼容設(shè)備(Universal Serial Bus-compatible devices )進行交互。 為了授權(quán)應(yīng)用程序訪問設(shè)備,用戶需要在瀏覽器的 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 應(yīng)用程序與通過串行端口(Serial Port)連接到系統(tǒng)的設(shè)備進行交互。 為了授權(quán)應(yīng)用程序訪問設(shè)備,用戶需要在瀏覽器的 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;
}
}
Audio & Video Capture API 允許授權(quán)的 Web 應(yīng)用程序訪問來自設(shè)備的音頻和視頻捕獲接口的流,包括用來自攝像頭和麥克風(fēng)的可用數(shù)據(jù)。 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);
});
}
Image Capture API 允許 Web 應(yīng)用程序控制設(shè)備相機的高級設(shè)置,例如: 變焦、白平衡、ISO 或焦點,并根據(jù)這些設(shè)置拍照,其依賴于可能從流中獲取的 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));
}
Media Recorder API 是一個 Web API,允許 Web 應(yīng)用程序錄制本地或遠程音頻和視頻媒體流,它依賴于 mediaStream 對象 。
recorder = new MediaRecorder(mediaStream, options)
MediaRecorder.isMimeTypeSupported(mimeType)
recorder.start(interval)
Web 中的實時通信(簡稱 WebRTC)是一組 API,允許 Web 應(yīng)用程序向遠程對等方發(fā)送和接收流式實時視頻、音頻和數(shù)據(jù),而無需通過集中式服務(wù)器進行依賴。 不過,初始發(fā)現(xiàn)和連接握手需要實現(xiàn)特定信令協(xié)議之一的服務(wù)器。 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 是一組向 Web 應(yīng)用程序公開底層系統(tǒng)的圖像處理(如 OCR(文本檢測)、條形碼 / QR 掃描或人臉檢測功能)的服務(wù)。 檢測的可用性和質(zhì)量因操作系統(tǒng)和硬件而異,API 按原樣公開這些服務(wù)。
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));
}
Network Information API 允許 Web 應(yīng)用程序讀取當(dāng)前網(wǎng)絡(luò)類型以及基于客戶端使用的底層連接技術(shù)假定的最大下行鏈路速度,同時還允許在網(wǎng)絡(luò)類型發(fā)生更改時訂閱通知。
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);
}
瀏覽器向 Web 應(yīng)用程序公開網(wǎng)絡(luò)連接可用性信息,以便應(yīng)用程序可以做出正確反應(yīng),即在檢測到離線情況時停止所有利用網(wǎng)絡(luò)的操作并切換到緩存數(shù)據(jù)。
navigator.onLine
window.addEventListener('online', listener)
window.addEventListener('offline', listener)
Vibration API 允許 Web 應(yīng)用程序使用設(shè)備的內(nèi)置振動(如果存在)。
function vibrateSimple() {
navigator.vibrate(200);
}
function vibratePattern() {
navigator.vibrate([100, 200, 200, 200, 500]);
}
Battery Status API 允許 Web 應(yīng)用程序獲取有關(guān)設(shè)備電源、電池電量、預(yù)期充電或放電時間的信息。 每當(dāng)任何可用信息發(fā)生變化時,它還會公開事件。 API 允許應(yīng)用程序根據(jù)功率級別打開或者關(guān)閉其低能效操作。
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 API 允許 Web 應(yīng)用程序根據(jù)安裝的 RAM 內(nèi)存的大小來評估設(shè)備的類別。 出于性能原因,它可用于識別低端設(shè)備以提供精簡、輕量級的網(wǎng)站體驗。 API 提供的值并不暗示有多少內(nèi)存實際可供應(yīng)用程序使用 ,其目的僅用作設(shè)備類別指示。
document.getElementById('result').innerHTML = navigator.deviceMemory || 'unknown'
Web 應(yīng)用程序的離線存儲功能的原型和標(biāo)準(zhǔn)化技術(shù)進行了多次迭代。 第一次嘗試要么只是一些簡單的解決方法(例如:將數(shù)據(jù)存儲在 cookie 中),要么需要額外的軟件(例如: Flash 或 Google Gears)。 后來,Web SQL 的想法(基本上是在瀏覽器中原生包含 SQLite)被創(chuàng)造并在某些瀏覽器中實現(xiàn),但后來由于標(biāo)準(zhǔn)化困難而被棄用。
目前至少有三種不同且獨立的技術(shù)已標(biāo)準(zhǔn)化并可用。 最簡單的是 Web Storage ,一種鍵值字符串存儲,允許 Web 應(yīng)用程序持久地跨窗口存儲數(shù)據(jù) (localStorage) 或在單個瀏覽器選項卡中存儲單個會話的數(shù)據(jù) (sessionStorage)。 更復(fù)雜的 IndexedDB 是一個基于類似數(shù)據(jù)庫結(jié)構(gòu)的底層 API,其中事務(wù)和游標(biāo)通過索引進行迭代。 而 最新的 Cache API 是一個專門的解決方案,用于保存請求 、 響應(yīng)對,主要在 Service Worker 實現(xiàn)中有用。
任何持久性存儲(無論是 localStorage、IndexedDB 還是 Cache API)中存儲的數(shù)據(jù)的實際持久性都是由瀏覽器管理的,默認在內(nèi)存壓力情況下,可能會在未經(jīng)最終用戶同意的情況下被刪除。 為了解決這個問題,引入了 Storage API , 它為 Web 應(yīng)用程序提供了一種在用戶允許的情況下以完全可靠的方式存儲數(shù)據(jù)的方法。
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 API 使 Web 應(yīng)用程序能夠訪問有關(guān)用戶決定與應(yīng)用程序共享的文件的文件系統(tǒng)級只讀信息,即大小、MIME 類型、修改日期、內(nèi)容,而無需將文件發(fā)送到服務(wù)器。
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 用于通過 Google Chrome 進行的,以允許 Web 應(yīng)用程序查詢系統(tǒng)當(dāng)前使用的和可供應(yīng)用程序使用的存儲空間的大小。
最新的 Quota Estimation API 還包括一種請求瀏覽器保留所存儲數(shù)據(jù)的方法,否則這些數(shù)據(jù)將在系統(tǒng)發(fā)出內(nèi)存壓力信號時被清除, 請求此持久存儲功能的權(quán)限可能由瀏覽器基于啟發(fā)式授予(即 Google Chrome),或者可能需要明確的用戶同意(即 Firefox)。
舊的實現(xiàn)僅在帶有 webkit- 前綴的 Chrome 中受支持,用于保持臨時存儲和持久存儲之間的分離,并允許 Web 應(yīng)用程序在需要時請求更多存儲空間。
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';
});
}
}
傳統(tǒng)意義上,Web 依賴鼠標(biāo)和鍵盤作為唯一的輸入設(shè)備,而移動設(shè)備主要通過觸摸控制。 移動 Web 從一個有點棘手的問題開始,即將觸摸事件轉(zhuǎn)換為鼠標(biāo)事件(例如 mousedown)。
較新的 HTML5 方法是將 touch 作為一流的輸入方式,允許 Web 應(yīng)用程序攔截和識別復(fù)雜的多點觸摸手勢、徒手繪圖等。不幸的是,目前要么通過觸摸事件,例如 touchstart,這是供應(yīng)商走的路線,或者通過由微軟發(fā)起的更新、更通用的指針事件規(guī)范時,蘋果公司后來將其標(biāo)準(zhǔn)化為事實上的解決方案。
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;
}
Web Speech API 的語音識別部分允許授權(quán)的 Web 應(yīng)用程序訪問設(shè)備的麥克風(fēng)并生成所錄制語音的文字記錄,從而使得 Web 應(yīng)用程序可以使用語音作為輸入和控制方法之一,類似于觸摸或鍵盤。
從技術(shù)上講,語音識別功能也可以通過訪問麥克風(fēng)并使用 Web Audio API 處理音頻流來實現(xiàn),采用這種方法的典型示例庫是 pocketsphinx.js。
let recognition = new SpeechRecognition()
Clipboard API 為 Web 應(yīng)用程序提供了一種對用戶執(zhí)行的剪切、復(fù)制和粘貼操作做出反應(yīng)以及代表用戶直接讀取或?qū)懭胂到y(tǒng)剪貼板的方法。
有兩種類型的剪貼板 API 可用 ,比如:較舊的同步式和較新的異步式。 較新的 API 僅限于 HTTPS,并且需要明確的用戶權(quán)限才能進行粘貼操作 ,但截至 2020 年初在 Safari 中依然不可用。 舊的 API 沒有正確解決隱私問題,因此粘貼功能在大多數(shù)瀏覽器中不再起作用。
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);
CSS4 規(guī)范的交互媒體部分定義了媒體查詢,允許 Web 應(yīng)用程序根據(jù)用戶與應(yīng)用程序交互的方式更改其布局和用戶界面。 其允許識別瀏覽器的主指針(即鼠標(biāo)、觸摸、鍵盤),并決定它是細還是粗,以及是否可以使用 “經(jīng)典” 界面(如平板電腦上的觸摸)將鼠標(biāo)懸停在元素上,以便界面可以縮小或放大,并啟用懸停交互或相應(yīng)地用替代方案替換。
@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;
}
}
EyeDropper API 允許用戶使用吸管工具從屏幕上捕獲樣本顏色。
與基于 Chromium 的桌面瀏覽器上的 <input type="color"> 不同,此 API 提供了一個簡單的界面,可以使用標(biāo)準(zhǔn) API 選擇整個設(shè)備屏幕的顏色。
// 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 內(nèi)容:
<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>
Geolocation API 允許授權(quán)的 Web 應(yīng)用程序訪問設(shè)備提供的位置數(shù)據(jù),其本身是使用 GPS 或從網(wǎng)絡(luò)環(huán)境獲得。 除了一次性位置查詢之外,還為應(yīng)用程序提供了一種通知位置更改的方式。
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 () {
// 獲取當(dāng)前位置
navigator.geolocation.getCurrentPosition(function (location) {
appendLocation(location, 'fetched');
});
// 更新位置
watchId = navigator.geolocation.watchPosition(appendLocation);
});
} else {
target.innerText = 'Geolocation API not supported.';
}
第一代設(shè)備位置支持是 Device Orientation API 的一部分,其允許 Web 應(yīng)用程序訪問陀螺儀和指南針數(shù)據(jù),以確定用戶設(shè)備在所有三個維度上的靜態(tài)方向。
基于 Generic Sensor API 的新規(guī)范也存在方向傳感器 API(絕對和相對變體)。 與之前的規(guī)范相反,它提供了以四元數(shù)表示的讀數(shù),這使得它直接與 WebGL 等繪圖環(huán)境兼容。
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)";
}
第一代設(shè)備運動支持是 Device Orientation API 的一部分,其允許 Web 應(yīng)用程序訪問以加速度(以 m/s2 為單位)表示的加速度計數(shù)據(jù)和以事件形式提供的三個維度中每個維度的以旋轉(zhuǎn)角度變化(以 °/s 為單位)表示的陀螺儀數(shù)據(jù)。
自 2018 年中期以來,針對每種傳感器類型推出了基于通用傳感器 API 的更新的單獨規(guī)范。 這些 API 可直接訪問物理設(shè)備(加速計 API、陀螺儀 API 和磁力計 API)的讀數(shù)以及通過組合物理傳感器(線性加速傳感器 API 和重力傳感器 API)的讀數(shù)組成的高級融合傳感器。
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;
}
截至 2020 年初,對 Web 應(yīng)用程序的虛擬和增強現(xiàn)實的支持有限且不一致,有兩個可用的 API, 較舊的 WebVR API 可在某些瀏覽器中用于某些特定的 VR 環(huán)境,而較新的 WebXR 設(shè)備 API 試圖以更通用的方式處理該主題,包括 AR 或混合現(xiàn)實設(shè)備,從 2019 年底開始將部署在基于 Chromium 的瀏覽器中。
兩個 API 共享相同的基本概念,范圍是允許授權(quán)的 Web 應(yīng)用程序發(fā)現(xiàn)可用的 VR/AR 設(shè)備,與設(shè)備建立會話,讀取準(zhǔn)備正確渲染所需的特定于設(shè)備的幾何數(shù)據(jù),并將 <canvas> 元素作為可視層綁定到設(shè)備上 。
通過這種方式,渲染細節(jié)由現(xiàn)有的畫布接口(如 WebGL 上下文)處理,并且實現(xiàn)者通常將渲染本身委托給專門的庫(如 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 API 允許 Web 應(yīng)用程序以全屏模式顯示自身或自身的一部分,而瀏覽器 UI 元素不可見,也是方向鎖定的先決條件狀態(tài)。
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 API 允許 Web 應(yīng)用程序獲取有關(guān)文檔當(dāng)前方向(縱向或橫向)的信息,以及將屏幕方向鎖定在請求的狀態(tài)。
當(dāng)前版本的規(guī)范在 window.screen.orientation 對象中完全定義了此功能。 以前的版本在 Microsoft Edge 中實現(xiàn)過一次,將方向鎖定分離為 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';
}
只要應(yīng)用程序持有該資源的鎖,Wake Lock API 就允許 Web 應(yīng)用程序防止屏幕或系統(tǒng)等資源變得不可用。 API 的目的是讓用戶或應(yīng)用程序不間斷地完成正在進行的長時間活動(例如導(dǎo)航或閱讀)。
在某些瀏覽器中實驗性的初始實現(xiàn)嘗試只是一個可由應(yīng)用程序控制的布爾標(biāo)志,被認為過于公開而容易被濫用,而且過于含蓄。
自 2019 年中期起,提出了更明確的方法,并可以在 “實驗性 Web 平臺功能” 標(biāo)志后面以及通過 Google Chrome 中的 Origin Trial 來使用。 它允許指定請求鎖定的資源,盡管目前只有屏幕選項可用。 當(dāng)外部因素中斷鎖定時,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 API 的目的是讓 Web 應(yīng)用程序可以使用演示顯示模式,用于呈現(xiàn)的顯示器可以與瀏覽器正在使用的顯示器相同,但也可以是外部顯示設(shè)備。 瀏覽器可以充當(dāng)演示的發(fā)起者以及接收在演示顯示器上外部發(fā)起的到演示的連接。
目前該 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
者:當(dāng)耐特
來 源:cnblogs.com/iamzhanglei/p/6177961.html
廣而告之:由于此訂閱號換了個皮膚,系統(tǒng)自動取消了讀者的公眾號置頂。導(dǎo)致用戶接受文章不及時。可以打開訂閱號,選擇置頂(標(biāo)星)公眾號,重磅干貨,第一時間送達!
GitHub Pages大家可能都知道,常用的做法,是建立一個gh-pages的分支,通過setting里的設(shè)置的GitHub Pages模塊可以自動創(chuàng)建該項目的網(wǎng)站。
這里經(jīng)常遇到的痛點是,master遇到變更,經(jīng)常需要去sync到gh-pages,特別是純web前端項目,這樣的痛點是非常地痛。
Github官方可能嗅覺到了該痛點,出了個master當(dāng)作網(wǎng)站是選項,太有用了。
選擇完master branch之后,master自動變成了網(wǎng)站。master所有的提交會自動更新到網(wǎng)站。
比如你有一個文件里的某一行代碼寫得非常酷炫或者關(guān)鍵,想分享一下。
可以在url后面加上#L行號
比如,點擊下面這個url:
https://github.com/AlloyTeam/AlloyTouch/blob/master/alloy_touch.js#L240
你便會跳到alloy_touch.js的第240行。
那么問題來了?如果我是一段代碼,即多行代碼想分享呢?也很簡單:url后面加上
#L
開始行號-L
結(jié)束行號
比如,AlloyTouch的運動緩動和逆向緩動函數(shù)如下面代碼段所示:
https://github.com/AlloyTeam/AlloyTouch/blob/master/alloy_touch.js#L39-L45
其實也不用記憶你直接在網(wǎng)址后面操作,github自動會幫你生成url。比如你點擊39行,url變成了
https://github.com/AlloyTeam/AlloyTouch/blob/master/alloy_touch.js#L39
再按住shift點擊45行,url變成了
https://github.com/AlloyTeam/AlloyTouch/blob/master/alloy_touch.js#L39-L45
然后你這個url就可以復(fù)制分享出去了,點擊這個url的人自動會跳到39行,并且39-45行高亮。
比如有人提交了個issues https://github.com/AlloyTeam/AlloyTouch/issues/6然后你去主干上改代碼,改完之后提交填msg的時候,填入:
fix https://github.com/AlloyTeam/AlloyTouch/issues/6
這個issues會自動被關(guān)閉。當(dāng)然不僅僅是fix這個關(guān)鍵字。下面這些關(guān)鍵字也可以:
close
closes
closed
fixes
fixed
resolve
resolves
resolved
如下面所示,user和repo改成你想要展示的便可以
<iframe src="http://ghbtns.com/github-btn.html?
user=alloyteam&repo=alloytouch&type=watch&count=true"
allowtransparency="true"
frameborder="0" scrolling="0"
width="110" height="20">
</iframe>
插入之后你便可以看到這樣的展示:
如上圖所示,github會根據(jù)相關(guān)文件代碼的數(shù)量來自動識別你這個項目是HTML項目還是Javascript項目。
這就帶來了一個問題,比如AlloyTouch最開始被識別成HTML項目。
因為HTML例子比JS文件多。怎么辦呢?gitattributes來幫助你搞定。在項目的根目錄下添加如下.gitattributes文件便可
https://github.com/AlloyTeam/AlloyTouch/blob/master/.gitattributes
里面的:
*.html linguist-language=JavaScript
主要意思是把所有html文件后綴的代碼識別成js文件。
在自己的項目下,點擊Graphs,然后再點擊Traffic如下所示:
里面有Referring sites和Popular content的詳細數(shù)據(jù)和排名。如:Referring sites
其中Referring sites代表大家都是從什么網(wǎng)站來到你的項目的,Popular content代表大家經(jīng)常看你項目的哪些文件。
上面教大家設(shè)置語言了,下面可以看看怎么查看某類型語言的每日排行榜。比如js每日排行榜:
https://github.com/trending/javascript?since=daily
https://github.com/trending/html?since=daily
https://github.com/trending/css?since=daily
Github推薦:https://github.com/explore
issue中輸入冒號 : 添加表情
任意界面,shift + ?顯示快捷鍵
issue中選中文字,R鍵快速引用
好了,我就會這么多,也是我經(jīng)常使用的技巧。歡迎補充實用的技巧,我會持續(xù)更新上去…
如何置頂、標(biāo)星公眾號?
推薦閱讀
1.Spring Boot 為什么這么火火火火火火?
2.一文讀懂 Spring Data Jpa!
3.字節(jié)跳動、騰訊后臺開發(fā)面經(jīng)分享(2019.5)
4.10 行代碼構(gòu)建 RESTful 風(fēng)格應(yīng)用
么是JavaScript
JavaScript是一種基于對象和事件驅(qū)動的、并具有安全性能的腳本語言,已經(jīng)被廣泛用于Web應(yīng)用開發(fā),常用來為網(wǎng)頁添加各式各樣的動態(tài)功能,為用戶提供更流暢美觀的瀏覽效果。通常JavaScript腳本是通過嵌入在HTML中來實現(xiàn)自身的功能的。
JavaScript特點
是一種解釋性腳本語言(代碼不進行預(yù)編譯)。
主要用來向HTML(標(biāo)準(zhǔn)通用標(biāo)記語言下的一個應(yīng)用)頁面添加交互行為。
可以直接嵌入HTML頁面,但寫成單獨的js文件有利于結(jié)構(gòu)和行為的分離。
跨平臺特性,在絕大多數(shù)瀏覽器的支持下,可以在多種平臺下運行(如Windows、Linux、Mac、Android、iOS等)。
JavaScript組成
JavaScript日常用途
1、嵌入動態(tài)文本于HTML頁面。
2、對瀏覽器事件做出響應(yīng)。
3、讀寫HTML元素。
4、在數(shù)據(jù)被提交到服務(wù)器之前驗證數(shù)據(jù)。
5、檢測訪客的瀏覽器信息。
6、控制cookies,包括創(chuàng)建和修改等。
7、基于Node.js技術(shù)進行服務(wù)器端編程。
JavaScript的基本結(jié)構(gòu)
<script type="text/javascript"> <!— JavaScript 語句; —> </script >
示例:
…… <title>初學(xué)JavaScript</title> </head> <body> <script type="text/javascript"> document.write("初學(xué)JavaScript"); document.write("<h1>Hello,JavaScript</h1>"); </script> </body> </html>
<script>…</script>可以包含在文檔中的任何地方,只要保證這些代碼在被使用前已讀取并加載到內(nèi)存即可
JavaScript的執(zhí)行原理
網(wǎng)頁中引用JavaScript的方式
1、使用<script>標(biāo)簽
2、外部JS文件
<script src="export.js" type="text/javascript"></script>
3.直接在HTML標(biāo)簽中
<input name="btn" type="button" value="彈出消息框" onclick="javascript:alert('歡迎你');"/>
JavaScript核心語法:
1. 變量
①先聲明變量再賦值
var width; width = 5; var - 用于聲明變量的關(guān)鍵字 width - 變量名
②同時聲明和賦值變量
var catName= "皮皮"; var x, y, z = 10;
③不聲明直接賦值【一般不使用】
width=5;
變量可以不經(jīng)聲明而直接使用,但這種方法很容易出錯,也很難查找排錯,不推薦使用。
2. 數(shù)據(jù)類型
①undefined:示例:var width;
變量width沒有初始值,將被賦予值undefined
②null:表示一個空值,與undefined值相等
③number:
var iNum=23; //整數(shù)
var iNum=23.0; //浮點數(shù)
④Boolean:true和false 但是JS會把他們解析成1;0
⑤String:一組被引號(單引號或雙引號)括起來的文本 var string1="This is a string";
3. typeof運算符
typeof檢測變量的返回值;typeof運算符返回值如下:
①undefined:變量被聲明后,但未被賦值.
②string:用單引號或雙引號來聲明的字符串。
③boolean:true或false。
④number:整數(shù)或浮點數(shù)。
⑤object:javascript中的對象、數(shù)組和null。
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。