性能评估工具 Lighthouse。
初识 PWA
Chrome 在 72 版本中,对 Android 平台使用了 Trusted Web Activities(TWA) 和 Digital Asset Links(DAL),将 Web 结合到了应用程序中,并支持发布到 Googleplay 商店。
2018 年 6 月,微软也宣布 PWA 可以基于 UWP 发布到 Microsoft Store 中,作为应用程序使用。
HTTP Server PWA 必须运行在 HTTPS 环境或者 127.0.0.1 的本地服务环境下,所以在开发、测试的过程中需要有一个本地的 HTTP Server,这里建议使用 http-server,它是一个基于 Node.js 环境的简单、零配置的 HTTP Server 命令行工具。
1 2 // -p 指定端口 http-server -p 9000
调试工具 调试工具建议使用 Chrome 的内置工具 DevTools。
网络层拦截图片 1 2 3 4 5 6 7 self.addEventListener("fetch" , (event ) => { if (/network\.jpg$/ .test(event.request.url)) { return event.respondWith(fetch("images/pwa.jpg" )); } });
定制 404 页面 1 2 3 4 5 6 7 8 9 10 11 12 13 self.addEventListener("fetch" , (event ) => { if (event.request.mode == "navigate" ) { return event.respondWith( fetch(event.request).then((res ) => { if (res.status == 404 ) { return fetch("custom404.html" ); } return res; }) ); } });
离线可用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 const CACHE_NAME = "pwa" ; self.addEventListener("install" , (event ) => { self.skipWaiting(); event.waitUntil( caches.open(CACHE_NAME).then((cache ) => cache.addAll([ "images/network.jpg" , "custom404.html" , "/" , "index.html" , ]) ) ); }); self.addEventListener("fetch" , (event ) => { return event.respondWith( fetch(event.request) .then((res ) => { if (event.request.mode == "navigate" && res.status == 404 ) { return fetch("custom404.html" ); } return res; }) .catch(() => { return caches.open(CACHE_NAME).then((cache ) => { return cache.match(event.request).then((response ) => { if (response) { return response; } return cache.match("custom404.html" ); }); }); }) ); }); self.addEventListener("activate" , (event ) => { clients.claim(); });
预备知识 JavaScript Module 1 <script type="module" ></script>
当项目变大时,代码也变得难以维护。们直接面临着一系列问题,包括:
命名空间冲突:每一个脚本都暴露在全局作用域下,很可能造成命名冲突,例如 JQuery 和 Zepto 都使用 window.$。
依赖关系不清晰:对于脚本的依赖、版本和加载顺序无法合理地管理。
JavaScript 的模块化发展过程为从无模块化到 Common.js 规范,再到 AMD 规范、CMD 规范、UMD 规范。
Common.js 规范是 Node.js 的模块化规范,核心是通过 require 方法加载模块,通过 module. exports 来导出模块。浏览器无法直接使用,需要依赖于打包工具进行支持,如 webpack。 > AMD 规范与 Common.js 规范的主要区别是异步加载模块。 > CMD 规范和 AMD 规范类似,主要区别是依赖后置,模块加载完再执行。 > UMD 规范则是 AMD 规范和 Common. js 规范的结合。这三种规范在导入相应规范库的前提下,可以直接在浏览器执行。 > 以上模块规范各有优缺点,最终整合为浏览器原生支持的 ES6 Module 规范,也就是本章将要介绍的 JavaScript Module。
JavaScript Module 也称为 ES Module 或 ECMAScript Module。模块化的主要共同点是允许导入和导出模块。之前的几种模块化方案都是社区实现的,并不是 JavaScript 的标准规范,而 JavaScript Module 模块化方案是一个真正的规范,是可以直接运行在浏览器中的。
JavaScript Module 主要使用到了 export 和 import 命令。在模块内, 可以使用export 关键字导出 const、函数或任何其他变量绑定或声明 ,如 utils.mjs:
1 2 3 4 export const sayLen = (str ) => `字符串长度为 ${str.length} ` ;export function insertSpace (str ) { return str.split("" ).join(" " ); }
要使用模块,可以用import 关键字将要使用的模块导入 。例如,我们在 index.mjs 中使用 utils.mjs 模块中的 sayLen 和 insertSpace 方法:
1 2 3 4 import { sayLen, insertSpace } from "./utils.mjs" ;console .log(sayLen("你好" )); console .log(insertSpace("hello" ));
export 关键字 模块以文件来承载,模块内的所有变量外部是无法访问的,需要使用 export 关键字进行输出才可以供外部访问。有以下几种导出方式。
单个变量或函数导出 1 2 3 4 5 6 export const a = 1 ;export const b = 2 ;export function say ( ) { console .log("say!" ); }
以组对象的方式导出 1 2 3 4 5 6 7 8 const a = 1 ;const b = 2 ;function say ( ) { console .log("say!" ); } export { a, b, say };
使用 as 对导出的变量重命名 1 2 3 4 5 6 7 8 const a = 1 ;const b = 2 ;function say ( ) { console .log("say!" ); } export { a as varA, b as varB, say as FnSay };
可以使用 default 默认导出 1 2 3 4 export default function say ( ) { console .log("say!" ); }
import 关键字 当需要使用模块文件时,要通过 import 关键字进行导入操作,来访问模块文件 export 的值。
按变量名导入 1 2 3 import { a, b, say } from "./utils.mjs" ;say();
导入重命名 1 2 import { a, b, say as FnSay } from "./utils.mjs" ;FnSay();
可以使用 * 进行全量导入 1 2 3 4 5 import * as utils from "./utils.mjs" ;utils.say(); utils.a; utils.b;
导入 export default 类型,可以随意命名 1 2 3 4 5 6 7 8 export default function say ( ) { console .log("say!" ); } import s from "./utils.mjs" ;s();
同时导入 default 和其他接口 1 2 3 4 5 6 7 8 9 10 11 const a = 1 ;const b = 2 ;export default function say ( ) { console .log("say!" ); } export { a, b };import say, { a, b } from "./utils.mjs" ;import say, * as utils from "./utils.mjs" ;
JavaScript Module 中对于 import from 的路径有严格要求,必须是完整的 URL 或者以 “/“ “./“ “../“ 开头。
1 2 3 4 5 import { each } from "./lodash.mjs" ;import utils from "../utils.mjs" ;import utils from "/modules/utils.mjs" ;import utils from "https://test.test/modules/utils.mjs" ;
动态 import 方法 按需加载,需要的时候下载。
1 2 3 import ("./utils.mjs" ).then((module ) => { module .say(); });
浏览器中使用 JavaScript Module 1 2 <script type ="module" src ="index.mjs" > </script > <script nomodule src ="index-compatible.js" > </script >
上面的代码中,module 主要用来让支持 Module 的浏览器使用 index.mjs、nomodule ,让不支持 Module 的浏览器使用 兼容的index-compatible.js ,同时忽略 type=”module”。
加载时机 浏览器对<script>
标签不同属性的加载及运行时机有所不同。
写 nomodule 的时候建议也加入 defer 属性
1 <script nomodule defer src ="index-compatible.js" > </script >
扩展名 对于JavaScript Module的脚本,浏览器要求服务器的相应类型必须是JavaScriptMIME type text/javascript,扩展名并不重要。上面我们用.mjs作为JavaScriptModule的扩展名主要有两个原因:
mjs 可以让开发者知道这个文件是JavaScript Module,很容易进行区分
mjs 的扩展名可以让Node.js 或者 Babel 等默认按照 Module 进行解析,作为Module的交叉兼容方式。
执行次数 JavaScript Module的执行次数与传统的JavaScript也是不同的。相同的JavaScript Module加载完成后只会执行一次,而传统的JavaScript加载几次就执行几次
1 2 3 4 5 6 7 8 9 10 // a.js 执行多次 <script src ="a.js" > </script > <script src ="a.js" > </script > // b.mjs 只执行一次 <script type ="module" src ="b.mjs" > </script > <script type ="module" src ="b.mjs" > </script > <script type ="module" > import './b.mjs' </script >
跨域 如果JavaScript Module文件存在跨域,需要相应的服务器提供必需的CORSHeader来进行支持,如Access-Control-Allow-Origin: *。
为什么要用 JavaScript Module JavaScript Module有如下优势:
提供了一种更好的方式来组织变量和函数。
可以把代码分割成更小的、可以独立运行的代码块。
支持更多的现代浏览器语法,书写起来更方便。
支持PWA Service Worker的所有主流浏览器,也支持JavaScriptModule,所以不需要做任何适配旧浏览器的代码转化,直接将代码提供给各个浏览器即可。也就是忽略了不支持的旧浏览器。
Promise 回调方式主要会导致两个关键问题:
嵌套太深导致代码可读性太差。
行逻辑必须串行执行。
Promise 对象用于表示一个异步操作的结果,最终结果可能是“完成”“失败”或者其结果的值。Promise将嵌套的回调改造成一系列使用.then的链式调用,去除了层层嵌套的劣式代码风格。Promise不是一种解决具体问题的算法,而是一种更好的代码组织模式。
Promise对象的特点
对象的状态不受外界影响。 Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)、 Rejected(已失败)。根据异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,表示无法通过其他手段改变对象的状态。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved;从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,再对Promise对象添加回调函数,也会立即得到这个结果,
Promise 构造函数
参数executor是一个带有resolve和reject两个参数的函数。executor函数在Promise构造函数执行时同步执行,被传递到resolve和reject函数(executor函数在Promise构造函数返回新建对象前被调用)。resolve和reject函数被调用时,分别将Promise的状态改为fulfilled(完成)或rejected(失败)。executor内部通常会执行一些异步操作,一旦完成,则可以调用resolve函数来将Promise状态改成fulfilled,或者在发生错误时将它的状态改为rejected。如果在executor函数中抛出一个错误,那么该Promise状态为rejected。executor函数的返回值被忽略。
1 2 3 4 5 6 7 new Promise ((resolve,reject ) => { if (done) { resolve(value); }else { reject(error) } })
实例方法 then() 1 promiseObj.then(onResolved,onRejected)
参数: onResolved:函数类型。用于处理当前 Promise 对象 Resolved 状态的回调,参数为 Resolved 的值。 onRejected:函数类型。可选。用于处理当前 Promise 对象 Rejected 状态的回调,参数 Rejected 的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 new Promise (resolve => { resolve("resolve value" ); }).then(value = >{ console .log("onResolved" ,value); }) new Promise ((resolve,reject )=> { reject("reject value" ); }).then( value => {}, value => { console .log("onRejected" ,value) } )
catch 1 promiseObj.catch(onRejected);
该方法用于添加当前 Promise 对象 Rejected 的状态回调,并返回 Promise 对象的方法。它的行为与调用 promiseObj.then(undefined, onRejected) 相同。
1 2 3 4 5 new Promise ((resolve,reject ) => { reject("reject value" ); }).catch(value => { console .log("onRejected" ,value); })
finally 1 promiseObj.finally(onFinally)
该方法用于添加当前Promise对象的状态回调,无论结果是Resolved还是Rejected,都会执行这个回调函数,其返回值为Promise。
1 2 3 4 5 6 new Promise ((resolve,reject ) => { resolve("resolve value" ); }).finally(()=> { console .log("finally" ); })
关系图 1 2 3 4 5 6 7 8 9 10 taskA().then( () => taskB(), () => taskB() }).then(() => { taskD() }).catch(() => { taskE() }).finally(()=>{ taskF(); })
静态方法 Promise 接口包含如下静态方法:resolve、reject、all、race。
resolve 1 2 3 Promise .resolve(value)Promise .resolve(promise)Promise .resolve(thenable)
该方法返回一个状态由给定value决定的Promise对象。如果该值是一个Promise对象,则直接返回该对象;如果该值是thenable(即带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则(即该value为空、基本类型或者不带then方法的对象),返回的Promise对象状态为Resolved,并且将该value传递给对应的then方法。有时需要将现有对象转换为Promise对象,Promise.resolve方法就起到这个作用 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Promise .resolve("data" );new Promise (resolve => resolve("data" ));Promise .resolve("value" ).then(value => { console .log(value); }) const originPromise = Promise .resolve("originPromise" );Promise .resolve(originPromise).then(value => { console .log(value); }) Promise .resolve({ then:function (onResolved,onReject ) { onReject("onReject!" ); } }).then(value => { console .log(value); })
reject 实践方案 系统集成 系统集成项目组Fugu 音频和视频的捕获 可以通过MediaDevices API来实现音频和视频的捕获。这个API主要用来访问连接媒体输入的设备,如摄像头和麦克风,以及屏幕共享等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <div > <button id ="btnVi" > 捕获视频</button > </div > <video id ="vi" controls width ="350" height ="200" > </video > <div > <button id ="btnAu" > 捕获音频</button > </div > <audio id ="au" controls > </audio > <script > document .getElementById("btnVi" ).onclick = () => { getStream("video" , document .getElementById("vi" )); }; document .getElementById("btnAu" ).onclick = () => { getStream("audio" , document .getElementById("au" )); }; function getStream (type, el ) { if (!navigator.mediaDevices) { alert("mediaDevices API 不支持" ); return ; } navigator.mediaDevices .getUserMedia({ [type]: true }) .then(stream => { if ("srcObject" in el) { el.srcObject = stream; } else { el.src = window .URL.createObjectURL(stream); } el.onloadedmetadata = () => { el.play(); }; }) .catch(err => { console .log("捕获视频错误:" , err); }); } </script >
视频流的截图 通过ImageCapture API来控制设备摄像头的高级设置,例如缩放、白平衡、ISO或对焦等,并根据这些设置进行照片生成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <div > <button id ="btnVi" > 捕获视频</button > </div > <video id ="vi" controls width ="350" height ="200" > </video > <div > <button id ="btnPhoto" > 视频截图</button > </div > <img id ="photo" style ="border: 1px solid #aaa;width:350px;height:200px;" /> <script > let vStream; document .getElementById("btnVi" ).onclick = () => { getStream("video" , document .getElementById("vi" )); }; document .getElementById("btnPhoto" ).onclick = () => { takePhoto(vStream); }; function getStream (type, el ) { if (!navigator.mediaDevices) { alert("mediaDevices API 不支持" ); return ; } navigator.mediaDevices .getUserMedia({ [type]: true }) .then(stream => { vStream = stream; if ("srcObject" in el) { el.srcObject = stream; } else { el.src = window .URL.createObjectURL(stream); } el.onloadedmetadata = () => { el.play(); }; }) .catch(err => { console .log("捕获视频错误:" , err); }); } function takePhoto (stream ) { if (!stream) { alert("请先进行视频捕获。" ); return ; } if (!("ImageCapture" in window )) { alert("ImageCapture API 不支持。" ); return ; } new ImageCapture(stream.getVideoTracks()[0 ]) .takePhoto() .then(data => { document .getElementById("photo" ).src = URL.createObjectURL(data); }) .catch(err => console .log("截图错误: " , err)); } </script >
视频流下载 对于视频录制,很多场景下需要提供下载录制的视频的功能。这里可以借助MediaRecorder API来实现这个功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <div > <button id ="btnVi" > 捕获视频 & 开始记录</button > </div > <video id ="vi" controls width ="350" height ="200" > </video > <div > <button id ="btnPhoto" > 下载</button > </div > <script type ="module" > let vStream; let vRecorder; let recorderData = []; document .getElementById("btnVi" ).onclick = () => { getStream(document .getElementById("vi" )); }; document .getElementById("btnPhoto" ).onclick = () => { download(); }; function getStream (el ) { if (!navigator.mediaDevices) { alert("mediaDevices API 不支持" ); return ; } navigator.mediaDevices .getUserMedia({ video : true , audio : true }) .then(stream => { vStream = stream; if ("srcObject" in el) { el.srcObject = stream; } else { el.src = window .URL.createObjectURL(stream); } el.onloadedmetadata = () => { el.play(); }; try { vRecorder = new MediaRecorder(stream, { mimeType : "video/webm" }); console .log("创建 MediaRecorder: " , vRecorder); } catch (e) { return console .error("创建 MediaRecorder 失败:" , e); } vRecorder.ondataavailable = e => { if (e.data.size == 0 ) { return ; } recorderData.push(event.data); }; vRecorder.start(100 ); }) .catch(err => { console .log("捕获视频错误:" , err); }); } function download ( ) { if (!vStream || !vRecorder) { alert("请先捕获视频" ); return ; } console .log("开始下载" ); vRecorder.stop(); vStream.getTracks()[0].stop(); vStream.getVideoTracks()[0].stop(); const aDom = document .createElement("a" ); document .body.appendChild(aDom); aDom.style = "display: none" ; aDom.href = URL.createObjectURL( new Blob(recorderData, { type : "video/webm" }) ); aDom.download = "download.webm" ; aDom.click(); recorderData = []; vStream = vRecorder = null ; } </script >
语音识别 Web目前提供的语音识别相关的API,可以实现语音的获取及识别能力,这也是Web的另一种常用的输入能力。这里主要使用SpeechRecognition API来实现语音识别输入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <div style ="border:1px solid #ccc; width: 350px; height: 200px; " > <span id ="content-final" > </span > <span id ="content-tmp" style ="color:gray" > </span > </div > <div > <button id ="btn" > 开始识别</button > </div > <script type ="module" > let btnDom = document .getElementById("btn" ); let contentFinalDom = document .getElementById("content-final" ); let contentTmpDom = document .getElementById("content-tmp" ); let recognition; btnDom.onclick = () => { window .SpeechRecognition = window .SpeechRecognition || window .webkitSpeechRecognition; if (!SpeechRecognition) { alert("不支持 SpeechRecognition API" ); return ; } if (btnDom.innerText === "开始识别" ) { recognition = new SpeechRecognition(); recognition.continuous = true ; recognition.interimResults = true ; recognition.lang = "cmn-Hans-CN" ; recognition.start(); btnDom.innerText = "停止识别" ; recognition.onstart = () => { contentFinalDom.innerText = "" ; contentTmpDom.innerText = "" ; console .log("识别开始" ); }; recognition.onresult = event => { console .log("识别中" , event.results); let content = "" ; let contentTmp = "" ; for (let i = 0 ; i < event.results.length; i++) { if (event.results[i].isFinal) { content += event.results[i][0].transcript; } else { contentTmp += event.results[i][0].transcript; } } contentFinalDom.innerText = content; contentTmpDom.innerText = contentTmp; }; recognition.onerror = event => { console .log("识别错误" , event); }; recognition.onend = () => { console .log("识别结束" ); }; return ; } recognition.stop(); recognition = null ; btnDom.innerText = "开始识别" ; }; </script >
剪切板操作 在传统的Web能力中是不允许读取剪切板的,但目前有了Clipboard API,我们可以通过这个API对剪切板很方便地进行写和读操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <input id="copy" value="这是一段文字" /> <div> <button id="btnCopy" >复制</button> </div> <input id="paster" /> <div> <button id="btnPaster" >粘贴</button> </div> <script type="module" > let btnCopyDom = document .getElementById("btnCopy" ); let btnPasterDom = document .getElementById("btnPaster" ); let copyValueDom = document .getElementById("copy" ); let pasterValueDom = document .getElementById("paster" ); btnCopyDom.onclick = () => { if (!"clipboard" in navigator) { alert("不支持 clipboard API" ); return ; } navigator.clipboard .writeText(copyValueDom.value) .then(() => { console .log(`复制 ${copyValueDom.value} 成功` ); }) .catch(err => { console .error(`复制失败` , err); }); }; btnPasterDom.onclick = () => { if (!"clipboard" in navigator) { alert("不支持 clipboard API" ); return ; } navigator.clipboard .readText() .then(e => { pasterValueDom.value = e; console .log(`粘贴 ${e} 成功` ); }) .catch(err => { console .error(`粘贴失败` , err); }); }; </script>
网络类型及速度信息 可以通过Network Information API获取设备网络相关信息,开发者可以根据这些网络信息进行定制化处理。
1 2 3 4 5 6 7 8 9 10 if (!navigator.connection) { console .log("不支持 Network Information API" ); return ; } console .log("底层连接类型:" + navigator.connection.type);console .log("有效连接类型:" + navigator.connection.effectiveType);console .log("最大下行速度(MB):" + navigator.connection.downlinkMax);navigator.connection.onchange = info => { };
网络状态信息 可以通过navigator.onLine及一些在线、离线事件来监听网络变化,根据网络变化来做一些用户交互。响应事件代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 if (navigator.onLine) { console .log("你的网络当前在线" ); } else { console .log("你的网络当前离线" ); } window .ononline = () => { console .log("网络状态变化:当前网络在线" ); }; window .onoffline = () => { console .log("网络状态变化:当前网络离线" ); };
电池状态信息 可以通过BatteryManager API来获取设备的电池状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if (!navigator.getBattery || !navigator.battery) { console .log("不支持 BatteryManager API" ); } else { (navigator.getBattery() || Promise .resolve(navigator.battery)).then( battery => { console .log("当前电池充电:" + battery.charging); console .log("距离充电完成还剩(S):" + battery.chargingTime); console .log("距离电池耗尽还剩(S)" + battery.dischargingTime); console .log("电池放点等级:" + battery.level); battery.onchargingchange; battery.onchargingtimechange; battery.ondischargingtimechange; battery.onlevelchange; } ); }
设备内存信息 可以通过deviceMemory API来获取设备的内存信息,根据内存信息来调整性能,提升各个端的体验。
1 2 console .log("当前设备内存大小:" + navigator.deviceMemory + " GB" );
地理定位 在Web中可以通过Geolocation API获取位置数据,通常它会基于GPS和网络进行定位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <div id ="content" style ="width: 420px; height: 200px;border: 1px solid #333;" > </div > <div > <button id ="btnGet" > 获取位置</button > <button id ="btnWatch" > 监听位置变化</button > </div > <script type ="module" > let btnGet = document .getElementById("btnGet" ); let btnWatch = document .getElementById("btnWatch" ); let contentDom = document .getElementById("content" ); let watcher; btnGet.onclick = () => { if (!"geolocation" in navigator) { alert("不支持 Geolocation API" ); return ; } navigator.geolocation.getCurrentPosition( info => { console .log("获取位置成功" , info); contentDom.innerText += `获取位置:\n纬度 ${info.coords.latitude} 经度 ${info.coords.longitude} \n` ; }, err => { console .log("获取位置错误" , err); }, { enableHighAccuracy: false , timeout: Infinity , maximumAge: 0 } ); }; btnWatch.onclick = () => { if (!"geolocation" in navigator) { alert("不支持 Geolocation API" ); return ; } if (btnWatch.innerText == "监听位置变化" ) { if (watcher) { return ; } watcher = navigator.geolocation.watchPosition( info => { console .log("监听位置变化:" , info); contentDom.innerText += `监听位置变化:\n纬度 ${info.coords.latitude} 经度 ${info.coords.longitude} \n` ; }, err => { console .log("监听位置变化错误" , err); }, {} ); btnWatch.innerText = "停止监听位置变化" ; return ; } watcher && navigator.geolocation.clearWatch(watcher); contentDom.innerText += "监听停止\n" ; console .log("监听停止" ); btnWatch.innerText = "监听位置变化" ; }; </script >
设备位置 在Web中可以使用Device Orientation API来实现获取陀螺仪、指南针等数据,也可以通过Generic Sensor API和Orientation Sensor API来获取设备方向数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 let aos = new AbsoluteOrientationSensor();aos.addEventListener("reading" ,listener); aos.start(); aos.quaternion; let ros = new RelativeOrientationSensor();ros.addEventListener('reading' ,listener); aos.start(); aos.quaternion;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <img id ="img" src ="img.png" /> <div id ="content" > </div > <script type ="module" > let img = document .getElementById("img" ); let content = document .getElementById("content" ); window .ondeviceorientation = e => { let { gamma, beta, alpha } = e; console .log(alpha, beta, gamma); img.style.transform = `rotate(${alpha} deg) rotate3d(1, 0, 0, ${beta} deg)` ; content.innerHTML = ` alpha(方向): ${alpha} deg<br /> beta(前后): ${beta} deg<br /> gamma(左右): ${gamma} deg `; }; </script >
相关资料FetchEvent.respondWith √ Request.mode √ ES6 模块化的时代真的来临了么?Using MJS √
源码PWA 入门与实践