«

几个很实用但是又比较冷门的 Web API 极其极简演示

时间:2026-1-25 23:17     作者:独元殇     分类: 前端技术


[toc]

前言

API(应用程序编程接口),就像是小国家与小国家之间沟通的桥梁。在编程里,就是模块与模块、软件与软件之间沟通的路子。

今天我来分享几个比较少用但 你肯定很有想用过 的 Web Api。

现在 AI 来了,可能我们很少去关注程序的细节了,但是我们的眼界和过去古法编程一样,仍然是:知道的越多更重要,知道了就是「学会」了,反正有文档用起来就是无复用成本,毕竟我们的很多问题是我们不知道我们不知道什么....

这个 Web API ,是用户和 浏览器 之间沟通的桥梁,一般是 JavaScript 在使用,也可以被 python go rust 等等使用。

之前,我们可能脍炙人口下面这些 API:

Geolocation(地理位置)

DOM(元素)

Console (控制台)

Drag & Drop API(拖放)

Canvas(画布)

Fetch(网络通信)

History(浏览器会话)

但其实还有很多很多。不过要注意,并非所有 Web API 都可以广泛使用,可能在 Firefox 或 edge 里无法用。

这里有个经典的 Can I Use 网站,可以轻松查询某个 API 能否被某个浏览器使用。

比如 canvas:

img

第一个 全屏(Fullscreen API)

我们可能经常在视频网站使用,但是我们有可能直接不记得有这个经历。

导致我们很少关注。

但是,浏览器 里的全屏,并不是我们想象中,直接单纯的把浏览器上面的地址栏去掉,全屏了,而是可以针对不同的元素(以及其子元素)也全屏了。

不仅如此,还有在切换全屏、非全屏状态时也执行一些操作。

下面是一个演示,可以在我自制的 https://www.ccgxk.com/cellhtmleditor.html 简易 html 编辑器里尝试。

<div style="background:#eee; height:50px">普通背景区域(全屏后我会消失)</div>
<div id="target" style="background:#b3e5fc; padding:20px;">
    要全屏的核心区域 <button onclick="tog()">进入/退出全屏</button>
</div>

<script>
    const el = document.getElementById('target');

    // 触发全屏逻辑
    const tog = () => document.fullscreenElement ? document.exitFullscreen() : el.requestFullscreen();

    // 状态切换时,在控制台 console
    el.onfullscreenchange = () => {
        const isFull = !!document.fullscreenElement;
        console.log(isFull ? "动作:已进入专注模式(业务逻辑启动)" : "动作:已切回普通模式");
    };
</script>

效果如下:

img

第二个 剪切板 (Clipboard Async API)

剪切板,就不用多介绍了吧。

其实剪切板的真实意思是,操作系统提供的缓冲区,让数据在应用程序内部和之间进行短期存储和传输,主要有三种操作:复制、剪切和粘贴。

我们平时可能多使用【点我复制】这种,这个是复制。但是其实还可以访问粘贴,只不过这个粘贴需要用户授权,否则很可怕,万一你剪切板里有密码或其他敏感信息,就完蛋了。

<input id="ipt" value="在此输入内容进行测试" style="width:200px">
<button onclick="handle('copy')">复制</button> 
<button onclick="handle('cut')">剪切</button> 
<button onclick="handle('paste')">粘贴</button>

<script>
    const el = document.getElementById('ipt');
    async function handle(type) {
        console.log(`[系统日志] 用户触发了 ${type} 动作`);  // 控制台日志

        if (type === 'paste') {
            el.value = await navigator.clipboard.readText(); // 粘贴
        } else {
            await navigator.clipboard.writeText(el.value);   // 复制/剪切
            if (type === 'cut') el.value = '';               // 清空源
        }
    }
</script>

效果如下:

img

三个按钮实现 复杂 剪切 粘贴 的演示。

第三个,监听元素变化(Resize Observer API)

这个就有点冷门,但是我们所有前端们都有想过,但压根连知道都不知道。

还是很实用的。

拖动右下角改变它的大小:
<div id="box" style="resize:both; overflow:auto; border:2px dashed #555; width:200px; padding:10px;">

</div>
<script>
    const el = document.getElementById('box');

    const observer = new ResizeObserver(entries => {
        const { width, height } = entries[0].contentRect;  // entries 是一个数组,包含所有被监听且发生变化的元素信息
        el.textContent = `宽:${Math.round(width)} 高:${Math.round(height)}`;  // 动作
    });

    observer.observe(el);  // 开始监听
</script>

效果如下:

img

第四个,广播频道(Broadcast Channel API)

这个就有点硬核了!

如果你不知道这个 API,你肯定错过了很多潜在的创意!

因为在我们的直觉想法里,浏览器里一个个标签页,都是独立的王国,肯定不能相互通信,但其实是可以的。

不过注意,必须是 「same origin」,也就是同源,一个域名下才可以。

<input id="ipt" value="在另一个标签页看页面变化" style="width:200px">
<button onclick="send()">广播消息</button>
<div id="log" style="margin-top:8px;color:#555"></div>

<script>
const channel = new BroadcastChannel('app_sync');
const log = document.getElementById('log');

const send = () => {
    channel.postMessage(ipt.value);
    log.textContent = `已发送:${ipt.value}`;
};

channel.onmessage = (event) => {
    log.textContent = `收到消息:${event.data}`;
};
</script>

演示如下:

img

在同源下,打开这个页面,然后输入信息后,广播信息,然后另一个标签页就收到了~ very cool!

第五个,性能(Performance Interface API)

一些内存、时间、navigation 等等指标很重要。

我写了一个简单的面板,帮助大家这样这个 api。

<h3>性能自检面板</h3>
    <table border="1" cellpadding="5" cellspacing="0">
        <thead>
            <tr>
                <th>属性名 (Key)</th>
                <th>中文含义</th>
                <th>当前值 (Value)</th>
            </tr>
        </thead>
        <tbody id="data-body">
            <tr><td colspan="3">数据加载中...</td></tr>
        </tbody>
    </table>
<script>
const dict = {
    // NavigationTiming
    name: "当前页面的 URL",
    entryType: "条目类型(navigation)",
    startTime: "开始时间(始终为 0)",
    duration: "页面完全加载总耗时",
    initiatorType: "发起类型",
    nextHopProtocol: "使用的网络协议",
    workerStart: "Service Worker 启动时间",
    redirectStart: "重定向开始时间",
    redirectEnd: "重定向结束时间",
    fetchStart: "开始获取资源时间",
    domainLookupStart: "DNS 查询开始时间",
    domainLookupEnd: "DNS 查询结束时间",
    connectStart: "TCP 连接开始时间",
    secureConnectionStart: "TLS 握手开始时间",
    connectEnd: "TCP 连接结束时间",
    requestStart: "请求发送开始时间",
    responseStart: "接收首字节时间(TTFB)",
    responseEnd: "响应数据接收完成时间",
    transferSize: "网络传输总字节数",
    encodedBodySize: "压缩后资源大小",
    decodedBodySize: "解压后资源大小",
    serverTiming: "服务端性能指标",
    unloadEventStart: "上一页面卸载开始时间",
    unloadEventEnd: "上一页面卸载结束时间",
    domInteractive: "DOM 可交互时间",
    domContentLoadedEventStart: "DOMContentLoaded 开始时间",
    domContentLoadedEventEnd: "DOMContentLoaded 结束时间",
    domComplete: "DOM 完全加载时间",
    loadEventStart: "load 事件开始时间",
    loadEventEnd: "load 事件结束时间",
    type: "导航类型",
    redirectCount: "重定向次数",
    activationStart: "预渲染激活时间",

    // Memory
    jsHeapSizeLimit: "JS 堆内存上限",
    totalJSHeapSize: "当前已分配 JS 堆内存",
    usedJSHeapSize: "当前使用中的 JS 堆内存",

    // Timing
    timeOrigin: "性能时间原点(页面创建时间)",
    now: "当前高精度时间戳"
};

function format(key, val) {
    if (typeof val === "number") {
        if (key.toLowerCase().includes("size")) return val + " 字节";
        return val.toFixed(2) + " 毫秒";
    }
    if (Array.isArray(val)) return val.length ? JSON.stringify(val) : "无";
    return val;
}

function update() {
    let html = "";

        // Memory(仅 Chromium 可用)
    if (performance.memory) {
        for (let k in performance.memory) {
            html += `<tr>
                <td><code>${k}</code></td>
                <td>${dict[k] || "内存相关指标"}</td>
                <td><strong>${format(k, performance.memory[k])}</strong></td>
            </tr>`;
        }
    }

    // Timing 通用接口
    html += `<tr>
        <td><code>timeOrigin</code></td>
        <td>${dict.timeOrigin}</td>
        <td><strong>${performance.timeOrigin.toFixed(2)} 毫秒</strong></td>
    </tr>`;

    html += `<tr>
        <td><code>now</code></td>
        <td>${dict.now}</td>
        <td><strong>${performance.now().toFixed(2)} 毫秒</strong></td>
    </tr>`;

    // Navigation Timing
    const nav = performance.getEntriesByType("navigation")[0];
    if (nav) {
        const data = nav.toJSON();
        for (let k in data) {
            html += `<tr>
                <td><code>${k}</code></td>
                <td>${dict[k] || ""}</td>
                <td><strong>${format(k, data[k])}</strong></td>
            </tr>`;
        }
    }

    document.getElementById("data-body").innerHTML = html;
}

update();
setInterval(update, 1000);
</script>

显示效果如下:

img

另外再插入 网络、电池信息。我就不新开标题了。

<h3>网络、电池信息</h3>
<table border="1" cellpadding="5">
  <thead>
    <tr><th>属性</th><th>中文含义</th><th>当前值</th></tr>
  </thead>
  <tbody id="data"></tbody>
</table>

<script>
    const dict = {
      // Network
      effectiveType: "当前网络类型(slow-2g / 2g / 3g / 4g)",
      downlink: "下行带宽估计值",
      rtt: "网络往返延迟估计",
      saveData: "是否开启省流量模式",
      type: "物理连接类型(wifi / cellular 等)",

      // Battery
      charging: "是否正在充电",
      chargingTime: "预计充满所需时间",
      dischargingTime: "预计可用时间",
      level: "当前电量比例"
    };

    const tbody = document.getElementById("data");

    function row(k, v) {
      return `<tr>
        <td><code>${k}</code></td>
        <td>${dict[k] || ""}</td>
        <td><strong>${v}</strong></td>
      </tr>`;
    }

    function renderNetwork() {
      const n = navigator.connection;
      if (!n) return "";
      let html = "";
      for (let k in n) {
        if (typeof n[k] !== "function") {
          html += row(k, typeof n[k] === "number" ? n[k] : String(n[k]));
        }
      }
      return html;
    }

    async function renderBattery() {
      if (!navigator.getBattery) return "";
      const b = await navigator.getBattery();
      return [
        row("charging", b.charging),
        row("chargingTime", b.chargingTime + " 秒"),
        row("dischargingTime", b.dischargingTime + " 秒"),
        row("level", Math.round(b.level * 100) + "%")
      ].join("");
    }

    async function update() {
      tbody.innerHTML = renderNetwork() + await renderBattery();
    }

    update();
    setInterval(update, 2000);
</script>

效果如下:

img

第六个,震动(Vibration API ,仅移动端可用)

这个是最好玩的,可惜只能在手机上体验。

没错,网页也可以振动,而是还可以自定义频率!

在我们的直觉里,好像震动只能使用原生的 APP ,但实际上网页也可以!

(大家可以使用移动端,打开这个网页体验: https://www.ccgxk.com/631.html

<h3>(仅 移动端 有效,注意要关闭静音模式,才能体验!)</h3>
<button onclick="run(200)">短震 200ms</button>
<button onclick="run([500,110,500,110,450,110,200,110,170,40,450,110,200,110,170,40,500])">马里奥节奏</button>
<button onclick="run(0)">停止</button>
<div id="log" style="margin-top:20px; color:blue">点击按钮开始</div>

<script>
    function run(p) {
        //判断是否存在 API
        if (!navigator.vibrate) return document.getElementById('log').innerText = "此设备不支持震动";
        const success = navigator.vibrate(p);  // 执行动作:p 为数字是单次震动,为数组是模式震动,为 0 是停止
        document.getElementById('log').innerText = success ? (p === 0 ? "已停止" : "📳 正在震动...") : "调用失败(需用户点击触发)";
    }
</script>

效果如下:

img

最后

当然,最后还有几个有意思的。大家有空可以捣鼓捣鼓、折腾折腾,未来这些都是创意的种子~

Payment Request API: 支付请求 API,为商家和用户提供一致的支付体验。

Touch Events: 触摸,提供相对底层的 API,可用于支持特定应用的多点触控交互,例如双指手势。

Page Visibility: 页面可见性,提供可监听的事件,用于判断文档何时变为可见或隐藏状态。

Channel Messaging API: 通道消息 API,在浏览上下文中传递消息的另一种优秀方式。但与广播机制不同,它专用于点对点消息传输

还有很多很多....

标签: 原创 API web