【无标题】

web API 整理

  1. 音频输出设备API: navigator.mediaDevices

    此API允许应用程序从网页中提供,用户指定应从扬声器,蓝牙耳机或其他音频输出设备播放音频

    就是选择播放音频的设备,示例l: (实验性的:浏览器兼容不太好)

    document.querSelector("#myButton").addEventListener("click", async () => {
    	if(!navigator.mediaDevices.selectAudioOutput) {
            console.log("selectAudioOutput() 不支持或不在安全上下文中");
            return;
        }
    	// 显示选择设备的提示
        const audioDevice = await navigator.mediaDevices.selectAudioOutput();
        // 创建一个音频元素并开始在默认设备上播放音频
        const audio = document.createElement("audio");
        audio.src="https://example.com/audio.mp3"
        audio.play();
        // 将接收器更改为选定的音频输出设备。
        audio.setSinkId(audioDevice.deviceId)
        console.log(`打印设备相关信息 ${audioDevice.kind}: ${audioDevice.label} id=${audioDevice.deviceId}`)
    })
    
  2. 后台提取API: 提供一种将处理延迟到用户连接之前的方法。

    用户在脱机时启动进程时进行提取。一旦它们连接起来,它就会开始。如果用户脱机,则该过程将暂停,直到用户再次打开。

    BackgroundFetchManager:一个映射,其中键时后台提取ID,值是BackgroundFetchRegistration对象, 示例:(实验性的:浏览器兼容不太好)

    if (!("BackgroundFetchManager" in self)){
        return
    }
    navigator.serviceWorker.ready.then(async (swReg) => {
        const bgFetch = await swReg.backgroundFetch.fetch(
        "my-fetch",["/ep-5.mp3", "ep-5-artwork.jpg"], {
            title: "xxx",
            icons:[
                {
                    sizes: "300*300",
                    src: "/ep-5-icon.png",
                    type: "image/png"
                }
            ],
            downloadTotal: 60 * 1024 *1024
        })
    })
    
  3. 后台同步API:允许 Web 应用程序将服务器同步工作推迟到其 Service Worker,以便在设备脱机时稍后处理。用途可能包括在使用应用程序时无法发送请求时在后台发送请求示例l: (实验性的:浏览器兼容不太好)

    // 请求后台同步
    async function syncMessagesLater() {
        const registration = await navigator.serviceWorker.ready
        try {
            await registration.sync.register("sys-messages")
        } catch {
            console.log("无法注册后台同步!")
        }
    }
    // 按代码验证后台同步
    navigator.serviceWorker.ready.then((registration) => {
        registration.sync.getTags().then(tags => {
            if (tags.includes("sys-messages")) {
                console.log("已请求信息同步")
            }
        })
    })
    // 监听Service Worker中的后台同步
    self.addEventListener("sync",(event) => {
        if (event.tag === "sync-messages") {
            event.waitUntil(sendOutboxMessage())
        }
    })
    
  4. Barcode DetectionAPI: 用于检测图像中条形码和二维码。支持条形码格式: aztec,code_128,code_39,code_93,codabar,data_matrix,ean_13,ean_8,itf,padf417,qr_code,upc_a,upc_a,upc_e

    // 创建检测器
    if(!("BarcodeDetector" in globalThis)) {
        console.log("此浏览器不支持条形码检测器。")
    }else {
        const barcodeDetector = new BarcodeDetector({
            formats: ["code_39","codabar","ean_13"]
        })
    }
    // 获取支持的格式
    BarcodeDetector.getSupportedFormats().then((supportedFormats) => {
        supportedFormats.forEach((format) => console.log(format))
    })
    // 检测条形码
    barcodeDetector.detect(imageEl)
    	.then((barcodes) => {
        	barcodes.forEach((barcode) => console.log(barcode.rawValue));
    	})
    	.catch((err) => {
        	console.log(err)
    	})
    
  5. Web蓝牙API: 提供连接低功耗蓝牙外围设备并与之交互的功能。示例:(实验性的:浏览器兼容不太好

    // 测试是否有权使用蓝牙设备
    const btPermission = await navigator.permission.query({name: "bluetooth"})
    if (btPermission.state !== "denied") {
        // navigator.bluetooth()
    }
    
  6. Battery Status API: 提供有关系统电池电量水平的信息,并让你在电池电量或充电状态发生变化时收到触发的事件通知,示例:

    navigator.getBattery().then((battery) => {
      function updateAllBatteryInfo() {
        updateChargeInfo();
        updateLevelInfo();
        updateChargingInfo();
        updateDischargingInfo();
      }
      updateAllBatteryInfo();
    
      battery.addEventListener("chargingchange", () => {
        updateChargeInfo();
      });
      function updateChargeInfo() {
        console.log(`电池是否充电中?${battery.charging ? "是" : "否"}`);
      }
    
      battery.addEventListener("levelchange", () => {
        updateLevelInfo();
      });
      function updateLevelInfo() {
        console.log(`电池电量:${battery.level * 100}%`);
      }
    
      battery.addEventListener("chargingtimechange", () => {
        updateChargingInfo();
      });
      function updateChargingInfo() {
        console.log(`电池充电时间:${battery.chargingTime}`);
      }
    
      battery.addEventListener("dischargingtimechange", () => {
        updateDischargingInfo();
      });
      function updateDischargingInfo() {
        console.log(`电池续航时间:${battery.dischargingTime}`);
      }
    });
    
  7. Broadcast Channel API: 实现同源下浏览器不同窗口,Tab页,iframe之间的简单通讯

    // 创建/加入某个频道
    let bc = new BroadcastChannel("test_channel")
    // 发送简单的消息示例
    bc.postMessage("this is a test message")
    // 接收消息
    bc.onmessage = (ev) => {
        console.log(ev)
    }
    // 频道断开连接
    bc.close()
    
  8. 后台任务协作调度API: requestIdleCallback(),提供了由用户代理决定的,在空闲时间自动执行队列任务的能力.

    window.requestIdleCallback = window.requestIdleCallback || function(handler) {
        let startTime = Date.now()
        return setTimeout(function () {
            handler({
             didTimeout: false,
                timeRemaining: function () {
                    return Math.max(0, 50.0 - (Date.now() - startTime));
                }
            })
        }, 1)
    }
    window.cancelIdleCallback = window.cancelIdleCallback || function(id) {
        clearTimeout(id)
    }
    // 对象的 Array,每个对象代表一个待运行的任务。
    const taskList = [];
    //  是一个已被添加到队列的任务数量计数器,只会增大,不会减小。我们用它计算总工作量进度的百分比值
    let totalTaskCount = 0;
    // 用于追踪到现在为止已处理了多少任务
    let currentTaskNumber = 0;
    // 是对当前处理中任务的一个引用。
    let taskHandle = null;
    
    // 用于插入我们在进度框显示状态中创建的任务总数。 span
    const totalTaskCountElem = document.getElementById("totalTaskCount");
    //  是我们用来呈现到当前为止处理过的任务数的元素。
    const currentTaskNumberElem = document.getElementById("currentTaskNumber");
    // 我们用它来呈现到当前为止处理过任务的百分比。
    const progressBarElem = document.getElementById("progress");
    // 是开始按钮。
    const startButtonElem = document.getElementById("startButton");
    //  我们在 <div> 中显示记录过的文本信息。
    const logElem = document.getElementById("log");
    
    // 当渲染下一帧,我们的记录方法都会生成一个 DocumentFragment 来创建添加到记录的内容,并保存到 logFragment 中 DocumentFragment 。
    let logFragment = null;
    // 我们用它来追踪我们是否已经为即将到来的帧安排了状态显示框的更新,所以我们每一帧只执行一次。
    let statusRefreshScheduled = false;
    
    /// 管理任务队列
    /*
    taskHandler 一个函数,被调用来处理任务。
    taskData 一个对象(object),被当作输入参数传递给 taskHandler,以允许任务接收自定义数据。
    */
    function enqueueTask(taskHandler, taskData) {
      taskList.push({
        handler: taskHandler,
        data: taskData,
      });
    
      totalTaskCount++;
    
      if (!taskHandle) {
        taskHandle = requestIdleCallback(runTaskQueue, { timeout: 1000 });
      }
    
      scheduleStatusRefresh();
    }
    // 执行任务
    function runTaskQueue(deadline) {
      while (
        (deadline.timeRemaining() > 0 || deadline.didTimeout) &&
        taskList.length
      ) {
        const task = taskList.shift();
        currentTaskNumber++;
    
        task.handler(task.data);
        scheduleStatusRefresh();
      }
    
      if (taskList.length) {
        taskHandle = requestIdleCallback(runTaskQueue, { timeout: 1000 });
      } else {
        taskHandle = 0;
      }
    }
    // 安排显示的更新
    function scheduleStatusRefresh() {
      if (!statusRefreshScheduled) {
        requestAnimationFrame(updateDisplay);
        statusRefreshScheduled = true;
      }
    }
    // 更新显示
    function updateDisplay() {
      const scrolledToEnd =
        logElem.scrollHeight - logElem.clientHeight <= logElem.scrollTop + 1;
    
      if (totalTaskCount) {
        if (progressBarElem.max !== totalTaskCount) {
          totalTaskCountElem.textContent = totalTaskCount;
          progressBarElem.max = totalTaskCount;
        }
    
        if (progressBarElem.value !== currentTaskNumber) {
          currentTaskNumberElem.textContent = currentTaskNumber;
          progressBarElem.value = currentTaskNumber;
        }
      }
    
      if (logFragment) {
        logElem.appendChild(logFragment);
        logFragment = null;
      }
    
      if (scrolledToEnd) {
        logElem.scrollTop = logElem.scrollHeight - logElem.clientHeight;
      }
    
      statusRefreshScheduled = false;
    }
    // 向记录添加文本
    function log(text) {
      if (!logFragment) {
        logFragment = document.createDocumentFragment();
      }
    
      const el = document.createElement("div");
      el.textContent = text;
      logFragment.appendChild(el);
    }
    // 运行任务
    // 任务处理器
    function logTaskHandler(data) {
      log(`运行任务 #${currentTaskNumber}`);
    
      for (let i = 0; i < data.count; i += 1) {
        log(`${(i + 1).toString()}. ${data.text}`);
      }
    }
    // 主程序
    function decodeTechnoStuff() {
      totalTaskCount = 0;
      currentTaskNumber = 0;
      updateDisplay();
    
      const n = getRandomIntInclusive(100, 200);
    
      for (let i = 0; i < n; i++) {
        const taskData = {
          count: getRandomIntInclusive(75, 150),
          text: `该文本来自任务 ${i + 1}/${n}`,
        };
    
        enqueueTask(logTaskHandler, taskData);
      }
    }
    
    document
      .getElementById("startButton")
      .addEventListener("click", decodeTechnoStuff, false);
    
  9. Compressiong Stream API :使用 gzip 或者默认格式压缩和解压缩数据流

    // 使用gzip对流进行压缩
    const compressedReadableStream = inputReadableStream.pipeThrough(
    	new CompressionStream("gzip"),
    )
    // 使用gzip解压缩blob
    async function DecompressBlob(blob) {
        const ds = new DecompressionStream("gzip")
        const decompressedStream = blob.stream().pipeThrough(ds)
        return await new Response(decompressedStream)
    }
    
  10. CSS 自定义高亮API: 可以通过使用 JavaScript 创建范围并使用 CSS 定义样式来设置文档中任意文本范围的样式。

    // 创建范围 标明你想设置样式的文本范围
    const parentNode = document.getElementById("foo");
    
    const range1 = new Range();
    range1.setStart(parentNode, 10);
    range1.setEnd(parentNode, 20);
    
    const range2 = new Range();
    range2.setStart(parentNode, 40);
    range2.setEnd(parentNode, 60);
    // 创建高亮
    const user1Highlight = new Highlight(user1Range1, user1Range2);
    const user2Highlight = new Highlight(user2Range1, user2Range2, user2Range3);
    // 注册高亮
    CSS.highlights.set("user-1-highlight", user1Highlight);
    CSS.highlights.set("user-2-highlight", user2Highlight);
    
    // 从注册表中删除一个高亮显示。
    CSS.highlights.delete("user-1-highlight");
    
    // 清除注册表。
    CSS.highlights.clear();
    
    // 为已注册高亮显示设置样式
    // css
    ::highlight(user-1-highlight) {
      background-color: yellow;
      color: black;
    }
    
  11. CSS Font Loading API: 为你提供了动态加载字体资源的事件和接口。

    //定义字体
    const font = new FontFace("myfont", "url(myfont.woff)", {
      style: "italic",
      weight: "400",
      stretch: "condensed",
    });
    // 把字体添加到 document.fonts(FontFaceSet)中
    document.fonts.add(font);
    // 加载字体
    font.load();
    
    // 等待到所有的字体都加载完毕
    document.fonts.ready.then(() => {
      // 使用该字体渲染文字(如:在 canvas 中绘制)
    });
    
    
  12. Contact Picker API: 允许用户从其联系人列表中选择条目,并与网站或应用程序共享所选条目的有限详细信息。

    // 代码检查是否支持联系人选取器 API
    const supported = "contacts" in navigator;
    
    // 检测支持的属性
    async function checkProperties() {
      const supportedProperties = await navigator.contacts.getProperties();
      if (supportedProperties.includes("name")) {
        // run code for name support
      }
      if (supportedProperties.includes("email")) {
        // run code for email support
      }
      if (supportedProperties.includes("tel")) {
        // run code for telephone number support
      }
      if (supportedProperties.includes("address")) {
        // run code for address support
      }
      if (supportedProperties.includes("icon")) {
        // run code for avatar support
      }
    }
    // 选择联系人
    const props = ["name", "email", "tel", "address", "icon"];
    const opts = { multiple: true };
    
    async function getContacts() {
      try {
        const contacts = await navigator.contacts.select(props, opts);
        handleResults(contacts);
      } catch (ex) {
        // Handle any errors here.
      }
    }
    
    
  13. CSS Painting API :它允许开发人员编写 JavaScript 函数,这些函数可以直接绘制到元素的背景、边框或内容中。

    // css
    aside {
      background-image: paint(myPaintedImage);
    }
    li {
      background-image: paint(boxbg);
      --boxColor: hsl(55 90% 60% / 100%);
    }
    
    li:nth-of-type(3n) {
      --boxColor: hsl(155 90% 60% / 100%);
      --widthSubtractor: 20;
    }
    
    li:nth-of-type(3n + 1) {
      --boxColor: hsl(255 90% 60% / 100%);
      --widthSubtractor: 40;
    }
    // js
    registerPaint(
      "boxbg",
      class {
        static get contextOptions() {
          return { alpha: true };
        }
    
        /*
         use this function to retrieve any custom properties (or regular properties, such as 'height')
         defined for the element, return them in the specified array
      */
        static get inputProperties() {
          return ["--boxColor", "--widthSubtractor"];
        }
    
        paint(ctx, size, props) {
          /*
           ctx -> drawing context
           size -> paintSize: width and height
           props -> properties: get() method
        */
    
          ctx.fillStyle = props.get("--boxColor");
          ctx.fillRect(
            0,
            size.height / 3,
            size.width * 0.4 - props.get("--widthSubtractor"),
            size.height * 0.6,
          );
        }
      },
    );
    CSS.paintWorklet.addModule("boxbg.js");
    
  14. CSS Properties and Values API: 允许开发者显式地定义它们的 CSS 自定义属性,允许设置属性类型检查、默认值以及是否可继承其值。

```
// js
window.CSS.registerProperty({
  name: "--my-color",
  syntax: "<color>",
  inherits: false,
  initialValue: "#c0ffee",
});
// css
@property --my-color {
  syntax: "<color>";
  inherits: false;
  initial-value: #c0ffee;
}
```
  1. Channel Messageing API: 允许两个不同的脚本运行在同一个文档的不同浏览器来直接通讯比如向iframe中通讯

    // 主页面-------------------------
    // 创建channel
    var input = document.getElementById("message-input");
    var output = document.getElementById("message-output");
    var button = document.querySelector("button");
    var iframe = document.querySelector("iframe");
    
    var channel = new MessageChannel();
    var port1 = channel.port1;
    
    // 等待 iframe 加载
    iframe.addEventListener("load", onLoad);
    
    function onLoad() {
      // 监听按钮点击
      button.addEventListener("click", onClick);
    
      // 在 port1 监听消息
      port1.onmessage = onMessage;
    
      // 把 port2 传给 iframe
      iframe.contentWindow.postMessage("init", "*", [channel.port2]);
    }
    
    // 当按钮点击时,在 port1 上发送一个消息
    function onClick(e) {
      e.preventDefault();
      port1.postMessage(input.value);
    }
    
    // 处理 port1 收到的消息
    function onMessage(e) {
      output.innerHTML = e.data;
      input.value = "";
    }
    //----------------------- 在IFrame里接收port和消息
    var list = document.querySelector("ul");
    var port2;
    
    // 监听初始的 port 传递消息
    window.addEventListener("message", initPort);
    
    // 设置传过来的 port
    function initPort(e) {
      port2 = e.ports[0];
      port2.onmessage = onMessage;
    }
    
    // 处理 port2 收到的消息
    function onMessage(e) {
      var listItem = document.createElement("li");
      listItem.textContent = e.data;
      list.appendChild(listItem);
      port2.postMessage('Message received by IFrame: "' + e.data + '"');
    }
    
  2. Clipboard API: 提供了响应剪贴板命令(剪切、复制和粘贴)与异步读写系统剪贴板的能力

    // 访问剪贴板
    navigator.clipboard
      .readText()
      .then(
        (clipText) => (document.querySelector(".editor").innerText += clipText),
      );
    // 写入剪贴板
    await navigator.clipboard.writeText("Howdy, partner!");
    
  3. Credential ManagementAPI: 允许网站存储和检索用户,联合账户和公钥证书.允许网站与用户代理的密码系统进行交互,以便网站能够以统一的方式处理站点凭证,而用户代理则可以为他们的凭证管理提供更好的帮助

    // 创建FederatedCredential的实例 提供关于联合身份提供程序的凭据的信息
    const cred = new FederatedCredential({
      id,
      name,
      provider: "https://account.google.com",
      iconURL,
    });
    
    // Store it
    navigator.credentials.store(cred).then(() => {
      // Do something else.
    });
    // 创建PasswordCredential的实例 提供有关用户名/密码对的信息。
    const pwdCredential = new PasswordCredential({
      id: "example-username", // 用户名 / ID
      name: "Carina Anand", // 显示名称
      password: "correct horse battery staple", // 密码
    });
    
    console.assert(pwdCredential.type === "password");
    // 创建 PublicKeyCredential 的新实例,提供使用不可窃取和数据破坏的非对称密钥对 (而不是密码登录) 的凭据。
    const createCredentialOptions = {
      challenge: new Uint8Array([
        21, 31, 105 /* 29 more random bytes generated by the server */,
      ]),
      rp: {
        name: "Example CORP",
        id: "login.example.com",
      },
      user: {
        id: new Uint8Array(16),
        name: "canand@example.com",
        displayName: "Carina Anand",
      },
      pubKeyCredParams: [
        {
          type: "public-key",
          alg: -7,
        },
      ],
    };
    
    navigator.credentials
      .create({ createCredentialOptions })
      .then((newCredentialInfo) => {
        const response = newCredentialInfo.response;
        const clientExtensionsResults =
          newCredentialInfo.getClientExtensionResults();
      })
      .catch((err) => {
        console.error(err);
      });
    
  4. Device Orientation Events: 允许您检测设备的物理方向以及允许您检测设备运动的事件。移动设备通常具有陀螺仪、指南针和加速度计等传感器,这些传感器可以使设备上运行的应用程序检测设备的方向和运动。

```javascript
// DeviceMotionEvent:表示设备加速度的变化以及旋转速率。
window.addEventListener("devicemotion", (event) => {
  console.log(`${event.acceleration.x} m/s2`);
});
// DeviceMotionEventAcceleration: 表示设备沿所有三个轴所经历的加速度
DeviceMotionEventAcceleration.x
DeviceMotionEventAcceleration.y
DeviceMotionEventAcceleration.z
// DeviceOrientationEvent: 表示设备物理方向的变化。
window.addEventListener("deviceorientation", (event) => {
  console.log(`${event.alpha} : ${event.beta} : ${event.gamma}`);
});
// DeviceMotion定期发射,以指示设备当时接收的物理加速度,以及设备的旋转速率。
ddEventListener("devicemotion", (event) => {});
ondevicemotion = (event) => {};
// DeviceOrientation 当设备提供有关设备当前方向与地球坐标系相比的最新数据时触发。
addEventListener("deviceorientation", (event) => {});
ondeviceorientation = (event) => {};
// DeviceOrientationAbsolute 当绝对设备方向发生变化时触发。
addEventListener("deviceorientationabsolute", (event) => {});
ondeviceorientationabsolute = (event) => {};

```
  1. Document Picture-in-Picture: 可以打开一个始终位于顶部的窗口,该窗口可以填充任意 HTML 内容,例如,具有自定义控件的视频或显示视频电话会议参与者的一组流。它扩展了早期的画中画,该 API 专门支持将 HTML Video 元素放入始终位于顶部的窗口中。 Document Picture-in-Picture API Example (mdn.github.io)

    // 创建
    const videoPlayer = document.getElementById("player");
    
    // ...
    
    // Open a Picture-in-Picture window.
    const pipWindow = await window.documentPictureInPicture.requestWindow({
      width: videoPlayer.clientWidth,
      height: videoPlayer.clientHeight,
    });
    // Add pagehide listener to handle the case of the pip window being closed using the browser X button
    pipWindow.addEventListener("pagehide", (event) => {
        inPipMessage.style.display = "none";
        playerContainer.append(videoPlayer);
    });
    
  2. Device Memory API: 确定设备内存,以确定设备具有的大致 RAM 量:使用设备内存 JavaScript API 或接受客户端提示

    const RAM = navigator.deviceMemory;
    
  3. EditContext API: 可用于在 Web 上生成支持高级文本输入体验的富文本编辑器,例如输入法编辑器(IME) 合成、表情符号选取器或任何其他特定于平台的编辑相关 UI 图面

    // 基本用法
    [HTML]
    <canvas id="editor-canvas"></canvas>
    [JS]
    const canvas = document.getElementById("editor-canvas");
    const editContext = new EditContext();
    canvas.editContext = editContext;
    //[HTML]
    <div id="editor" style="height:200px;background:#eee;"></div>
    [JS]
    const editorEl = document.getElementById("editor");
    const editContext = new EditContext(editorEl);
    editorEl.editContext = editContext;
    
    editContext.addEventListener("textformatupdate", (e) => {
      // Get the TextFormat instances.
      const formats = e.getTextFormats();
    
      // Iterate over the TextFormat instances.
      for (const format of formats) {
        console.log(
          `Applying a ${format.underlineThickness} ${format.underlineStyle} underline between ${format.rangeStart} and ${format.rangeEnd}.`,
        );
      }
    });
    
  4. Encrypted Media Extensions: 提供了用于控制内容播放的接口,这些内容受数字限制管理方案的约束

  5. EyeDropper API: 提供了一种创建拾色器工具的机制。使用该工具,用户可以从屏幕上取样颜色,包括浏览器窗口之外的区域。通常允许用户从应用程序中的绘图或形状中取样颜色以便重用

    <button id="start-button">打开拾色器</button> <span id="result"></span>
    
    document.getElementById("start-button").addEventListener("click", () => {
      const resultElement = document.getElementById("result");
    
      if (!window.EyeDropper) {
        resultElement.textContent = "你的浏览器不支持 EyeDropper API";
        return;
      }
    
      const eyeDropper = new EyeDropper();
    
      eyeDropper
        .open()
        .then((result) => {
          resultElement.textContent = result.sRGBHex;
          resultElement.style.backgroundColor = result.sRGBHex;
        })
        .catch((e) => {
          resultElement.textContent = e;
        });
      setTimeout(() => {
          // 中止拾色器
        abortController.abort();
      }, 2000);
    });
    
  6. EncodingAPI:处理各种[字符编码文本],包括传统的非 [UTF-8] 编码

    包括 TextDecoder, TextEncoder

    // 解码
    let utf8decoder = new TextDecoder(); // default 'utf-8' or 'utf8'
    
    let u8arr = new Uint8Array([240, 160, 174, 183]);
    let i8arr = new Int8Array([-16, -96, -82, -73]);
    let u16arr = new Uint16Array([41200, 47022]);
    let i16arr = new Int16Array([-24336, -18514]);
    let i32arr = new Int32Array([-1213292304]);
    
    console.log(utf8decoder.decode(u8arr));
    console.log(utf8decoder.decode(i8arr));
    console.log(utf8decoder.decode(u16arr));
    console.log(utf8decoder.decode(i16arr));
    console.log(utf8decoder.decode(i32arr));
    
    const win1251decoder = new TextDecoder("windows-1251");
    const bytes = new Uint8Array([
      207, 240, 232, 226, 229, 242, 44, 32, 236, 232, 240, 33,
    ]);
    console.log(win1251decoder.decode(bytes)); // Привет, мир!
    // 编码
    const encoder = new TextEncoder();
    const view = encoder.encode("€");
    console.log(view); // Uint8Array(3) [226, 130, 172]
    
  7. FedCM API: 为身份提供商 (IdP) 提供了一种标准机制,以隐私保护的方式在 Web 上提供联合身份验证服务,而无需第三方 Cookie 和重定向。这包括一个 JavaScript API,该 API 允许对登录或在网站上注册等活动使用联合身份验证。FedCM RP Demo (fedcm-rp-demo.glitch.me)

    //显示授予iframe使用FedCM的权限
    <iframe src="3rd-party.example" allow="identity-credentials-get"></iframe>
    
  8. 文件API,使得web应用可以访问文件和其他内容

    <input type="file" />
    <div class="output"></div>
    
    .output {
      overflow: scroll;
      margin: 1rem 0;
      height: 200px;
    }
    
    const fileInput = document.querySelector("input[type=file]");
    const output = document.querySelector(".output");
    
    fileInput.addEventListener("change", () => {
      const [file] = fileInput.files;
      if (file) {
        const reader = new FileReader();
        reader.addEventListener("load", () => {
          output.innerText = reader.result;
        });
        reader.readAsText(file);
      }
    });
    
  9. Fencde Frame API: 用于控制嵌入 元素中的内容的功能。不能直接通过常规脚本控制嵌入

    类似frame标签,内容与其嵌入网站之间无法共享通信

    const frameConfig = await navigator.runAdAuction({
      // ...auction configuration
      resolveToConfig: true,
    });
    
    const frame = document.createElement("fencedframe");
    frame.config = frameConfig;
    
  10. 文件系统API:以及通过文件系统访问 API提供的扩展来访问设备文件系统中的文件——允许使用文件读写以及文件管理功能。

    // 显示目录选择器 showDirectoryPicker()
    const dirHandle = await window.showDirectoryPicker();
    
    // 打开文件选择器
    const pickerOpts = {
      types: [
        {
          description: "Images",
          accept: {
            "image/*": [".png", ".gif", ".jpeg", ".jpg"],
          },
        },
      ],
      excludeAcceptAllOption: true,
      multiple: false,
    };
    // 创建用于存放文件句柄的变量。
    let fileHandle;
    
    async function getFile() {
      // 打开文件选择器,解构返回的数组中的第一个元素
      [fileHandle] = await window.showOpenFilePicker(pickerOpts);
    
      // 操作 fileHandle 的后续代码
    }
    
    
  11. 全屏API: 为使用用户的整个屏幕展现网络内容提供了一种简单的方式,并且在不需要时退出全屏模式

    function toggleFullScreen(dom) {
      if (!document.fullscreenElement) {
        // 全屏
        dom.requestFullscreen();
      } else {
        if (document.exitFullscreen) {
          document.exitFullscreen();
        }
      }
    }
    function startup() {
      // Get the reference to video
      const video = document.getElementById("video");
        // 默认 document.documentElement
    
      // On pressing ENTER call toggleFullScreen method
      document.addEventListener("keypress", function(e) {
        if (e.key === 'Enter') {
          toggleFullScreen(video);
        }
      }, false);
    }
    
  12. 文件与目录API: 模拟一个 web 应用可以导航和访问的本地文件系统。你可以在虚拟的沙箱文件系统中开发一个读、写、创建文件或者目录的应用

    // FileSystem 表示文件系统。
    // FileSystemEntry 表示文件系统中单个条目的基本接口。这是由表示文件或目录的其他接口实现的。
    // FileSystemFileEntry表示文件系统中的单个文件。
    // FileSystemDirectoryEntry 表示文件系统中的单个目录
    // FileSystemDirectoryReader 提供了允许读取目录内容的功能。
    
  13. Gamepad API: API 可以给予开发者一种简单、统一的方式来识别并响应游戏控制器(手柄)。其中包含了三个接口、两个事件、一个特殊函数,用来响应控制器的连接与断开、获取其他关于控制器的信息以及识别当前是哪个按键或是哪个控制器被按下了

    // getGamepads
    function runAnimation() {
        window.requestAnimationFrame(runAnimation);
        for (const pad of navigator.getGamepads()) {
          // todo; simple demo of displaying pad.axes and pad.buttons
          console.log(pad);
        }
    }
    
    window.requestAnimationFrame(runAnimation);
    window.addEventListener("gamepadconnected", function (e) {
      console.log(
        "控制器已连接与 %d 位:%s. %d 个按钮,%d 个坐标方向。",
        e.gamepad.index,
        e.gamepad.id,
        e.gamepad.buttons.length,
        e.gamepad.axes.length,
      );
    });
    
  14. 地理位置API: Geolocation API,允许用户向 web 应用程序提供他们的位置。出于隐私考虑,报告地理位置前会先请求用户许可。

    geo = navigator.geolocation
    
    // getCurrentPosition 检索设备的当前位置。
    var options = {
      enableHighAccuracy: true,
      timeout: 5000,
      maximumAge: 0,
    };
    
    function success(pos) {
      var crd = pos.coords;
    
      console.log("Your current position is:");
      console.log("Latitude : " + crd.latitude);
      console.log("Longitude: " + crd.longitude);
      console.log("More or less " + crd.accuracy + " meters.");
    }
    
    function error(err) {
      console.warn("ERROR(" + err.code + "): " + err.message);
    }
    
    geo.getCurrentPosition(success, error, options);
    
    // watchPosition 注册一个处理函数,在设备位置发生改变时都会自动调用,并返回改变后的位置信息
    var id, target, options;
    
    function success(pos) {
      var crd = pos.coords;
    
      if (target.latitude === crd.latitude && target.longitude === crd.longitude) {
        console.log("Congratulations, you reached the target");
        navigator.geolocation.clearWatch(id);
      }
    }
    
    function error(err) {
      console.warn("ERROR(" + err.code + "): " + err.message);
    }
    
    target = {
      latitude: 0,
      longitude: 0,
    };
    
    options = {
      enableHighAccuracy: false,
      timeout: 5000,
      maximumAge: 0,
    };
    
    id = navigator.geolocation.watchPosition(success, error, options);
    
  15. HTML清理程序API: Sanitizer API,允许开发人员获取不受信任的 HTML 和 [Document] 或 [DocumentFragment] 对象字符串,并对其进行清理,以便安全地插入到文档的 DOM 中

    const unsanitized_string = "abc <script>alert(1)<" + "/script> def"; // Unsanitized string of HTML
    
    const sanitizer = new Sanitizer(); // Default sanitizer;
    
    // Get the Element with id "target" and set it with the sanitized string.
    const target = document.getElementById("target");
    target.setHTML(unsanitized_string, { sanitizer });
    
    console.log(target.innerHTML);
    // "abc  def"
    
  16. Houdini APi: 它们公开了 CSS 引擎的各个部分,从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展 CSS,可以直接访问CSS对象模型,其他再研究下

  17. HTML拖放API:接口使应用程序能够在浏览器中使用拖放功能。例如,用户可使用鼠标选择可拖拽(draggable)元素,将元素拖拽到可放置(droppable)元素,并释放鼠标按钮以放置这些元素。拖拽操作期间,会有一个可拖拽元素的半透明快照跟随着鼠标指针

    <script>
      function dragstart_handler(ev) {
      	// 定义拖拽效果
      	ev.dataTransfer.dropEffect = "copy";
      
         // 添加拖拽数据
          ev.dataTransfer.setData("text/plain", ev.target.innerText);
          ev.dataTransfer.setData("text/html", ev.target.outerHTML);
          ev.dataTransfer.setData(
            "text/uri-list",
            ev.target.ownerDocument.location.href,
          );
          // 定义拖拽图片
          var img = new Image();
          img.src = "example.gif";
          ev.dataTransfer.setDragImage(img, 10, 10);
          
      }
      function drop_handler(ev) {
        ev.preventDefault();
        // Get the id of the target and add the moved element to the target's DOM
        var data = ev.dataTransfer.getData("text/plain");
        ev.target.appendChild(document.getElementById(data));
      }
      function dragover_handler(ev) {
        ev.preventDefault();
        ev.dataTransfer.dropEffect = "move";
      }
      window.addEventListener("DOMContentLoaded", () => {
        // Get the element by id
        const element = document.getElementById("p1");
        // Add the ondragstart event listener
        element.addEventListener("dragstart", dragstart_handler);
      });
    </script>
    
    <p id="p1" draggable="true">This element is draggable.</p>
    
    <p
      id="target"
      ondrop="drop_handler(event)"
      ondragover="dragover_handler(event)">
      Drop Zone
    </p>
    
  18. History API: 通过 history 全局对象提供了对浏览器会话的历史记录(不要与 WebExtensions 的 history 混淆)的访问功能, history.back():在历史记录中向后跳转:,history.forward():向前跳转,history.go(-1),跳转到历史记录中的指定位置

```js
window.addEventListener("popstate", (event) => {
  alert(`位置:${document.location},状态:${JSON.stringify(event.state)}`);
});

history.pushState({ page: 1 }, "标题 1", "?page=1");
history.pushState({ page: 2 }, "标题 2", "?page=2");
history.replaceState({ page: 3 }, "标题 3", "?page=3");
history.back(); // 显示警告“位置:http://example.com/example.html?page=1,状态:{"page":1}”
history.back(); // 显示警告“位置:http://example.com/example.html,状态:null”
history.go(2); // 显示警告“位置:http://example.com/example.html?page=3,状态:{"page":3}”
```
  1. IdIe Detection API: 提供了一种方法来检测用户的空闲状态(活动、空闲和锁定),并在不从脚本轮询的情况下收到空闲状态更改的通知。提供用于检测设备或屏幕上的用户活动的方法和事件。

    const controller = new AbortController();
    const signal = controller.signal;
    
    startButton.addEventListener("click", async () => {
      if ((await IdleDetector.requestPermission()) !== "granted") {
        console.error("Idle detection permission denied.");
        return;
      }
    
      try {
        const idleDetector = new IdleDetector();
        idleDetector.addEventListener("change", () => {
          const userState = idleDetector.userState;
          const screenState = idleDetector.screenState;
          console.log(`Idle change: ${userState}, ${screenState}.`);
        });
    
        await idleDetector.start({
          threshold: 60_000,
          signal,
        });
        console.log("IdleDetector is active.");
      } catch (err) {
        // Deal with initialization errors like permission denied,
        // running outside of top-level frame, etc.
        console.error(err.name, err.message);
      }
    });
    
    stopButton.addEventListener("click", () => {
      controller.abort();
      console.log("IdleDetector is stopped.");
    });
    
  2. Ink API: 墨迹API, 允许浏览器在墨迹书写应用程序功能中绘制笔触时直接使用可用的操作系统级合成器,从而减少延迟并提高性能。使用鼠标绘制笔画

    <canvas id="canvas"></canvas>
    <div id="div">Delegated ink trail should match the color of this div.</div>
    
    div {
      background-color: rgb(0 255 0 / 100%);
      position: fixed;
      top: 1rem;
      left: 1rem;
    }
    
    const ctx = canvas.getContext("2d");
    const presenter = navigator.ink.requestPresenter({ presentationArea: canvas });
    let move_cnt = 0;
    let style = { color: "rgb(0 255 0 / 100%)", diameter: 10 };
    
    function getRandomInt(min, max) {
      min = Math.ceil(min);
      max = Math.floor(max);
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
    canvas.addEventListener("pointermove", async (evt) => {
      const pointSize = 10;
      ctx.fillStyle = style.color;
      ctx.fillRect(evt.pageX, evt.pageY, pointSize, pointSize);
      if (move_cnt == 20) {
        const r = getRandomInt(0, 255);
        const g = getRandomInt(0, 255);
        const b = getRandomInt(0, 255);
    
        style = { color: `rgb(${r} ${g} ${b} / 100%)`, diameter: 10 };
        move_cnt = 0;
        document.getElementById("div").style.backgroundColor =
          `rgb(${r} ${g} ${b} / 60%)`;
      }
      move_cnt += 1;
      await presenter.updateInkTrailStartPoint(evt, style);
    });
    
    window.addEventListener("pointerdown", () => {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    });
    
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    
  3. Insertable Streams for MediaStreamTrack API: 处理视频和音频时,插入其他元素或以其他方式处理流

    提供了一种向 [MediaStreamTrack] 添加新组件的方法

    const stream = await getUserMedia({ video: true });
    const videoTrack = stream.getVideoTracks()[0];
    
    const trackProcessor = new MediaStreamTrackProcessor({ track: videoTrack });
    const trackGenerator = new MediaStreamTrackGenerator({ kind: "video" });
    
    const transformer = new TransformStream({
      async transform(videoFrame, controller) {
        const barcodes = await detectBarcodes(videoFrame);
        const newFrame = highlightBarcodes(videoFrame, barcodes);
        videoFrame.close();
        controller.enqueue(newFrame);
      },
    });
    
    trackProcessor.readable
      .pipeThrough(transformer)
      .pipeTo(trackGenerator.writable);
    
  4. Image Capture API: 图像捕获,是一个用于从摄影设备捕获图像或视频的 API。除了捕获数据外,它还允许您检索有关设备功能的信息,例如图像大小、红眼减少以及是否有闪光灯以及它们当前设置为什么。相反,API 允许在设备允许的约束范围内配置功能。

    // 获取对设备的引用
    navigator.mediaDevices.getUserMedia({ video: true }).then((mediaStream) => {
       / 隔离媒体流的可视部分
        const track = mediaStream.getVideoTracks()[0];
        // 配置设备功能
        let zoom = document.querySelector("#zoom");
        const capabilities = track.getCapabilities();
        // Check whether zoom is supported or not.
        if (!capabilities.zoom) {
          return;
        }
        track.applyConstraints({ advanced: [{ zoom: zoom.value }] });
        
        let imageCapture = new ImageCapture(track);
    });
    
  5. InputDeviceCapabilities API: 提供有关输入事件的基础源的详细信息,比如触发触摸事件

    myButton.addEventListener("mousedown", (e) => {
      // Touch event case handled above, don't change the style again on tap.
      if (!e.sourceCapabilities.firesTouchEvents) myButton.classList.add("pressed");
    });
    
  6. IndexedDB: 用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索.https://w3c.github.io/IndexedDB/

  7. Intersection Observer API:异步检测目标元素与祖先元素或文档的视口相交情况变化的方法

    交叉观察器 API 可令代码注册一个回调函数,当特定元素进入或退出与另一元素(或视口)的交集时,或者当两个元素之间的交集发生指定变化时,该函数就会被执行

    // 创建一个交叉观察器
    let options = {
      root: document.querySelector("#scrollArea"),
      rootMargin: "0px",
      threshold: 1.0,
    };
    
    let observer = new IntersectionObserver(callback, options);
    let target = document.querySelector("#listItem");
    observer.observe(target);
    
    // 我们为观察器设置的回调将在第一次执行,
    // 它将等待我们为观察器分配目标(即使目标当前不可见)
    let callback = (entries, observer) => {
      entries.forEach((entry) => {
        // 每个条目描述一个目标元素观测点的交叉变化:
        //   entry.boundingClientRect
        //   entry.intersectionRatio
        //   entry.intersectionRect
        //   entry.isIntersecting
        //   entry.rootBounds
        //   entry.target
        //   entry.time
      });
    };
    
  8. Keyboard API: 提供了用于处理附加到运行浏览器的设备的物理键盘的方法.它提供了多种功能。键盘映射提供了一个接口,用于检索键盘上特定物理键生成的字符串,以便向用户正确标识该键。键盘锁定使网页能够捕获通常由用户代理或基础操作系统保留的密钥。键盘 API 的预期用途是提供全屏沉浸式体验的 Web 应用程序,例如游戏或远程访问应用

    // 键盘映射
    if (navigator.keyboard) {
      const keyboard = navigator.keyboard;
      keyboard.getLayoutMap().then((keyboardLayoutMap) => {
        const upKey = keyboardLayoutMap.get("KeyW");
        window.alert(`Press ${upKey} to move up.`);
      });
    } else {
      // Do something else.
    }
    // 键盘锁定
    navigator.keyboard.lock(["KeyW", "KeyA", "KeyS", "KeyD"]);
    // 写入系统密钥
    
    
  9. Lauch Handler API:启动处理程序 API 允许开发人员控制[渐进式 Web 应用](PWA) 的启动方式,例如,它是使用现有窗口还是创建新窗口,以及如何处理应用的目标启动 URL.

    
     // Do something with launchParams.targetURL
    if ("launchQueue" in window) {
      window.launchQueue.setConsumer((launchParams) => {
        if (launchParams.targetURL) {
          const params = new URL(launchParams.targetURL).searchParams;
    
          // Assuming a music player app that gets a track passed to it to be played
          const track = params.get("track");
          if (track) {
            audio.src = track;
            title.textContent = new URL(track).pathname.substr(1);
            audio.play();
          }
        }
      });
    }
    
  10. Local Font Access API: 提供了一种访问用户本地安装的字体数据的机制——这包括更高层次的详细信息,例如名称、样式和系列,以及底层字体文件的原始字节内容。

    if ("queryLocalFonts" in window) {
      // 支持本地字体访问 API
        // 字体枚举
        async function logFontData() {
          try {
            const availableFonts = await window.queryLocalFonts();
            for (const fontData of availableFonts) {
              console.log(fontData.postscriptName);
              console.log(fontData.fullName);
              console.log(fontData.family);
              console.log(fontData.style);
            }
          } catch (err) {
            console.error(err.name, err.message);
          }
        }
        //访问底层数据
        async function computeOutlineFormat() {
          try {
            const availableFonts = await window.queryLocalFonts({
              postscriptNames: ["ComicSansMS"],
            });
            for (const fontData of availableFonts) {
              // `blob()` 方法返回一个包含有效且完整的 SFNT 包装字体数据的 Blob。
              const sfnt = await fontData.blob();
              // 仅裁剪出我们需要的字节部分:前 4 个字节是 SFNT 版本信息。
              // 规范:https://learn.microsoft.com/zh-cn/typography/opentype/spec/otff#organization-of-an-opentype-font
              const sfntVersion = await sfnt.slice(0, 4).text();
    
              let outlineFormat = "UNKNOWN";
              switch (sfntVersion) {
                case "\x00\x01\x00\x00":
                case "true":
                case "typ1":
                  outlineFormat = "truetype";
                  break;
                case "OTTO":
                  outlineFormat = "cff";
                  break;
              }
              console.log("矢量字体格式:", outlineFormat);
            }
          } catch (err) {
            console.error(err.name, err.message);
          }
        }
    }
    
  11. Media Capabilities API: 媒体功能API,允许开发人员确定设备的解码和编码能力,公开媒体是否受支持、播放是否应流畅和节能等信息,并提供有关播放的实时反馈以更好地启用自适应流式处理,以及访问显示属性信息

    // 检测音频文件支持和预期性能
    if ("mediaCapabilities" in navigator) {
      const audioFileConfiguration = {
        type: "file",
        audio: {
          contentType: "audio/mp3",
          channels: 2,
          bitrate: 132700,
          samplerate: 5200,
        },
      };
    
      navigator.mediaCapabilities
        .decodingInfo(audioFileConfiguration)
        .then((result) => {
          console.log(
            `This configuration is ${result.supported ? "" : "not "}supported,`,
          );
          console.log(`${result.smooth ? "" : "not "}smooth, and`);
          console.log(`${result.powerEfficient ? "" : "not "}power efficient.`);
        })
        .catch(() => {
          console.log(`decodingInfo error: ${contentType}`);
        });
    }
    
  12. Media Session API: 媒体会话: 提供了一种自定义媒体通知的方法。它通过提供元数据供用户代理显示 Web 应用正在播放的媒体来实现此目的。它还提供了操作处理程序,浏览器可以使用这些处理程序来访问平台媒体键,例如键盘、耳机、遥控器上的硬件键,以及通知区域和移动设备锁屏上的软件键

    // 访问媒体会话 navigator.mediaSession
    navigator.mediaSession.playbackState = "playing"
    // 为音乐播放器设置操作处理程序
    if ("mediaSession" in navigator) {
      navigator.mediaSession.metadata = new MediaMetadata({
        title: "Unforgettable",
        artist: "Nat King Cole",
        album: "The Ultimate Collection (Remastered)",
        artwork: [
          {
            src: "https://dummyimage.com/96x96",
            sizes: "96x96",
            type: "image/png",
          },
          {
            src: "https://dummyimage.com/128x128",
            sizes: "128x128",
            type: "image/png",
          },
          {
            src: "https://dummyimage.com/192x192",
            sizes: "192x192",
            type: "image/png",
          },
          {
            src: "https://dummyimage.com/256x256",
            sizes: "256x256",
            type: "image/png",
          },
          {
            src: "https://dummyimage.com/384x384",
            sizes: "384x384",
            type: "image/png",
          },
          {
            src: "https://dummyimage.com/512x512",
            sizes: "512x512",
            type: "image/png",
          },
        ],
      });
    
      navigator.mediaSession.setActionHandler("play", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("pause", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("stop", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("seekbackward", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("seekforward", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("seekto", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("previoustrack", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("nexttrack", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("skipad", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("togglecamera", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("togglemicrophone", () => {
        /* Code excerpted. */
      });
      navigator.mediaSession.setActionHandler("hangup", () => {
        /* Code excerpted. */
      });
    }
    // 某些用户代理会禁用移动设备上媒体元素的自动播放,并且需要用户手势才能启动媒体
    playButton.addEventListener("pointerup", (event) => {
      const audio = document.querySelector("audio");
    
      // User interacted with the page. Let's play audio!
      audio
        .play()
        .then(() => {
          /* Set up media session controls, as shown above. */
        })
        .catch((error) => {
          console.error(error);
        });
    });
    // 使用操作处理程序控制幻灯片
    try {
      navigator.mediaSession.setActionHandler("previousslide", () => {
        log('> User clicked "Previous Slide" icon.');
        if (slideNumber > 1) slideNumber--;
        updateSlide();
      });
    } catch (error) {
      log('Warning! The "previousslide" media session action is not supported.');
    }
    
    try {
      navigator.mediaSession.setActionHandler("nextslide", () => {
        log('> User clicked "Next Slide" icon.');
        slideNumber++;
        updateSlide();
      });
    } catch (error) {
      log('Warning! The "nextslide" media session action is not supported.');
    }
    
  13. Media Capture and Streams API: 媒体捕捉与媒体流,是与 WebRTC 相关的 API,提供对音频或视频数据流的支持。它提供了用于处理媒体流及其组成轨道的接口和方法、与数据格式相关的约束、异步使用数据时成功和错误的回调以及在处理期间触发的事件。

    // 接口
    CanvasCaptureMediaStreamTrack
    InputDeviceInfo (en-US)
    MediaDeviceInfo (en-US)
    MediaDevices
    MediaStream
    MediaStreamEvent
    MediaStreamTrack
    MediaStreamTrackEvent (en-US)
    MediaTrackConstraints
    MediaTrackSettings (en-US)
    MediaTrackSupportedConstraints (en-US)
    OverconstrainedError (en-US)
    URL
    // 事件
    addtrack (en-US)
    ended (en-US)
    mute (en-US)
    overconstrained (en-US)
    removetrack (en-US)
    unmute (en-US)
    
  14. Media Source Extensions API: 媒体源扩展,提供了实现无插件且基于 Web 的流媒体的功能。使用 MSE,媒体串流能够通过 JavaScript 创建,并且能通过使用 audiovideo 元素进行播放。

  15. Navigation API: 导航API 提供了启动、拦截和管理浏览器导航操作的功能。它还可以检查应用程序的历史记录条目。这是以前的 Web 平台功能(如 History APIwindow.location)的继任者,解决了它们的缺点,专门针对单页应用程序 (SPA) 的需求。Navigation API demo (gigantic-honored-octagon.glitch.me)

    // 使用intercept 处理导航
    navigation.addEventListener("navigate", (event) => {
      // Exit early if this navigation shouldn't be intercepted,
      // e.g. if the navigation is cross-origin, or a download request
      if (shouldNotIntercept(event)) {
        return;
      }
    
      const url = new URL(event.destination.url);
    
      if (url.pathname.startsWith("/articles/")) {
        event.intercept({
          async handler() {
            // The URL has already changed, so show a placeholder while
            // fetching the new content, such as a spinner or loading page
            renderArticlePagePlaceholder();
    
            // Fetch the new content and display when ready
            const articleContent = await getArticleContent(url.pathname);
            renderArticlePage(articleContent);
          },
        });
      }
    });
    // 使用scroll 处理滚动
    navigation.addEventListener("navigate", (event) => {
      if (shouldNotIntercept(event)) {
        return;
      }
      const url = new URL(event.destination.url);
    
      if (url.pathname.startsWith("/articles/")) {
        event.intercept({
          async handler() {
            const articleContent = await getArticleContent(url.pathname);
            renderArticlePage(articleContent);
    
            event.scroll();
    
            const secondaryContent = await getSecondaryContent(url.pathname);
            addSecondaryContent(secondaryContent);
          },
        });
      }
    });
    //遍历到特定历史记录条目
    // On JS startup, get the key of the first loaded page
    // so the user can always go back there.
    const { key } = navigation.currentEntry;
    backToHomeButton.onclick = () => navigation.traverseTo(key);
    
    // Navigate away, but the button will always work.
    await navigation.navigate("/another_url").finished;
    // 更新状态
    navigation.navigate(url, { state: newState });
    // 或
    navigation.reload({ state: newState });
    // 或
    navigation.updateCurrentEntry({ state: newState });
    
  16. Network Information API: 网络信息API提供关于系统连接的一般连接类型的信息(如“wifi”、“cellular”等)。应用程序可以根据此信息为用户展现不同清晰度的内容

    // 侦测连接状态变化
    let type = navigator.connection.effectiveType;
    
    function updateConnectionStatus() {
      console.log(
        `设备的网络连接从 ${type} 变为了 ${navigator.connection.effectiveType}`,
      );
      type = navigator.connection.effectiveType;
    }
    
    connection.addEventListener("change", updateConnectionStatus);
    // 预加载大型资源
    let preloadVideo = true;
    const connection = navigator.connection;
    if (connection) {
      if (connection.effectiveType === "slow-2g") {
        preloadVideo = false;
      }
    }
    
  17. Page Visibility API: 页面可见性API: 提供了一些事件,你可以通过查看这些事件来了解文档何时变为可见或隐藏,还提供了一些功能来查看页面的当前可见性状态。

    网站有图片轮播效果,只有在用户观看轮播的时候,才会自动展示下一张幻灯片。
    显示信息仪表盘的应用程序不希望在页面不可见时轮询服务器进行更新。
    页面想要检测何时正在预渲染,以便可以准确的计算页面浏览量。
    当设备进入待机模式(用户按下电源键关闭屏幕)时,网站想要关闭设备声音。
    
    // 再页面隐藏时暂停音频
    <audio
      controls
      src="https://mdn.github.io/webaudio-examples/audio-basics/outfoxing.mp3"></audio>
     const audio = document.querySelector("audio");
    
    // 处理页面可见性变化:
    // - 如果页面隐藏,暂停音频
    // - 如果页面显示,播放音频
    document.addEventListener("visibilitychange", () => {
      if (document.hidden) {
        audio.pause();
      } else {
        audio.play();
      }
    });
    
  18. Periodic Background Synchronization API: 定期后台同步API, 提供了一种通过网络连接定期注册要在 Service Worker 中运行的任务的方法。这些任务称为定期后台同步请求,定期后台同步 API 允许 Web 应用程序以定期时间间隔提醒其 Service Worker 进行任何更新。用途可能包括在设备连接到 Wi-Fi 时获取最新内容,或允许对应用程序进行后台更新。

    // 请求定期后台同步
    async function registerPeriodicNewsCheck() {
      const registration = await navigator.serviceWorker.ready;
      try {
        await registration.periodicSync.register("get-latest-news", {
          minInterval: 24 * 60 * 60 * 1000,
        });
      } catch {
        console.log("Periodic Sync could not be registered!");
      }
    }
    // 通过标记验证后台定期同步
    navigator.serviceWorker.ready.then((registration) => {
      registration.periodicSync.getTags().then((tags) => {
        if (tags.includes("get-latest-news")) skipDownloadingLatestNewsOnPageLoad();
      });
    });
    // 删除定期后台同步任务
    self.addEventListener("periodicsync", (event) => {
      if (event.tag === "get-latest-news") {
        event.waitUntil(fetchAndCacheLatestNews());
      }
    });
    
  19. Popover API: 弹出框API, 为开发人员提供了一种标准、一致、灵活的机制,用于在其他页面内容之上显示弹出框内容。弹出框内容可以使用 HTML 属性以声明方式控制,也可以通过 JavaScript 进行控制

    <button popovertarget="mypopover">Toggle the popover</button>
    <div id="mypopover" popover>Popover content</div>
    
  20. Payment Handler API:支付处理程序API, 为 Web 应用程序提供了一组标准化的功能,用于直接处理付款,而不必重定向到单独的站点进行付款处理

    // 构造PaymentRequest 对象来启动付款请求
    const request = new PaymentRequest(
      [
        {
          // 付款方式
          supportedMethods: "https://alicebucks.dev/pay",
        },
        {
          supportedMethods: "https://bobbucks.dev/pay",
        },
      ],
      {
        total: {
          label: "total",
          amount: { value: "10", currency: "USD" },
        },
      },
    );
    // 付款方式清单文件
    // playment-manifest.json
    {
       // 告诉浏览器在哪里可以找到可以使用 BobBucks 付款方式的默认支付应用程序
      "default_applications": ["https://bobbucks.dev/manifest.json"],
      //如果需要,告诉浏览器允许哪些其他支付应用程序来处理 BobBucks 付款。如果它们已经安装在设备上,它们将作为替代支付选项与默认应用程序一起呈现给用户
      "supported_origins": ["https://alicepay.friendsofalice.example"]
    }
    // 检查支付应用是否准备好付款了
    async function checkCanMakePayment() {
      // ...
    
      const canMakePayment = await request.canMakePayment();
      if (!canMakePayment) {
        // Fallback to other means of payment or hide the button.
      }
    }
    self.addEventListener("canmakepayment", (e) => {
      e.respondWith(
        new Promise((resolve, reject) => {
          someAppSpecificLogic()
            .then((result) => {
              resolve(result);
            })
            .catch((error) => {
              reject(error);
            });
        }),
      );
    });
    // 处理付款
    let payment_request_event;
    let resolver;
    let client;
    
    // `self` is the global object in service worker
    self.addEventListener("paymentrequest", async (e) => {
      if (payment_request_event) {
        // If there's an ongoing payment transaction, reject it.
        resolver.reject();
      }
      // Preserve the event for future use
      payment_request_event = e;
    
      // ...
    });
    // 管理支付应用功能
    navigator.serviceWorker.register("serviceworker.js").then((registration) => {
      registration.paymentManager.userHint = "Card number should be 16 digits";
    
      registration.paymentManager
        .enableDelegations(["shippingAddress", "payerName"])
        .then(() => {
          // ...
        });
    
      // ...
    });
    
  21. Presentation API: 演示API 允许用户代理(如 Web 浏览器)通过大型演示设备(如投影仪和联网电视)有效地显示 Web 内容。支持的多媒体设备类型包括使用 HDMI、DVI 等连接的显示器,或使用 DLNA、ChromecastAirPlayMiracast 的无线显示器。

    // 监视演示显示器的可用性
    <button id="presentBtn" style="display: none;">Present</button>
    <script>
      // The Present button is visible if at least one presentation display is available
      const presentBtn = document.getElementById("presentBtn");
    
      // It is also possible to use relative presentation URL e.g. "presentation.html"
      const presUrls = [
        "https://example.com/presentation.html",
        "https://example.net/alternate.html",
      ];
    
      // Show or hide present button depending on display availability
      const handleAvailabilityChange = (available) => {
        presentBtn.style.display = available ? "inline" : "none";
      };
    
      // Promise is resolved as soon as the presentation display availability is known.
      const request = new PresentationRequest(presUrls);
      request
        .getAvailability()
        .then((availability) => {
          // availability.value may be kept up-to-date by the controlling UA as long
          // as the availability object is alive. It is advised for the web developers
          // to discard the object as soon as it's not needed.
          handleAvailabilityChange(availability.value);
          availability.onchange = () => {
            handleAvailabilityChange(availability.value);
          };
        })
        .catch(() => {
          // Availability monitoring is not supported by the platform, so discovery of
          // presentation displays will happen only after request.start() is called.
          // Pretend the devices are available for simplicity; or, one could implement
          // a third state for the button.
          handleAvailabilityChange(true);
        });
    </script>
    // 开始新的演示
    <script>
      presentBtn.onclick = () => {
        // Start new presentation.
        request
          .start()
          // The connection to the presentation will be passed to setConnection on success.
          .then(setConnection);
        // Otherwise, the user canceled the selection dialog or no screens were found.
      };
    </script>
    // 重新连接到演示文稿
    <button id="reconnectBtn" style="display: none;">Reconnect</button>
    <script>
      const reconnect = () => {
        // read presId from localStorage if exists
        const presId = localStorage["presId"];
        // presId is mandatory when reconnecting to a presentation.
        if (presId) {
          request
            .reconnect(presId)
            // The new connection to the presentation will be passed to
            // setConnection on success.
            .then(setConnection);
          // No connection found for presUrl and presId, or an error occurred.
        }
      };
      // On navigation of the controller, reconnect automatically.
      document.addEventListener("DOMContentLoaded", reconnect);
      // Or allow manual reconnection.
      reconnectBtn.onclick = reconnect;
    </script>
    // 由控制UA启动演示
    <script>
      navigator.presentation.defaultRequest = new PresentationRequest(presUrls);
      navigator.presentation.defaultRequest.onconnectionavailable = (evt) => {
        setConnection(evt.connection);
      };
    </script>
    // 监控连接状态并交换数据
    <button id="disconnectBtn" style="display: none;">Disconnect</button>
    <button id="stopBtn" style="display: none;">Stop</button>
    <button id="reconnectBtn" style="display: none;">Reconnect</button>
    <script>
      let connection;
    
      // The Disconnect and Stop buttons are visible if there is a connected presentation
      const stopBtn = document.querySelector("#stopBtn");
      const reconnectBtn = document.querySelector("#reconnectBtn");
      const disconnectBtn = document.querySelector("#disconnectBtn");
    
      stopBtn.onclick = () => {
        connection?.terminate();
      };
    
      disconnectBtn.onclick = () => {
        connection?.close();
      };
    
      function setConnection(newConnection) {
        // Disconnect from existing presentation, if not attempting to reconnect
        if (
          connection &&
          connection !== newConnection &&
          connection.state !== "closed"
        ) {
          connection.onclose = undefined;
          connection.close();
        }
    
        // Set the new connection and save the presentation ID
        connection = newConnection;
        localStorage["presId"] = connection.id;
    
        function showConnectedUI() {
          // Allow the user to disconnect from or terminate the presentation
          stopBtn.style.display = "inline";
          disconnectBtn.style.display = "inline";
          reconnectBtn.style.display = "none";
        }
    
        function showDisconnectedUI() {
          disconnectBtn.style.display = "none";
          stopBtn.style.display = "none";
          reconnectBtn.style.display = localStorage["presId"] ? "inline" : "none";
        }
    
        // Monitor the connection state
        connection.onconnect = () => {
          showConnectedUI();
    
          // Register message handler
          connection.onmessage = (message) => {
            console.log(`Received message: ${message.data}`);
          };
    
          // Send initial message to presentation page
          connection.send("Say hello");
        };
    
        connection.onclose = () => {
          connection = null;
          showDisconnectedUI();
        };
    
        connection.onterminate = () => {
          // Remove presId from localStorage if exists
          delete localStorage["presId"];
          connection = null;
          showDisconnectedUI();
        };
      }
    </script>
    // 监控可用连接并打招呼
    <script>
    const addConnection = (connection) => {
      connection.onmessage = (message) => {
        if (message.data === "Say hello") connection.send("hello");
      };
    };
    
    navigator.presentation.receiver.connectionList.then((list) => {
      list.connections.forEach((connection) => {
        addConnection(connection);
      });
      list.onconnectionavailable = (evt) => {
        addConnection(evt.connection);
      };
    });
    </script>
    //使用消息传递区域设置信息
    <script>
      connection.send('{"string": "你好,世界!", "lang": "zh-CN"}');
      connection.send('{"string": "こんにちは、世界!", "lang": "ja"}');
      connection.send('{"string": "안녕하세요, 세계!", "lang": "ko"}');
      connection.send('{"string": "Hello, world!", "lang": "en-US"}');
    </script>
    <script>
      connection.onmessage = (message) => {
        const messageObj = JSON.parse(message.data);
        const spanElt = document.createElement("SPAN");
        spanElt.lang = messageObj.lang;
        spanElt.textContent = messageObj.string;
        document.body.appendChild(spanElt);
      };
    </script>
    
  22. Permissions API: 权限API,提供了一种一致的编程方式来查询归因于当前上下文的 API 权限的状态。例如,权限 API 可用于确定是否已授予或拒绝访问特定 API 的权限,或者是否需要特定的用户权限

    // set up the page
    
    var mapCanvas = document.getElementById('map_canvas');
    var geoBtn = document.querySelector('.enable');
    var revokeBtn = document.querySelector('.revoke');
    geoBtn.onclick = function() {
      console.log('Permission currently denied; future features of the Permissions API will allow us to request permission here.')
      console.log('Currently you have to reset the permission state using the browser UI.')
      console.log('In Firefox it is done with Tools > Page Info > Permissions > Access Your Location.')
    }
    
    revokeBtn.onclick = function() {
      revokePermission();
    }
    
    // draw the google map, or not
    
      
    var positionDenied = function() {
      geoBtn.style.display = 'inline';
    };
      
    var revealPosition = function(position) {
      geoBtn.style.display = 'none';
      var markerTitle = "You are here";
    
      var latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
      var myOptions = {
        zoom: 16,
        center: latlng,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };
      
      var map = new google.maps.Map(mapCanvas, myOptions);
    
      var marker = new google.maps.Marker({
        position: latlng,
        map: map,
        title: markerTitle
      });
    }
    
    // test for geolocation support, provide geolocation settings, determine location of the user's device
    
    
    if (!"geolocation" in navigator) {
      alert("No geolocation available!");
    }
      
    var geoSettings = {
      enableHighAccuracy: false,
      maximumAge        : 30000,
      timeout           : 20000
    };
    
    // Start everything off
    
    function handlePermission() {
      navigator.permissions.query({name:'geolocation'}).then(function(result) {
        if (result.state == 'granted') {
          report(result.state);
          geoBtn.style.display = 'none';
        } else if (result.state == 'prompt') {
          report(result.state);
          navigator.geolocation.getCurrentPosition(revealPosition,positionDenied,geoSettings);
        } else if (result.state == 'denied') {
          report(result.state);
          geoBtn.style.display = 'inline';
        }
        result.onchange = function() {
          report(result.state);
        }
      });
    }
    
    function revokePermission() {
      navigator.permissions.revoke({name:'geolocation'}).then(function(result) {
        report(result.state);
      });
    }
    
    function report(state) {
      console.log('Permission: ' + state);
    }
    
    handlePermission();
    
  23. Prioritized Task Scheduing API: 优先级任务调度API, 提供了一种标准化方法来确定属于应用程序的所有任务的优先级,无论这些任务是在网站开发人员的代码中定义的,还是在第三方库和框架中定义的.

    // 优先任务调度
    const promise = scheduler.postTask(myTask)
    
    scheduler
      .postTask(() => "Task executing")
      // Promise resolved: log task result when promise resolves
      .then((taskResult) => console.log(`${taskResult}`))
      // Promise rejected: log AbortError or errors thrown by task
      .catch((error) => console.error(`Error: ${error}`));
      
     (async () => {
      try {
        const result = await scheduler.postTask(() => "Task executing");
        console.log(result);
      } catch (error) {
        // Log AbortError or error thrown in task function
        console.error(`Error: ${error}`);
      }
    })(); 
    
    scheduler
      .postTask(() => "Task executing", { priority: "user-blocking" })
      .then((taskResult) => console.log(`${taskResult}`)) // Log the task result
      .catch((error) => console.error(`Error: ${error}`)); // Log any errors
     
    
  24. Playment Request API: 支付请求接口, 为商家和支付者提供了统一的用户体验。它并非提供一种新的支付方式,而是让用户可以在原有的支付方式中进行选择,并使商家可以获悉用户的支付情况

  25. Performance API: 用于衡量Web应用性能的标准,

    <!doctype html>
    <html>
    <head></head>
    <body onload="init()">
      <img id="image0" src="https://www.w3.org/Icons/w3c_main.png" />
      <script>
        function init() {
          // see [[USER-TIMING-2]]
          performance.mark("startWork");
          doWork(); // Some developer code
          performance.mark("endWork");
          measurePerf();
        }
        function measurePerf() {
          performance
            .getEntries()
            .map(entry => JSON.stringify(entry, null, 2))
            .forEach(json => console.log(json));
        }
      </script>
      </body>
    </html>
    
    <!doctype html>
    <html>
    <head></head>
    <body>
    <img id="image0" src="https://www.w3.org/Icons/w3c_main.png" />
    <script>
    // Know when the entry types we would like to use are not supported.
    function detectSupport(entryTypes) {
      for (const entryType of entryTypes) {
        if (!PerformanceObserver.supportedEntryTypes.includes(entryType)) {
          // Indicate to client-side analytics that |entryType| is not supported.
        }
      }
    }
    detectSupport(["resource", "mark", "measure"]);
    const userTimingObserver = new PerformanceObserver(list => {
      list
        .getEntries()
        // Get the values we are interested in
        .map(({ name, entryType, startTime, duration }) => {
          const obj = {
            "Duration": duration,
            "Entry Type": entryType,
            "Name": name,
            "Start Time": startTime,
          };
          return JSON.stringify(obj, null, 2);
        })
        // Display them to the console.
        .forEach(console.log);
      // Disconnect after processing the events.
      userTimingObserver.disconnect();
    });
    // Subscribe to new events for User-Timing.
    userTimingObserver.observe({entryTypes: ["mark", "measure"]});
    const resourceObserver = new PerformanceObserver(list => {
      list
        .getEntries()
        // Get the values we are interested in
        .map(({ name, startTime, fetchStart, responseStart, responseEnd }) => {
          const obj = {
            "Name": name,
            "Start Time": startTime,
            "Fetch Start": fetchStart,
            "Response Start": responseStart,
            "Response End": responseEnd,
          };
          return JSON.stringify(obj, null, 2);
        })
        // Display them to the console.
        .forEach(console.log);
      // Disconnect after processing the events.
      resourceObserver.disconnect();
    });
    // Retrieve buffered events and subscribe to newer events for Resource Timing.
    resourceObserver.observe({type: "resource", buffered: true});
    </script>
    </body>
    </html>
    
  26. Pointer Events: 指针事件, 是一类可以为定点设备所触发的 DOM 事件, 一个指针事件,也同时包含了鼠标事件中所常见的属性。类似鼠标的其他设备,如触控笔和触摸屏幕

    // 注册一个事件处理器
    <html>
      <script>
        function over_handler(event) {}
        function enter_handler(event) {}
        function down_handler(event) {}
        function move_handler(event) {}
        function up_handler(event) {}
        function cancel_handler(event) {}
        function out_handler(event) {}
        function leave_handler(event) {}
        function gotcapture_handler(event) {}
        function lostcapture_handler(event) {}
    
        function init() {
          var el = document.getElementById("target");
          // Register pointer event handlers
          el.onpointerover = over_handler;
          el.onpointerenter = enter_handler;
          el.onpointerdown = down_handler;
          el.onpointermove = move_handler;
          el.onpointerup = up_handler;
          el.onpointercancel = cancel_handler;
          el.onpointerout = out_handler;
          el.onpointerleave = leave_handler;
          el.gotpointercapture = gotcapture_handler;
          el.lostpointercapture = lostcapture_handler;
        }
      </script>
      <body onload="init();">
        <div id="target">Touch me ...</div>
      </body>
    </html>
    // 事件属性
    <html>
      <script>
        var id = -1;
    
        function process_id(event) {
          // Process this event based on the event's identifier
        }
        function process_mouse(event) {
          // Process the mouse pointer event
        }
        function process_pen(event) {
          // Process the pen pointer event
        }
        function process_touch(event) {
          // Process the touch pointer event
        }
        function process_tilt(tiltX, tiltY) {
          // Tilt data handler
        }
        function process_pressure(pressure) {
          // Pressure handler
        }
        function process_non_primary(event) {
          // Pressure handler
        }
    
        function down_handler(ev) {
          // Calculate the touch point's contact area
          var area = ev.width * ev.height;
    
          // Compare cached id with this event's id and process accordingly
          if (id == ev.identifier) process_id(ev);
    
          // Call the appropriate pointer type handler
          switch (ev.pointerType) {
            case "mouse":
              process_mouse(ev);
              break;
            case "pen":
              process_pen(ev);
              break;
            case "touch":
              process_touch(ev);
              break;
            default:
              console.log("pointerType " + ev.pointerType + " is Not suported");
          }
    
          // Call the tilt handler
          if (ev.tiltX != 0 && ev.tiltY != 0) process_tilt(ev.tiltX, ev.tiltY);
    
          // Call the pressure handler
          process_pressure(ev.pressure);
    
          // If this event is not primary, call the non primary handler
          if (!ev.isPrimary) process_non_primary(evt);
        }
    
        function init() {
          var el = document.getElementById("target");
          // Register pointerdown handler
          el.onpointerdown = down_handler;
        }
      </script>
      <body onload="init();">
        <div id="target">Touch me ...</div>
      </body>
    </html>
    
  27. Pointer Lock API: 指针锁定(鼠标锁定),通过它可以访问原始的鼠标运动,把鼠标事件的目标锁定到一个单独的元素,这就消除了鼠标在一个单独的方向上到底可以移动多远这方面的限制,并从视图中删去光标。

    这个 API 对于需要大量的鼠标输入来控制运动,旋转物体,以及更改项目的应用程序来说非常有用。

    <button onclick="lockPointer();">锁住它!</button>
    <div id="pointer-lock-element"></div>
    <script>
    // 注意:截止本文撰写时,仅有 Mozilla 和 WebKit 支持指针锁定。
    
    // 我们将要使之全屏并指针锁定的元素。
    var elem;
    
    document.addEventListener("mousemove", function(e) {
      var movementX = e.movementX       ||
                      e.mozMovementX    ||
                      e.webkitMovementX ||
                      0,
          movementY = e.movementY       ||
                      e.mozMovementY    ||
                      e.webkitMovementY ||
                      0;
    
      // 打印鼠标移动的增量值。
      console.log("movementX=" + movementX, "movementY=" + movementY);
    }, false);
    
    function fullscreenChange() {
      if (document.webkitFullscreenElement === elem ||
          document.mozFullscreenElement === elem ||
          document.mozFullScreenElement === elem) { // 较旧的 API 大写 'S'.
        // 元素进入全屏模式了,现在我们可以请求指针锁定。
        elem.requestPointerLock = elem.requestPointerLock    ||
                                  elem.mozRequestPointerLock ||
                                  elem.webkitRequestPointerLock;
        elem.requestPointerLock();
      }
    }
    
    document.addEventListener('fullscreenchange', fullscreenChange, false);
    document.addEventListener('mozfullscreenchange', fullscreenChange, false);
    document.addEventListener('webkitfullscreenchange', fullscreenChange, false);
    
    function pointerLockChange() {
      if (document.mozPointerLockElement === elem ||
          document.webkitPointerLockElement === elem) {
        console.log("指针锁定成功了。");
      } else {
        console.log("指针锁定已丢失。");
      }
    }
    
    document.addEventListener('pointerlockchange', pointerLockChange, false);
    document.addEventListener('mozpointerlockchange', pointerLockChange, false);
    document.addEventListener('webkitpointerlockchange', pointerLockChange, false);
    
    function pointerLockError() {
      console.log("锁定指针时出错。");
    }
    
    document.addEventListener('pointerlockerror', pointerLockError, false);
    document.addEventListener('mozpointerlockerror', pointerLockError, false);
    document.addEventListener('webkitpointerlockerror', pointerLockError, false);
    
    function lockPointer() {
      elem = document.getElementById("pointer-lock-element");
      // 开始于使元素进入全屏模式。目前的实现
      // 要求元素在请求指针锁定前要处于全屏模式下
      // -- 这在以后可能会发生改变。
      elem.requestFullscreen = elem.requestFullscreen    ||
                               elem.mozRequestFullscreen ||
                               elem.mozRequestFullScreen || // 较旧的 API 把‘S’大写
                               elem.webkitRequestFullscreen;
      elem.requestFullscreen();
    }
    </script>
    
  28. Remote Playback API: 远程播放API: 扩展了 HTMLMediaElement,以启用对远程设备上播放的媒体的控制.远程播放设备是连接的设备,例如电视、投影仪或扬声器。该 API 考虑了通过 HDMI 或 DVI 连接的有线设备,以及无线设备,例如 Chromecast 或 AirPlay.该 API 使具有媒体元素(如视频或音频文件)的页面能够在连接的远程设备上启动和控制该媒体的播放。例如,在联网的电视上播放视频。

    <video id="videoElement" src="https://example.org/media.ext">
      <button id="deviceBtn" style="display: none;">Pick device</button>
    </video>
    <script>
    const deviceBtn = document.getElementById("deviceBtn");
    const videoElem = document.getElementById("videoElement");
    
    function availabilityCallback(available) {
      // Show or hide the device picker button depending on device availability.
      deviceBtn.style.display = available ? "inline" : "none";
    }
    
    videoElem.remote.watchAvailability(availabilityCallback).catch(() => {
      /* If the device cannot continuously watch available,
      show the button to allow the user to try to prompt for a connection.*/
      deviceBtn.style.display = "inline";
    });
    </script>
    
  29. Reporting API: 为 Web 应用程序提供了一种通用报告机制,用于以一致的方式根据各种平台功能(例如内容安全策略权限策略或功能弃用报告)提供报告.

    const options = {
      types: ["deprecation"],
      buffered: true,
    };
    
    const observer = new ReportingObserver((reports, observer) => {
      reportBtn.onclick = () => displayReports(reports);
    }, options);
    observer.observe();
    if (navigator.mozGetUserMedia) {
      navigator.mozGetUserMedia(constraints, success, failure);
    } else {
      navigator.getUserMedia(constraints, success, failure);
    }
    
  30. Resize Observer API: 提供了一种高性能的机制,通过该机制,代码可以监视元素的大小更改,并且每次大小更改时都会向观察者传递通知。它还使你能够轻松观察和响应元素内容或边框的大小变化,并以高效的方式做出响应

    // 
    const resizeObserver = new ResizeObserver((entries) => {
      const calcBorderRadius = (size1, size2) =>
        `${Math.min(100, size1 / 10 + size2 / 10)}px`;
    
      for (const entry of entries) {
        if (entry.borderBoxSize) {
          entry.target.style.borderRadius = calcBorderRadius(
            entry.borderBoxSize[0].inlineSize,
            entry.borderBoxSize[0].blockSize,
          );
        } else {
          entry.target.style.borderRadius = calcBorderRadius(
            entry.contentRect.width,
            entry.contentRect.height,
          );
        }
      }
    });
    
    resizeObserver.observe(document.querySelector("div"));
    
  31. Sensor APIs: 传感器API 它们在 web 平台中为各类传感器提供了一致的访问方式.

```js
// 功能检测
if (typeof Gyroscope === "function") {
  // run in circles...
}

if ("ProximitySensor" in window) {
  // watch out!
}

if (window.AmbientLightSensor) {
  // go dark...
}
// 许可和功能和策略
navigator.permissions.query({ name: 'accelerometer' })
.then(result => {
  if (result.state === 'denied') {
    console.log('Permission to use accelerometer sensor is denied.');
    return;
  }
  // Use the sensor.
}
// 启动并错误侦听
const sensor = new AbsoluteOrientationSensor();
sensor.start();
sensor.onerror = (event) => {
  if (event.error.name === "SecurityError")
    console.log("No permissions to use AbsoluteOrientationSensor.");
};
// 读数
let magSensor = new Magnetometer({ frequency: 60 });

magSensor.addEventListener("reading", (e) => {
  console.log("Magnetic field along the X-axis " + magSensor.x);
  console.log("Magnetic field along the Y-axis " + magSensor.y);
  console.log("Magnetic field along the Z-axis " + magSensor.z);
});
magSensor.addEventListener("error", (event) => {
  console.log(event.error.name, event.error.message);
});
magSensor.start();
```
  1. Speculation Rules API: 旨在提高未来导航的性能。它以文档 URL 而不是特定资源文件为目标,因此对于多页应用程序 (MPA) 而不是单页应用程序 (SPA) 有意义.推测规则 API 为广泛可用的 <linke ref="prefetch" 功能提供了替代方案,旨在取代仅限 Chrome 的已弃用的 <link ref="prerender" 功能。

    <script type="speculationrules">
      {
        "prerender": [
          {
            "source": "list",
            "urls": ["extra.html", "extra2.html"]
          }
        ],
        "prefetch": [
          {
            "source": "list",
            "urls": ["next.html", "next2.html"],
            "requires": ["anonymous-client-ip-when-cross-origin"],
            "referrer_policy": "no-referrer"
          }
        ]
      }
    </script>
    // 推测规则 功能检测
    if (
      HTMLScriptElement.supports &&
      HTMLScriptElement.supports("speculationrules")
    ) {
      const specScript = document.createElement("script");
      specScript.type = "speculationrules";
      const specRules = {
        prefetch: [
          {
            source: "list",
            urls: ["/next.html"],
          },
        ],
      };
      specScript.textContent = JSON.stringify(specRules);
      document.body.append(specScript);
    } else {
      const linkElem = document.createElement("link");
      linkElem.rel = "prefetch";
      linkElem.href = "/next.html";
      document.head.append(linkElem);
    }
    
    
  2. Screen Capture API: 屏幕捕捉API,对现有的媒体捕获和流 API 进行了补充,让用户选择一个屏幕或屏幕的一部分(如一个窗口)作为媒体流进行捕获。然后,该流可以被记录或通过网络与他人共享

```js
captureStream =
  await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
 <iframe allow="display-capture" src="/some-other-document.html"></iframe>
```
  1. Service Worker API: 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。这个 API 旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的资源。它还提供入口以推送通知和访问后台同步 API.Service worker 只能由 HTTPS 承载

  2. Storage API: Web 存储标准,the Storage Standard,定义了一个通用的、共享的存储系统,供所有 API 和技术使用,以存储各个网站的内容可访问数据.

    navigator.storage.estimate().then((estimate) => {
      // estimate.quota 是估得的配额
      // estimate.usage 是估得的使用量,单位为 byte 比特
    });
    
  3. Screen Wake Lock API: 屏幕唤醒锁定功能,提供了一种防止设备在应用程序需要继续运行时变暗或锁定屏幕的方法, 屏幕唤醒锁定 API 可防止屏幕关闭、变暗或锁定。它允许为可见(活动)文档提供基于平台的简单解决方案,以获取平台屏幕唤醒锁定,您可以在此处找到 GitHub 上的完整代码

    //特征检测
    if ("wakeLock" in navigator) {
      isSupported = true;
      statusElem.textContent = "Screen Wake Lock API supported!";
    } else {
      wakeButton.disabled = true;
      statusElem.textContent = "Wake lock is not supported by this browser.";
    }
    // 请求唤醒锁定
    let wakeLock = null;
    
    // create an async function to request a wake lock
    try {
      wakeLock = await navigator.wakeLock.request("screen");
      statusElem.textContent = "Wake Lock is active!";
    } catch (err) {
      // The Wake Lock request has failed - usually system related, such as battery.
      statusElem.textContent = `${err.name}, ${err.message}`;
    }
    // 释放唤醒锁
    wakeLock.release().then(() => {
      wakeLock = null;
    });
    // 侦听唤醒锁定释放
    wakeLock.addEventListener("release", () => {
      // the wake lock has been released
      statusElem.textContent = "Wake Lock has been released";
    });
    // 重新获取唤醒锁
    document.addEventListener("visibilitychange", async () => {
      if (wakeLock !== null && document.visibilityState === "visible") {
        wakeLock = await navigator.wakeLock.request("screen");
      }
    });
    
  4. Shared Storage API: 共享存储API,是一种客户端存储机制,可在保护隐私(即不依赖跟踪 Cookie)的同时实现未分区的跨站点数据访问.它旨在提供所需的数据存储、处理和共享功能,而无需跟踪和分析用户

```js
// 写入共享存储
// Randomly assigns a user to a group 0 or 1
function getExperimentGroup() {
  return Math.round(Math.random());
}

async function injectContent() {
  // Assign user to a random group (0 or 1) and store it in shared storage
  window.sharedStorage.set("ab-testing-group", getExperimentGroup(), {
    ignoreIfPresent: true,
  });
}
// 从共享存储读取和处理数据
// ab-testing-worklet.js
class SelectURLOperation {
  async run(urls, data) {
    // Read the user's experiment group from shared storage
    const experimentGroup = await this.sharedStorage.get("ab-testing-group");

    // Return the group number
    return experimentGroup;
  }
}

register("ab-testing", SelectURLOperation);
// 将模块添加到共享存储工作let
async function injectContent() {
  // Add the module to the shared storage worklet
  await window.sharedStorage.worklet.addModule("ab-testing-worklet.js");

  // Assign user to a random group (0 or 1) and store it in shared storage
  window.sharedStorage.set("ab-testing-group", getExperimentGroup(), {
    ignoreIfPresent: true,
  });
}
// 选择一个URL并将其加载到受保护的框架中
// Run the URL selection operation
const fencedFrameConfig = await window.sharedStorage.selectURL(
  "ab-testing",
  [
    { url: `https://your-server.example/content/default-content.html` },
    { url: `https://your-server.example/content/experiment-content-a.html` },
  ],
  {
    resolveToConfig: true,
  },
);
// 完整应用脚本
// Randomly assigns a user to a group 0 or 1
function getExperimentGroup() {
  return Math.round(Math.random());
}

async function injectContent() {
  // Add the module to the shared storage worklet
  await window.sharedStorage.worklet.addModule("ab-testing-worklet.js");

  // Assign user to a random group (0 or 1) and store it in shared storage
  window.sharedStorage.set("ab-testing-group", getExperimentGroup(), {
    ignoreIfPresent: true,
  });

  // Run the URL selection operation
  const fencedFrameConfig = await window.sharedStorage.selectURL(
    "ab-testing",
    [
      { url: `https://your-server.example/content/default-content.html` },
      { url: `https://your-server.example/content/experiment-content-a.html` },
    ],
    {
      resolveToConfig: true,
    },
  );

  // Render the chosen URL into a fenced frame
  document.getElementById("content-slot").config = fencedFrameConfig;
}

injectContent();

```
  1. Selection API: 选择API,使开发人员能够访问和操作用户选择的文档部分。

    Window.getSelection()Document.getSelection() 方法返回一个 Selection 对象,该对象表示用户选择的文档部分

  2. Touch Events: 触摸事件, 触摸事件接口是较为底层的 API,可为特定程序提供多点触控交互(比如双指手势)的支持。多点触控交互开始于一个手指(或触控笔)开始接触设备平面的时刻

    // 创建画布
    <canvas id="canvas" width="600" height="600" style="border:solid black 1px;">
      你的浏览器不支持 canvas 元素。
    </canvas>
    <br />
    日志:
    <pre id="log" style="border: 1px solid #ccc;"></pre>
    
    // 设置事件处理器
    window.onload = function startup() {
      const el = document.getElementsByTagName("canvas")[0];
      el.addEventListener("touchstart", handleStart, false);
      el.addEventListener("touchend", handleEnd, false);
      el.addEventListener("touchmove", handleMove, false);
      log("初始化成功。");
    };
    // 跟踪新触摸
    function handleStart(evt) {
      evt.preventDefault();
      console.log("触摸开始。");
      const el = document.getElementsByTagName("canvas")[0];
      const ctx = el.getContext("2d");
      const touches = evt.changedTouches;
    
      for (let i = 0; i < touches.length; i++) {
        console.log("开始第 " + i + " 个触摸 ...");
        ongoingTouches.push(copyTouch(touches[i]));
        const color = colorForTouch(touches[i]);
        ctx.beginPath();
        ctx.arc(touches[i].pageX, touches[i].pageY, 4, 0, 2 * Math.PI, false);
        // 在起点画一个圆。
        ctx.fillStyle = color;
        ctx.fill();
        console.log("第 " + i + " 个触摸已开始。");
      }
    }
    // 当触摸移动时绘制
    function handleMove(evt) {
      evt.preventDefault();
      const el = document.getElementsByTagName("canvas")[0];
      const ctx = el.getContext("2d");
      const touches = evt.changedTouches;
    
      for (let i = 0; i < touches.length; i++) {
        const color = colorForTouch(touches[i]);
        const idx = ongoingTouchIndexById(touches[i].identifier);
    
        if (idx >= 0) {
          log("继续第 " + idx + "个触摸。");
          ctx.beginPath();
          log(
            "ctx.moveTo(" +
              ongoingTouches[idx].pageX +
              ", " +
              ongoingTouches[idx].pageY +
              ");",
          );
          ctx.moveTo(ongoingTouches[idx].pageX, ongoingTouches[idx].pageY);
          log("ctx.lineTo(" + touches[i].pageX + ", " + touches[i].pageY + ");");
          ctx.lineTo(touches[i].pageX, touches[i].pageY);
          ctx.lineWidth = 4;
          ctx.strokeStyle = color;
          ctx.stroke();
    
          ongoingTouches.splice(idx, 1, copyTouch(touches[i])); // 切换触摸信息
          console.log(".");
        } else {
          log("无法确定下一个触摸点。");
        }
      }
    }
    // 触摸结束处理
    function handleEnd(evt) {
      evt.preventDefault();
      log("触摸结束。");
      const el = document.getElementsByTagName("canvas")[0];
      const ctx = el.getContext("2d");
      touches = evt.changedTouches;
    
      for (let i = 0; i < touches.length; i++) {
        const color = colorForTouch(touches[i]);
        const idx = ongoingTouchIndexById(touches[i].identifier);
    
        if (idx >= 0) {
          ctx.lineWidth = 4;
          ctx.fillStyle = color;
          ctx.beginPath();
          ctx.moveTo(ongoingTouches[idx].pageX, ongoingTouches[idx].pageY);
          ctx.lineTo(touches[i].pageX, touches[i].pageY);
          ctx.fillRect(touches[i].pageX - 4, touches[i].pageY - 4, 8, 8);
          // 在终点画一个正方形
          ongoingTouches.splice(idx, 1); // 用完后移除
        } else {
          log("无法确定要结束哪个触摸点。");
        }
      }
    }
    // 触摸取消处理
    function handleCancel(evt) {
      evt.preventDefault();
      console.log("触摸取消。");
      const touches = evt.changedTouches;
    
      for (let i = 0; i < touches.length; i++) {
        const idx = ongoingTouchIndexById(touches[i].identifier);
        ongoingTouches.splice(idx, 1); // 用完后移除
      }
    }
    // 为每个触摸点选择一个颜色
    function colorForTouch(touch) {
      let r = touch.identifier % 16;
      let g = Math.floor(touch.identifier / 3) % 16;
      let b = Math.floor(touch.identifier / 7) % 16;
      r = r.toString(16); // 转换为十六进制字符串
      g = g.toString(16); // 转换为十六进制字符串
      b = b.toString(16); // 转换为十六进制字符串
      const color = "#" + r + g + b;
      log("identifier " + touch.identifier + " 触摸的颜色为:" + color);
      return color;
    }
    // 拷贝触摸对象
    function copyTouch(touch) {
      return {
        identifier: touch.identifier,
        pageX: touch.pageX,
        pageY: touch.pageY,
      };
    }
    // 查找触摸点
    function ongoingTouchIndexById(idToFind) {
      for (let i = 0; i < ongoingTouches.length; i++) {
        const id = ongoingTouches[i].identifier;
    
        if (id == idToFind) {
          return i;
        }
      }
      return -1; // 未找到
    }
    // 显示后台操作记录
    function log(msg) {
      const p = document.getElementById('log');
      p.innerHTML = msg + "\n" + p.innerHTML;
    }
    // 处理点击
    function onTouch(evt) {
      evt.preventDefault();
      if (
        evt.touches.length > 1 ||
        (evt.type == "touchend" && evt.touches.length > 0)
      )
        return;
    
      const newEvt = document.createEvent("MouseEvents");
      let type = null;
      let touch = null;
    
      switch (evt.type) {
        case "touchstart":
          type = "mousedown";
          touch = evt.changedTouches[0];
          break;
        case "touchmove":
          type = "mousemove";
          touch = evt.changedTouches[0];
          break;
        case "touchend":
          type = "mouseup";
          touch = evt.changedTouches[0];
          break;
      }
    
      newEvt.initMouseEvent(
        type,
        true,
        true,
        evt.originalTarget.ownerDocument.defaultView,
        0,
        touch.screenX,
        touch.screenY,
        touch.clientX,
        touch.clientY,
        evt.ctrlKey,
        evt.altKey,
        evt.shiftKey,
        evt.metaKey,
        0,
        null,
      );
      evt.originalTarget.dispatchEvent(newEvt);
    }
    
  3. URLPattern API: 定义了用于创建 URL 模式的语法 匹配器。这些模式可以与 URL 或单个 URL 进行匹配 组件。URL 模式 API 由 URLPattern 接口使用

```
// 固定文本和捕获组
// A pattern matching some fixed text
const pattern = new URLPattern({ pathname: "/books" });
console.log(pattern.test("https://example.com/books")); // true
console.log(pattern.exec("https://example.com/books").pathname.groups); // {}
// A pattern matching with a named group
const pattern = new URLPattern({ pathname: "/books/:id" });
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.exec("https://example.com/books/123").pathname.groups); // { id: '123' }

// 正则表达式匹配器
const pattern = new URLPattern("/books/:id(\\d+)", "https://example.com");
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books/abc")); // false
console.log(pattern.test("https://example.com/books/")); // false
// 正则表达式匹配器限制

// 仅当在 URLPattern 的协议部分的开头使用时,才会匹配,如果使用,则为冗余
// with `^` in pathname
const pattern = new URLPattern({ pathname: "(^b)" });
console.log(pattern.test("https://example.com/ba")); // false
console.log(pattern.test("https://example.com/xa")); // false
// with `^` in protocol
const pattern = new URLPattern({ protocol: "(^https?)" });
console.log(pattern.test("https://example.com/index.html")); // true
console.log(pattern.test("xhttps://example.com/index.html")); // false
// without `^` in protocol
const pattern = new URLPattern({ protocol: "(https?)" });
console.log(pattern.test("https://example.com/index.html")); // true
console.log(pattern.test("xhttps://example.com/index.html")); // false
// 仅当在 URLPattern 的哈希部分的末尾使用时,Ends with 才会匹配,如果使用,则为冗余
// with `$` in pathname
const pattern = new URLPattern({ pathname: "(path$)" });
console.log(pattern.test("https://example.com/path")); // false
console.log(pattern.test("https://example.com/other")); // false
// with `$` in hash
const pattern = new URLPattern({ hash: "(hash$)" });
console.log(pattern.test("https://example.com/#hash")); // true
console.log(pattern.test("xhttps://example.com/#otherhash")); // false
// without `$` in hash
const pattern = new URLPattern({ hash: "(hash)" });
console.log(pattern.test("https://example.com/#hash")); // true
console.log(pattern.test("xhttps://example.com/#otherhash")); // false
// Lookahead 和 lookbehind 永远不会匹配 URLPattern 的任何部分。
// lookahead
const pattern = new URLPattern({ pathname: "(a(?=b))" });
console.log(pattern.test("https://example.com/ab")); // false
console.log(pattern.test("https://example.com/ax")); // false
// negative-lookahead
const pattern = new URLPattern({ pathname: "(a(?!b))" });
console.log(pattern.test("https://example.com/ab")); // false
console.log(pattern.test("https://example.com/ax")); // false
// lookbehind
const pattern = new URLPattern({ pathname: "((?<=b)a)" });
console.log(pattern.test("https://example.com/ba")); // false
console.log(pattern.test("https://example.com/xa")); // false
// negative-lookbehind
const pattern = new URLPattern({ pathname: "((?<!b)a)" });
console.log(pattern.test("https://example.com/ba")); // false
console.log(pattern.test("https://example.com/xa")); // false
// 括号需要在 URLPattern 中的范围表达式中转义,即使它们在 RegExp 中没有。
new URLPattern({ pathname: "([()])" }); // throws
new URLPattern({ pathname: "([\\(\\)])" }); // ok

new RegExp("[()]"); // ok
new RegExp("[\\(\\)]"); // ok

// 未命名和命名组
// A named group
const pattern = new URLPattern("/books/:id(\\d+)", "https://example.com");
console.log(pattern.exec("https://example.com/books/123").pathname.groups); // { id: '123' }
// An unnamed group
const pattern = new URLPattern("/books/(\\d+)", "https://example.com");
console.log(pattern.exec("https://example.com/books/123").pathname.groups); // { '0': '123' }

// 组修饰符
// An optional group
const pattern = new URLPattern("/books/:id?", "https://example.com");
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books")); // true
console.log(pattern.test("https://example.com/books/")); // false
console.log(pattern.test("https://example.com/books/123/456")); // false
console.log(pattern.test("https://example.com/books/123/456/789")); // false
// A repeating group with a minimum of one
const pattern = new URLPattern("/books/:id+", "https://example.com");
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books")); // false
console.log(pattern.test("https://example.com/books/")); // false
console.log(pattern.test("https://example.com/books/123/456")); // true
console.log(pattern.test("https://example.com/books/123/456/789")); // true
// A repeating group with a minimum of zero
const pattern = new URLPattern("/books/:id*", "https://example.com");
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books")); // true
console.log(pattern.test("https://example.com/books/")); // false
console.log(pattern.test("https://example.com/books/123/456")); // true
console.log(pattern.test("https://example.com/books/123/456/789")); // true
// 组分隔符
// A group delimiter with a ? (optional) modifier
const pattern = new URLPattern("/book{s}?", "https://example.com");
console.log(pattern.test("https://example.com/books")); // true
console.log(pattern.test("https://example.com/book")); // true
console.log(pattern.exec("https://example.com/books").pathname.groups); // {}
// A group delimiter without a modifier
const pattern = new URLPattern("/book{s}", "https://example.com");
console.log(pattern.pathname); // /books
console.log(pattern.test("https://example.com/books")); // true
console.log(pattern.test("https://example.com/book")); // false
// A group delimiter containing a capturing group
const pattern = new URLPattern({ pathname: "/blog/:id(\\d+){-:title}?" });
console.log(pattern.test("https://example.com/blog/123-my-blog")); // true
console.log(pattern.test("https://example.com/blog/123")); // true
console.log(pattern.test("https://example.com/blog/my-blog")); // false
// 路劲名中的自动组前缀
// A pattern with an optional group, preceded by a slash
const pattern = new URLPattern("/books/:id?", "https://example.com");
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books")); // true
console.log(pattern.test("https://example.com/books/")); // false
// A pattern with a repeating group, preceded by a slash
const pattern = new URLPattern("/books/:id+", "https://example.com");
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books/123/456")); // true
console.log(pattern.test("https://example.com/books/123/")); // false
console.log(pattern.test("https://example.com/books/123/456/")); // false
// Segment prefixing does not occur outside of pathname patterns
const pattern = new URLPattern({ hash: "/books/:id?" });
console.log(pattern.test("https://example.com#/books/123")); // true
console.log(pattern.test("https://example.com#/books")); // false
console.log(pattern.test("https://example.com#/books/")); // true
// Disabling segment prefixing for a group using a group delimiter
const pattern = new URLPattern({ pathname: "/books/{:id}?" });
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books")); // false
console.log(pattern.test("https://example.com/books/")); // true
// 通配符令牌
// A wildcard at the end of a pattern
const pattern = new URLPattern("/books/*", "https://example.com");
console.log(pattern.test("https://example.com/books/123")); // true
console.log(pattern.test("https://example.com/books")); // false
console.log(pattern.test("https://example.com/books/")); // true
console.log(pattern.test("https://example.com/books/123/456")); // true
// A wildcard in the middle of a pattern
const pattern = new URLPattern("/*.png", "https://example.com");
console.log(pattern.test("https://example.com/image.png")); // true
console.log(pattern.test("https://example.com/image.png/123")); // false
console.log(pattern.test("https://example.com/folder/image.png")); // true
console.log(pattern.test("https://example.com/.png")); // true
// 模式归一化
// 区分大小写
// Case-sensitive matching by default
const pattern = new URLPattern("https://example.com/2022/feb/*");
console.log(pattern.test("https://example.com/2022/feb/xc44rsz")); // true
console.log(pattern.test("https://example.com/2022/Feb/xc44rsz")); // false
// Case-insensitive matching
const pattern = new URLPattern("https://example.com/2022/feb/*", {
  ignoreCase: true,
});
console.log(pattern.test("https://example.com/2022/feb/xc44rsz")); // true
console.log(pattern.test("https://example.com/2022/Feb/xc44rsz")); // true
// 筛选特定的URL组件
// Construct a URLPattern that matches a specific domain and its subdomains.
// All other URL components default to the wildcard `*` pattern.
const pattern = new URLPattern({
  hostname: "{*.}?example.com",
});

console.log(pattern.hostname); // '{*.}?example.com'

console.log(pattern.protocol); // '*'
console.log(pattern.username); // '*'
console.log(pattern.password); // '*'
console.log(pattern.pathname); // '*'
console.log(pattern.search); // '*'
console.log(pattern.hash); // '*'

console.log(pattern.test("https://example.com/foo/bar")); // true

console.log(pattern.test({ hostname: "cdn.example.com" })); // true

console.log(pattern.test("custom-protocol://example.com/other/path?q=1")); // false

// Prints `false` because the hostname component does not match
console.log(pattern.test("https://cdn-example.com/foo/bar"));

// 从完整的URL字符串构造URLPattern
// Construct a URLPattern that matches URLs to CDN servers loading jpg images.
// URL components not explicitly specified, like search and hash here, result
// in the empty string similar to the URL() constructor.
const pattern = new URLPattern("https://cdn-*.example.com/*.jpg");

console.log(pattern.protocol); // 'https'

console.log(pattern.hostname); // 'cdn-*.example.com'

console.log(pattern.pathname); // '/*.jpg'

console.log(pattern.username); // ''
console.log(pattern.password); // ''
console.log(pattern.search); // ''
console.log(pattern.hash); // ''

// Prints `true`
console.log(
  pattern.test("https://cdn-1234.example.com/product/assets/hero.jpg"),
);

// Prints `false` because the search component does not match
console.log(
  pattern.test("https://cdn-1234.example.com/product/assets/hero.jpg?q=1"),
);

// 构造具有不明确URL字符串的URLPattern
// Throws because this is interpreted as a single relative pathname pattern
// with a ":foo" named group and there is no base URL.
const pattern = new URLPattern("data:foo*");
// 转义字符以消除 URLPattern 构造函数字符串的歧义
// Constructs a URLPattern treating the `:` as the protocol suffix.
const pattern = new URLPattern("data\\:foo*");

console.log(pattern.protocol); // 'data'

console.log(pattern.pathname); // 'foo*'

console.log(pattern.username); // ''
console.log(pattern.password); // ''
console.log(pattern.hostname); // ''
console.log(pattern.port); // ''
console.log(pattern.search); // ''
console.log(pattern.hash); // ''

console.log(pattern.test("data:foobar")); // true

// 对 test() 和 exec() 使用基本 URL
const pattern = new URLPattern({ hostname: "example.com", pathname: "/foo/*" });

// Prints `true` as the hostname based in the dictionary `baseURL` property
// matches.
console.log(
  pattern.test({
    pathname: "/foo/bar",
    baseURL: "https://example.com/baz",
  }),
);

// Prints `true` as the hostname in the second argument base URL matches.
console.log(pattern.test("/foo/bar", "https://example.com/baz"));

// Throws because the second argument cannot be passed with a dictionary input.
try {
  pattern.test({ pathname: "/foo/bar" }, "https://example.com/baz");
} catch (e) {}

// The `exec()` method takes the same arguments as `test()`.
const result = pattern.exec("/foo/bar", "https://example.com/baz");

console.log(result.pathname.input); // '/foo/bar'

console.log(result.pathname.groups[0]); // 'bar'

console.log(result.hostname.input); // 'example.com'

// 在 URLPattern 构造函数中使用基 URL
const pattern1 = new URLPattern({
  pathname: "/foo/*",
  baseURL: "https://example.com",
});

console.log(pattern1.protocol); // 'https'
console.log(pattern1.hostname); // 'example.com'
console.log(pattern1.pathname); // '/foo/*'

console.log(pattern1.username); // ''
console.log(pattern1.password); // ''
console.log(pattern1.port); // ''
console.log(pattern1.search); // ''
console.log(pattern1.hash); // ''

// Equivalent to pattern1
const pattern2 = new URLPattern("/foo/*", "https://example.com");

// Throws because a relative constructor string must have a base URL to resolve
// against.
try {
  const pattern3 = new URLPattern("/foo/*");
} catch (e) {}

// 访问匹配的组值
const pattern = new URLPattern({ hostname: "*.example.com" });
const result = pattern.exec({ hostname: "cdn.example.com" });

console.log(result.hostname.groups[0]); // 'cdn'

console.log(result.hostname.input); // 'cdn.example.com'

console.log(result.inputs); // [{ hostname: 'cdn.example.com' }]

// 使用自定义名称访问匹配的组值

// Construct a URLPattern using matching groups with custom names. These
// names can then be later used to access the matched values in the result
// object.
const pattern = new URLPattern({ pathname: "/:product/:user/:action" });
const result = pattern.exec({ pathname: "/store/wanderview/view" });

console.log(result.pathname.groups.product); // 'store'
console.log(result.pathname.groups.user); // 'wanderview'
console.log(result.pathname.groups.action); // 'view'

console.log(result.pathname.input); // '/store/wanderview/view'

console.log(result.inputs); // [{ pathname: '/store/wanderview/view' }]

// 自定义正则表达式组
const pattern = new URLPattern({ pathname: "/(foo|bar)" });

console.log(pattern.test({ pathname: "/foo" })); // true
console.log(pattern.test({ pathname: "/bar" })); // true
console.log(pattern.test({ pathname: "/baz" })); // false

const result = pattern.exec({ pathname: "/foo" });

console.log(result.pathname.groups[0]); // 'foo'

// 具有自定义正则表达式的命名组
const pattern = new URLPattern({ pathname: "/:type(foo|bar)" });
const result = pattern.exec({ pathname: "/foo" });

console.log(result.pathname.groups.type); // 'foo'

// 将匹配组设为可选
const pattern = new URLPattern({ pathname: "/product/(index.html)?" });

console.log(pattern.test({ pathname: "/product/index.html" })); // true
console.log(pattern.test({ pathname: "/product" })); // true

const pattern2 = new URLPattern({ pathname: "/product/:action?" });

console.log(pattern2.test({ pathname: "/product/view" })); // true
console.log(pattern2.test({ pathname: "/product" })); // true

// Wildcards can be made optional as well. This may not seem to make sense
// since they already match the empty string, but it also makes the prefix
// `/` optional in a pathname pattern.
const pattern3 = new URLPattern({ pathname: "/product/*?" });

console.log(pattern3.test({ pathname: "/product/wanderview/view" })); // true
console.log(pattern3.test({ pathname: "/product" })); // true
console.log(pattern3.test({ pathname: "/product/" })); // true

// 使匹配组重复
const pattern = new URLPattern({ pathname: "/product/:action+" });
const result = pattern.exec({ pathname: "/product/do/some/thing/cool" });

result.pathname.groups.action; // 'do/some/thing/cool'

console.log(pattern.test({ pathname: "/product" })); // false

// 使匹配组成为可选和重复

const pattern = new URLPattern({ pathname: "/product/:action*" });
const result = pattern.exec({ pathname: "/product/do/some/thing/cool" });

console.log(result.pathname.groups.action); // 'do/some/thing/cool'

console.log(pattern.test({ pathname: "/product" })); // true

// 对可选或重复修饰符使用自定义前缀或后缀

const pattern = new URLPattern({ hostname: "{:subdomain.}*example.com" });

console.log(pattern.test({ hostname: "example.com" })); // true
console.log(pattern.test({ hostname: "foo.bar.example.com" })); // true
console.log(pattern.test({ hostname: ".example.com" })); // false

const result = pattern.exec({ hostname: "foo.bar.example.com" });

console.log(result.hostname.groups.subdomain); // 'foo.bar'

// 使文本可选或重复,而没有匹配的组

const pattern = new URLPattern({ pathname: "/product{/}?" });

console.log(pattern.test({ pathname: "/product" })); // true
console.log(pattern.test({ pathname: "/product/" })); // true

const result = pattern.exec({ pathname: "/product/" });

console.log(result.pathname.groups); // {}

// 一次使用多个组件和功能
const pattern = new URLPattern({
  protocol: "http{s}?",
  username: ":user?",
  password: ":pass?",
  hostname: "{:subdomain.}*example.com",
  pathname: "/product/:action*",
});

const result = pattern.exec(
  "http://foo:bar@sub.example.com/product/view?q=12345",
);

console.log(result.username.groups.user); // 'foo'
console.log(result.password.groups.pass); // 'bar'
console.log(result.hostname.groups.subdomain); // 'sub'
console.log(result.pathname.groups.action); // 'view'

```
  1. URL API: 是一个 URL 标准的组件,它定义了有效的Uniform Resource Locator和访问、操作 URL 的 API。URL 标准还定义了像域名、主机和 IP 地址等概念,并尝试以标准的方式去描述用于以键/值对的形式提交 web 表单内容的遗留application/x-www-form-urlencoded MIME type .

    // 使用URL组件
    let addr = new URL("https://developer.mozilla.org/zh-CN/docs/Web/API/URL_API");
    let host = addr.host;
    let path = addr.pathname;
    
    // 修改URL
    let myUsername = "someguy";
    let addr = new URL("https://mysite.com/login");
    addr.username = myUsername;
    
    // 查询
    let addr = new URL("https://mysite.com/login?user=someguy&page=news");
    try {
      loginUser(addr.searchParams.get("user"));
      gotoPage(addr.searchParams.get("page"));
    } catch(err) {
      showErrorMessage(err);
    }
    
    // 完全例子
    function fillTableWithParameters(tbl) {
      let url = new URL(document.location.href);
      url.searchParams.sort();
      let keys = url.searchParams.keys();
    
      for (let key of keys) {
        let val = url.searchParams.get(key);
        let row = document.createElement("tr");
        let cell = document.createElement("td");
        cell.innerText = key;
        row.appendChild(cell);
        cell = document.createElement("td");
        cell.innerText = val;
        row.appendChild(cell);
        tbl.appendChild(row);
      }
    }
    
  2. Vibration API: 振动模式API,大多数现代移动设备包括振动硬件,其允许软件代码通过使设备摇动来向用户提供物理反馈。Vibration API 为 Web 应用程序提供访问此硬件(如果存在)的功能,如果设备不支持此功能,则不会执行任何操作。

    // 一个单次振动, 使设备振动 200 ms.
    window.navigator.vibrate(200);
    window.navigator.vibrate([200]);
    
    // 振动模式, 使设备振动 200 ms,然后暂停 100 ms,然后再次振动设备 200 ms。
    window.navigator.vibrate([200, 100, 200]);
    // 停止振动
    window.navigator.vibrate(0);
    // 持续振动
    var vibrateInterval;
    
    // Starts vibration at passed in level
    function startVibrate(duration) {
      navigator.vibrate(duration);
    }
    
    // Stops vibration
    function stopVibrate() {
      // Clear interval and stop persistent vibrating
      if (vibrateInterval) clearInterval(vibrateInterval);
      navigator.vibrate(0);
    }
    
    // Start persistent vibration at given duration and interval
    // Assumes a number value is given
    function startPeristentVibrate(duration, interval) {
      vibrateInterval = setInterval(function () {
        startVibrate(duration);
      }, interval);
    }
    
    
  3. VirtualKeyboard API, 虚拟键盘API, 使开发人员能够在平板电脑、移动电话或其他硬件键盘不可用的设备上出现和消失屏幕虚拟键盘时控制其应用程序的布局。

Web 浏览器通常通过调整视口高度并在聚焦时滚动到输入字段来自行处理虚拟键盘。

```js
// 使用 JavaScript 检测虚拟键盘几何图形
if ("virtualKeyboard" in navigator) {
  navigator.virtualKeyboard.overlaysContent = true;

  navigator.virtualKeyboard.addEventListener("geometrychange", (event) => {
    const { x, y, width, height } = event.target.boundingRect;
  });
}
```

```html
// 使用 CSS 环境变量检测虚拟键盘几何图形
<style>
  body {
    display: grid;
    margin: 0;
    height: 100vh;
    grid-template:
      "messages" 1fr
      "input" auto
      "keyboard" env(keyboard-inset-height, 0px);
  }
</style>
<ul id="messages"></ul>
<input type="text" />
<script>
  if ("virtualKeyboard" in navigator) {
    navigator.virtualKeyboard.overlaysContent = true;
  }
</script>
// 控制 contenteditable 元素上的虚拟键盘
<div contenteditable virtualkeyboardpolicy="manual" id="editor"></div>
<script>
  if ("virtualKeyboard" in navigator) {
    navigator.virtualKeyboard.overlaysContent = true;

    const editor = document.getElementById("editor");
    editor.addEventListener("dblclick", () => {
      navigator.virtualKeyboard.show();
    });
  }
</script>
```
  1. View Transitions API: 提供了一种机制,可以在更新 DOM 内容的同时,轻松地创建不同 DOM 状态之间的动画过渡。同时还可以在单个步骤中更新 DOM 内容.视图过渡是一种流行的设计选择,可以减少用户认知负荷,帮助他们保持上下文,并减少他们在应用程序的状态或视图之间移动时感知的加载延迟。

    // 创建基本视图过渡
    function updateView(event) {
      // 处理在 <a> 或 <img> 上触发事件的差异
      const targetIdentifier = event.target.firstChild || event.target;
    
      const displayNewImage = () => {
        const mainSrc = `${targetIdentifier.src.split("_th.jpg")[0]}.jpg`;
        galleryImg.src = mainSrc;
        galleryCaption.textContent = targetIdentifier.alt;
      };
    
      // 浏览器不支持 View Transitions 时的回退方案:
      if (!document.startViewTransition) {
        displayNewImage();
        return;
      }
    
      // 开始一次视图过渡:
      const transition = document.startViewTransition(() => displayNewImage());
    }
    
    figcaption {
      view-transition-name: figure-caption;
    }
    
    // 自定义动画
    // 调整动画速度
    ::view-transition-old(root),
    ::view-transition-new(root) {
      animation-duration: 0.5s;
    }
    @keyframes grow-x {
      from {
        transform: scaleX(0);
      }
      to {
        transform: scaleX(1);
      }
    }
    
    @keyframes shrink-x {
      from {
        transform: scaleX(1);
      }
      to {
        transform: scaleX(0);
      }
    }
    
    ::view-transition-old(figure-caption),
    ::view-transition-new(figure-caption) {
      height: auto;
      right: 0;
      left: auto;
      transform-origin: right center;
    }
    
    ::view-transition-old(figure-caption) {
      animation: 0.25s linear both shrink-x;
    }
    
    ::view-transition-new(figure-caption) {
      animation: 0.25s 0.25s linear both grow-x;
    }
    // 另一种比上面更简单且产生更好结果的过渡选项。我们最终的 <figcaption> 视图过渡看起来像这样
    figcaption {
      view-transition-name: figure-caption;
    }
    
    ::view-transition-old(figure-caption),
    ::view-transition-new(figure-caption) {
      height: 100%;
    }
    
    // 使用Javsscript控制动画
    // 保存最后一次点击事件
    let lastClick;
    addEventListener("click", (event) => (lastClick = event));
    
    function spaNavigate(data) {
      // 为不支持此 API 的浏览器提供回退方案:
      if (!document.startViewTransition) {
        updateTheDOMSomehow(data);
        return;
      }
    
      // 获取点击位置,或者回退到屏幕中间
      const x = lastClick?.clientX ?? innerWidth / 2;
      const y = lastClick?.clientY ?? innerHeight / 2;
      // 获取到最远角的距离
      const endRadius = Math.hypot(
        Math.max(x, innerWidth - x),
        Math.max(y, innerHeight - y),
      );
    
      // 开始一次视图过渡:
      const transition = document.startViewTransition(() => {
        updateTheDOMSomehow(data);
      });
    
      // 等待伪元素创建完成:
      transition.ready.then(() => {
        // 新视图的根元素动画
        document.documentElement.animate(
          {
            clipPath: [
              `circle(0 at ${x}px ${y}px)`,
              `circle(${endRadius}px at ${x}px ${y}px)`,
            ],
          },
          {
            duration: 500,
            easing: "ease-in",
            // 指定要附加动画的伪元素
            pseudoElement: "::view-transition-new(root)",
          },
        );
      });
    }
    // css
    ::view-transition-image-pair(root) {
      isolation: auto;
    }
    
    ::view-transition-old(root),
    ::view-transition-new(root) {
      animation: none;
      mix-blend-mode: normal;
      display: block;
    }
    
  2. Visual Viewport: 可视化视口API, 提供了一种显式机制,用于查询和修改窗口的可视视口的属性。可视视口是屏幕的可视部分,不包括屏幕键盘、捏合缩放区域之外的区域或任何其他不随页面尺寸缩放的屏幕项目。

    let pendingUpdate = false;
    
    function viewportHandler(event) {
      if (pendingUpdate) return;
      pendingUpdate = true;
    
      requestAnimationFrame(() => {
        pendingUpdate = false;
        const layoutViewport = document.getElementById("layoutViewport");
    
        // Since the bar is position: fixed we need to offset it by the
        // visual viewport's offset from the layout viewport origin.
        const viewport = event.target;
        const offsetLeft = viewport.offsetLeft;
        const offsetTop =
          viewport.height -
          layoutViewport.getBoundingClientRect().height +
          viewport.offsetTop;
    
        // You could also do this by setting style.left and style.top if you
        // use width: 100% instead.
        bottomBar.style.transform = `translate(${offsetLeft}px, ${offsetTop}px) scale(${
          1 / viewport.scale
        })`;
      });
    }
    
    window.visualViewport.addEventListener("scroll", viewportHandler);
    window.visualViewport.addEventListener("resize", viewportHandler);
    
  3. Web Share API: web共享,用于将文本、链接、文件和其他内容共享到用户选择的任意共享目标

    const shareData = {
      title: "MDN",
      text: "Learn web development on MDN!",
      url: "https://developer.mozilla.org",
    };
    
    const btn = document.querySelector("button");
    const resultPara = document.querySelector(".result");
    
    // Share must be triggered by "user activation"
    btn.addEventListener("click", async () => {
      try {
        await navigator.share(shareData);
        resultPara.textContent = "MDN shared successfully";
      } catch (err) {
        resultPara.textContent = `Error: ${err}`;
      }
    });
    
  4. Web Audio API: 提供了在 Web 上控制音频的一个非常有效通用的系统,允许开发者来自选音频源,对音频添加特效,使音频可视化,添加空间效果(如平移),等等

    var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); // define audio context
    // Webkit/blink browsers need prefix, Safari won't work without window.
    
    var voiceSelect = document.getElementById("voice"); // select box for selecting voice effect options
    var visualSelect = document.getElementById("visual"); // select box for selecting audio visualization options
    var mute = document.querySelector(".mute"); // mute button
    var drawVisual; // requestAnimationFrame
    
    var analyser = audioCtx.createAnalyser();
    var distortion = audioCtx.createWaveShaper();
    var gainNode = audioCtx.createGain();
    var biquadFilter = audioCtx.createBiquadFilter();
    
    function makeDistortionCurve(amount) {
      // function to make curve shape for distortion/wave shaper node to use
      var k = typeof amount === "number" ? amount : 50,
        n_samples = 44100,
        curve = new Float32Array(n_samples),
        deg = Math.PI / 180,
        i = 0,
        x;
      for (; i < n_samples; ++i) {
        x = (i * 2) / n_samples - 1;
        curve[i] = ((3 + k) * x * 20 * deg) / (Math.PI + k * Math.abs(x));
      }
      return curve;
    }
    
    navigator.getUserMedia(
      // constraints - only audio needed for this app
      {
        audio: true,
      },
    
      // Success callback
      function (stream) {
        source = audioCtx.createMediaStreamSource(stream);
        source.connect(analyser);
        analyser.connect(distortion);
        distortion.connect(biquadFilter);
        biquadFilter.connect(gainNode);
        gainNode.connect(audioCtx.destination); // connecting the different audio graph nodes together
    
        visualize(stream);
        voiceChange();
      },
    
      // Error callback
      function (err) {
        console.log("The following gUM error occured: " + err);
      },
    );
    
    function visualize(stream) {
      WIDTH = canvas.width;
      HEIGHT = canvas.height;
    
      var visualSetting = visualSelect.value;
      console.log(visualSetting);
    
      if (visualSetting == "sinewave") {
        analyser.fftSize = 2048;
        var bufferLength = analyser.frequencyBinCount; // half the FFT value
        var dataArray = new Uint8Array(bufferLength); // create an array to store the data
    
        canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
    
        function draw() {
          drawVisual = requestAnimationFrame(draw);
    
          analyser.getByteTimeDomainData(dataArray); // get waveform data and put it into the array created above
    
          canvasCtx.fillStyle = "rgb(200, 200, 200)"; // draw wave with canvas
          canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
    
          canvasCtx.lineWidth = 2;
          canvasCtx.strokeStyle = "rgb(0, 0, 0)";
    
          canvasCtx.beginPath();
    
          var sliceWidth = (WIDTH * 1.0) / bufferLength;
          var x = 0;
    
          for (var i = 0; i < bufferLength; i++) {
            var v = dataArray[i] / 128.0;
            var y = (v * HEIGHT) / 2;
    
            if (i === 0) {
              canvasCtx.moveTo(x, y);
            } else {
              canvasCtx.lineTo(x, y);
            }
    
            x += sliceWidth;
          }
    
          canvasCtx.lineTo(canvas.width, canvas.height / 2);
          canvasCtx.stroke();
        }
    
        draw();
      } else if (visualSetting == "off") {
        canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
        canvasCtx.fillStyle = "red";
        canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
      }
    }
    
    function voiceChange() {
      distortion.curve = new Float32Array();
      biquadFilter.gain.value = 0; // reset the effects each time the voiceChange function is run
    
      var voiceSetting = voiceSelect.value;
      console.log(voiceSetting);
    
      if (voiceSetting == "distortion") {
        distortion.curve = makeDistortionCurve(400); // apply distortion to sound using waveshaper node
      } else if (voiceSetting == "biquad") {
        biquadFilter.type = "lowshelf";
        biquadFilter.frequency.value = 1000;
        biquadFilter.gain.value = 25; // apply lowshelf filter to sounds using biquad
      } else if (voiceSetting == "off") {
        console.log("Voice settings turned off"); // do nothing, as off option was chosen
      }
    }
    
    // event listeners to change visualize and voice settings
    
    visualSelect.onchange = function () {
      window.cancelAnimationFrame(drawVisual);
      visualize(stream);
    };
    
    voiceSelect.onchange = function () {
      voiceChange();
    };
    
    mute.onclick = voiceMute;
    
    function voiceMute() {
      // toggle to mute and unmute sound
      if (mute.id == "") {
        gainNode.gain.value = 0; // gain set to 0 to mute sound
        mute.id = "activated";
        mute.innerHTML = "Unmute";
      } else {
        gainNode.gain.value = 1; // gain set to 1 to unmute sound
        mute.id = "";
        mute.innerHTML = "Mute";
      }
    }
    
  5. Web Speech API: 使你能够将语音数据合并到 Web 应用程序中。Web Speech API 有两个部分:SpeechSynthesis 语音合成(文本到语音 TTS)和 SpeechRecognition 语音识别(异步语音识别)

  6. Web USB API: 提供了一种向 Web 公开非标准通用串行总线 (USB) 兼容设备服务的方法,使 USB 更安全、更易于使用。

    // 访问连接的设备
    navigator.usb
      .requestDevice({ filters: [{ vendorId: 0x2341 }] })
      .then((device) => {
        console.log(device.productName); // "Arduino Micro"
        console.log(device.manufacturerName); // "Arduino LLC"
      })
      .catch((error) => {
        console.error(error);
      });
    // 查找所有连接的设备
    navigator.usb.getDevices().then((devices) => {
      devices.forEach((device) => {
        console.log(device.productName); // "Arduino Micro"
        console.log(device.manufacturerName); // "Arduino LLC"
      });
    });
    
  7. Web Component 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 web 应用中使用它们.examples

  8. Web Worker API: 使得在一个独立于 Web 应用程序主执行线程的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务,使主线程(通常是 UI 线程)的运行不会被阻塞/放慢.

    基本的专用 worker 示例运行专用 worker

    基本的共享 worker 示例运行共享 worker

    OffscreenCanvas worker 示例

  9. WebVR API: 能为虚拟现实设备的渲染提供支持——例如像 Oculus Rift 或者 HTC Vive 这样的头戴式设备与 Web 应用的连接。它能让开发者将位置和动作信息转换成 3D 场景中的运动。基于这项技术能产生很多有趣的应用,比如虚拟的产品展示,可交互的培训课程,以及超强沉浸感的第一人称游戏。

    mdn/webvr-tests简单构建的演示用于阐明基本的使用方法。

    MozVR team 更多复杂的演示,关于 WebVR 特别的资源,以及更多的内容!

  10. WebCodecs API: 为 web 开发者提供了对视频流的单个帧和音频数据块的底层访问能力。这对于那些需要完全控制媒体处理方式的 web 应用程序非常有用。例如,视频或音频编辑器,以及视频会议。使用 WebCodec 处理视频

```js
let frame_counter = 0;
const track = stream.getVideoTracks()[0];
const media_processor = new MediaStreamTrackProcessor(track);
const reader = media_processor.readable.getReader();
while (true) {
  const result = await reader.read();
  if (result.done) break;
  let frame = result.value;
  if (encoder.encodeQueueSize > 2) {
    // Too many frames in flight, encoder is overwhelmed
    // let's drop this frame.
    frame.close();
  } else {
    frame_counter++;
    const insert_keyframe = frame_counter % 150 === 0;
    encoder.encode(frame, { keyFrame: insert_keyframe });
    frame.close();
  }
}
```
  1. WebVTT: Web视频文本规格式,是一种使用 track 元素显示定时文本轨道(例如字幕或者标题)的格式。WebVTT 文件的主要用途是将文本叠加到 video 中。WebVTT 是一种基于文本的格式,必须使用 UTF-8 进行编码。在可以使用空格的地方,也可以使用制表符。这里也有一个小的 API 可用于表示和管理这些轨道以及在正确时间执行文本回放所需的数据。Web 视频文本轨格式(WebVTT) - Web API 接口参考 | MDN (mozilla.org)

  2. Web Locks API: 允许在一个标签页或 worker 中运行的脚本异步获取锁,在执行工作时保持锁,最后释放锁。持有锁时,在同一源中执行的其他脚本都无法获取相同的锁,这允许在多个标签页或 worker 中运行的 Web 应用程序协调工作和资源的使用。

    await do_something_without_lock();
    
    // 请求锁。
    await navigator.locks.request("my_resource", async (lock) => {
      // 锁已被获取。
      await do_something_with_lock();
      await do_something_else_with_lock();
      // 现在锁将被释放。
    });
    // 锁已被释放。
    
    await do_something_else_without_lock();
    
    // 进阶用法
    // 捕获 Promise 控制函数:
    let resolve, reject;
    const p = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    
    // 请求锁:
    navigator.locks.request(
      "my_resource",
      // 锁已被获取。
      (lock) => p, // 现在锁将被保持直到调用 resolve() 方法或 reject() 方法为止。
    );
    
    
    
  3. WebXR 设备API是一组支持将渲染 3D 场景用来呈现虚拟世界(虚拟现实,也称作 VR)或将图形图像添加到现实世界(增强现实,也称作 AR)的标准。 WebXR 设备 API 实现了 WebXR 功能集的核心,管理输出设备的选择,以适当的帧速率将 3D 场景呈现给所选设备,并管理使用输入控制器创建的运动矢量.

    兼容性设备包括沉浸式 3D 运动和定位跟踪耳机,通过框架覆盖在真实世界场景之上的眼镜,以及手持移动电话,它们通过用摄像机捕捉世界来增强现实,并通过计算机生成的图像增强场景.

  4. WebGL: 是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需使用插件。WebGL 通过引入一个与 OpenGL ES 2.0 非常一致的 API 来做到这一点,该 API 可以在 HTML5 Canvas 元素中使用。这种一致性使 API 可以利用用户设备提供的硬件图形加速。WebGL - Web API 接口参考 | MDN (mozilla.org)

  5. Web MIDI API: 连接到乐器数字接口 (MIDI) 设备并与之交互。

    这些接口处理发送和接收MIDI消息的实际方面。 因此,该 API 可用于音乐和非音乐用途,任何 MIDI 设备都连接到您的计算机。

    // 获取设备的访问权限
    navigator.permissions.query({ name: "midi", sysex: true }).then((result) => {
      if (result.state === "granted") {
        // Access granted.
      } else if (result.state === "prompt") {
        // Using API will prompt for permission
      }
      // Permission was denied by user prompt or permission policy
    });
    // 获取对 MIDI 端口的访问权限
    let midi = null; // global MIDIAccess object
    function onMIDISuccess(midiAccess) {
      console.log("MIDI ready!");
      midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance)
    }
    
    function onMIDIFailure(msg) {
      console.error(`Failed to get MIDI access - ${msg}`);
    }
    
    navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
    // 列出输入和输出
    function listInputsAndOutputs(midiAccess) {
      for (const entry of midiAccess.inputs) {
        const input = entry[1];
        console.log(
          `Input port [type:'${input.type}']` +
            ` id:'${input.id}'` +
            ` manufacturer:'${input.manufacturer}'` +
            ` name:'${input.name}'` +
            ` version:'${input.version}'`,
        );
      }
    
      for (const entry of midiAccess.outputs) {
        const output = entry[1];
        console.log(
          `Output port [type:'${output.type}'] id:'${output.id}' manufacturer:'${output.manufacturer}' name:'${output.name}' version:'${output.version}'`,
        );
      }
    }
    // 处理 MIDI 输入
    function onMIDIMessage(event) {
      let str = `MIDI message received at timestamp ${event.timeStamp}[${event.data.length} bytes]: `;
      for (const character of event.data) {
        str += `0x${character.toString(16)} `;
      }
      console.log(str);
    }
    
    function startLoggingMIDIInput(midiAccess) {
      midiAccess.inputs.forEach((entry) => {
        entry.onmidimessage = onMIDIMessage;
      });
    }
    
    
  6. WebGPU API: 使 web 开发人员能够使用底层系统的 GPU(图形处理器)进行高性能计算并绘制可在浏览器中渲染的复杂图形。

    WebGPU 是 WebGL 的继任者,为现代 GPU 提供更好的兼容、支持更通用的 GPU 计算、更快的操作以及能够访问到更高级的 GPU 特性。

    // 获取设备的访问权限
    async function init() {
      if (!navigator.gpu) {
        throw Error("WebGPU not supported.");
      }
    
      const adapter = await navigator.gpu.requestAdapter();
      if (!adapter) {
        throw Error("Couldn't request WebGPU adapter.");
      }
    
      const device = await adapter.requestDevice();
    
      //...
    }
    // 创建着色器模块
    const shaders = `
    struct VertexOut {
      @builtin(position) position : vec4f,
      @location(0) color : vec4f
    }
    
    @vertex
    fn vertex_main(@location(0) position: vec4f,
                   @location(1) color: vec4f) -> VertexOut
    {
      var output : VertexOut;
      output.position = position;
      output.color = color;
      return output;
    }
    
    @fragment
    fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
    {
      return fragData.color;
    }
    `;
    const shaderModule = device.createShaderModule({
      code: shaders,
    });
    // 获取和配置 canvas 上下文
    const canvas = document.querySelector("#gpuCanvas");
    const context = canvas.getContext("webgpu");
    
    context.configure({
      device: device,
      format: navigator.gpu.getPreferredCanvasFormat(),
      alphaMode: "premultiplied",
    });
    // 创建缓冲区并将我们的三角形数据写入
    const vertices = new Float32Array([
      0.0, 0.6, 0, 1, 1, 0, 0, 1, -0.5, -0.6, 0, 1, 0, 1, 0, 1, 0.5, -0.6, 0, 1, 0,
      0, 1, 1,
    ]);
    const vertexBuffer = device.createBuffer({
      size: vertices.byteLength, // make it big enough to store vertices in
      usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
    });
    device.queue.writeBuffer(vertexBuffer, 0, vertices, 0, vertices.length);
    
    // 定义和创建渲染管线
    
    const vertexBuffers = [
      {
        attributes: [
          {
            shaderLocation: 0, // 位置
            offset: 0,
            format: "float32x4",
          },
          {
            shaderLocation: 1, // 颜色
            offset: 16,
            format: "float32x4",
          },
        ],
        arrayStride: 32,
        stepMode: "vertex",
      },
    ];
    const pipelineDescriptor = {
      vertex: {
        module: shaderModule,
        entryPoint: "vertex_main",
        buffers: vertexBuffers,
      },
      fragment: {
        module: shaderModule,
        entryPoint: "fragment_main",
        targets: [
          {
            format: navigator.gpu.getPreferredCanvasFormat(),
          },
        ],
      },
      primitive: {
        topology: "triangle-list",
      },
      layout: "auto",
    };
    const renderPipeline = device.createRenderPipeline(pipelineDescriptor);
    // 运行渲染通道
    const commandEncoder = device.createCommandEncoder();
    const clearColor = { r: 0.0, g: 0.5, b: 1.0, a: 1.0 };
    
    const renderPassDescriptor = {
      colorAttachments: [
        {
          clearValue: clearColor,
          loadOp: "clear",
          storeOp: "store",
          view: context.getCurrentTexture().createView(),
        },
      ],
    };
    
    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    passEncoder.setPipeline(renderPipeline);
    passEncoder.setVertexBuffer(0, vertexBuffer);
    passEncoder.draw(3);
    assEncoder.end();
    
    device.queue.submit([commandEncoder.finish()]);
    
    // 基础的计算管线
    
    // 定义全局的缓冲区大小
    const BUFFER_SIZE = 1000;
    
    const shader = `
    @group(0) @binding(0)
    var<storage, read_write> output: array<f32>;
    
    @compute @workgroup_size(64)
    fn main(
      @builtin(global_invocation_id)
      global_id : vec3u,
    
      @builtin(local_invocation_id)
      local_id : vec3u,
    ) {
      // Avoid accessing the buffer out of bounds
      if (global_id.x >= ${BUFFER_SIZE}) {
        return;
      }
    
      output[global_id.x] =
        f32(global_id.x) * 1000. + f32(local_id.x);
    }
    `;
    // 创建缓冲区处理我们的数据
    const output = device.createBuffer({
      size: BUFFER_SIZE,
      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
    });
    
    const stagingBuffer = device.createBuffer({
      size: BUFFER_SIZE,
      usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
    });
    
    // 创建绑定组布局
    
    const bindGroupLayout = device.createBindGroupLayout({
      entries: [
        {
          binding: 0,
          visibility: GPUShaderStage.COMPUTE,
          buffer: {
            type: "storage",
          },
        },
      ],
    });
    const bindGroup = device.createBindGroup({
      layout: bindGroupLayout,
      entries: [
        {
          binding: 0,
          resource: {
            buffer: output,
          },
        },
      ],
    });
    // 创建计算管线
    const computePipeline = device.createComputePipeline({
      layout: device.createPipelineLayout({
        bindGroupLayouts: [bindGroupLayout],
      }),
      compute: {
        module: shaderModule,
        entryPoint: "main",
      },
    });
    // 运行计算通道
    passEncoder.setPipeline(computePipeline);
    passEncoder.setBindGroup(0, bindGroup);
    passEncoder.dispatchWorkgroups(Math.ceil(BUFFER_SIZE / 64));
    
    passEncoder.end();
    
    // 将结果读回 JavaScript
    
    // 复制 output 缓冲去到 staging 缓冲区
    commandEncoder.copyBufferToBuffer(
      output,
      0, // 来源缓冲区偏移量
      stagingBuffer,
      0, // 目的缓冲区偏移量
      BUFFER_SIZE,
    );
    
    // 通过将命令缓冲区数组传递给命令队列以执行来结束
    device.queue.submit([commandEncoder.finish()]);
    // 映射 staging 缓冲区,以便读回到 JS
    await stagingBuffer.mapAsync(
      GPUMapMode.READ,
      0, // 偏移量
      BUFFER_SIZE, // 长度
    );
    
    const copyArrayBuffer = stagingBuffer.getMappedRange(0, BUFFER_SIZE);
    const data = copyArrayBuffer.slice();
    stagingBuffer.unmap();
    console.log(new Float32Array(data));
    
    
  7. Window Controls Overlay API: 窗口控件覆盖API: 使安装在桌面操作系统上的渐进式 Web 应用能够隐藏默认窗口标题栏并显示自己的内容 在应用窗口的整个外围区域上,将控制按钮(最大化、最小化和关闭)变成叠加层。

    if ("windowControlsOverlay" in navigator) {
      navigator.windowControlsOverlay.addEventListener(
        "geometrychange",
        (event) => {
          if (event.visible) {
            const rect = event.titlebarAreaRect;
            // Do something with the coordinates of the title bar area.
          }
        },
      );
    }
    
  8. Web NFC API: 允许通过轻量级 NFC 数据交换格式 (NDEF) 消息通过 NFC 交换数据.Web NFC API - Web API 接口 |MDN网络 (mozilla.org)

  9. WebHID API: 人机接口设备 是一种从人类获取输入或向人类提供输出的设备。它还指 HID 协议,这是主机和设备之间双向通信的标准,旨在简化安装过程。HID 协议最初是为 USB 设备开发的,但后来在许多其他协议(包括蓝牙)上实现。

    // 检索网站之前被授予访问权限的所有设备,并将设备名称记录到控制台。
    let devices = await navigator.hid.getDevices();
    devices.forEach((device) => {
      console.log(`HID: ${device.productName}`);
    });
    // 可以注册事件侦听器以断开任何 HID 设备的连接。
    navigator.hid.addEventListener("disconnect", (event) => {
      console.log(`HID disconnected: ${event.device.productName}`);
      console.dir(event);
    });
    
  10. Web Notifications API: 允许网页控制向最终用户显示系统通知——这些都在顶层浏览上下文窗口之外,因此即使用户已经切换标签页或移动到不同的应用程序也可以显示。该 API 被设计成与不同平台上的现有通知系统兼容。

    btn.addEventListener("click", () => {
      let promise = Notification.requestPermission();
      // 等待授权
    });
    // 示例
    <button onclick="notifyMe()">提醒我!</button>
    function notifyMe() {
      if (!("Notification" in window)) {
        // 检查浏览器是否支持通知
        alert("当前浏览器不支持桌面通知");
      } else if (Notification.permission === "granted") {
        // 检查是否已授予通知权限;如果是的话,创建一个通知
        const notification = new Notification("你好!");
        // …
      } else if (Notification.permission !== "denied") {
        // 我们需要征求用户的许可
        Notification.requestPermission().then((permission) => {
          // 如果用户接受,我们就创建一个通知
          if (permission === "granted") {
            const notification = new Notification("你好!");
            // …
          }
        });
      }
    
      // 最后,如果用户拒绝了通知,并且你想尊重用户的选择,则无需再打扰他们
    }
    
  11. Web OTP API: 为 Web 应用提供简化的用户体验,以验证电话号码在用作登录因素时是否属于用户。WebOTP 是凭证管理 API 的扩展。

 ```js
 <input type="text" autocomplete="one-time-code" inputmode="numeric" />
 
 // Detect feature support via OTPCredential availability
 if ("OTPCredential" in window) {
   window.addEventListener("DOMContentLoaded", (e) => {
     const input = document.querySelector('input[autocomplete="one-time-code"]');
     if (!input) return;
     // Set up an AbortController to use with the OTP request
     const ac = new AbortController();
     const form = input.closest("form");
     if (form) {
       // Abort the OTP request if the user attempts to submit the form manually
       form.addEventListener("submit", (e) => {
         ac.abort();
       });
     }
     // Request the OTP via get()
     navigator.credentials
       .get({
         otp: { transport: ["sms"] },
         signal: ac.signal,
       })
       .then((otp) => {
         // When the OTP is received by the app client, enter it into the form
         // input and submit the form automatically
         input.value = otp.code;
         if (form) form.submit();
       })
       .catch((err) => {
         console.error(err);
       });
   });
 }
 
 setTimeout(() => {
   // abort after 30 seconds
   ac.abort();
 }, 30 * 1000);
 ```
  1. Window Management API: 允许您获取有关连接到设备的显示器的详细信息,并更轻松地将窗口放置在特定屏幕上,从而为更有效的多屏幕应用程序铺平道路。
 ```js
 const myWindow = window.open(
   "https://example.com/",
   "myWindow",
   "left=50,top=50,width=400,height=300",
 );
 ```
  1. Web Serial API: 为网站提供了一种读取和写入串行设备的方法。这些设备可以通过串行端口连接,也可以是模拟串行端口的 USB 或蓝牙设备。

    // 检查可用端口
    navigator.serial.addEventListener("connect", (e) => {
      // Connect to `e.target` or add it to a list of available ports.
    });
    
    navigator.serial.addEventListener("disconnect", (e) => {
      // Remove `e.target` from the list of available ports.
    });
    
    navigator.serial.getPorts().then((ports) => {
      // Initialize the list of available ports with `ports` on page load.
    });
    
    button.addEventListener("click", () => {
      const usbVendorId = 0xabcd;
      navigator.serial
        .requestPort({ filters: [{ usbVendorId }] })
        .then((port) => {
          // Connect to `port` or add it to the list of available ports.
        })
        .catch((e) => {
          // The user didn't select a port.
        });
    });
    // 从端口读取数据
    while (port.readable) {
      const reader = port.readable.getReader();
      try {
        while (true) {
          const { value, done } = await reader.read();
          if (done) {
            // |reader| has been canceled.
            break;
          }
          // Do something with |value|...
        }
      } catch (error) {
        // Handle |error|...
      } finally {
        reader.releaseLock();
      }
    }
    
  2. WebRTC API: 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。

    WebRTC 包含了若干相互关联的 API 和协议以达到这个目标。你在这里看到的文档将会帮助你理解 WebRTC 的基本概念,还会教你如何去建立和使用可以传输媒体数据和其他任意数据的连接。当然你还会学到更多其他的东西。WebRTC API - Web API 接口参考 | MDN (mozilla.org)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值