给页面注入一个快捷键面板:数字键快速点击标签

做标注 / 高频点击类工作,鼠标点半天累。给页面注入一个悬浮面板 + 快捷键,数字键触发对应元素的 click()——效率能翻几倍。

直接粘贴到浏览器 Console

(() => {
  const items = [
    { key: "1", title: "标签A" },
    { key: "2", title: "标签B" },
    { key: "3", title: "标签C" },
    { key: "4", title: "标签D" },
    { key: "5", title: "标签E" },
  ];

  function findEl(title) {
    return document.querySelector(`span.label[title="${title}"]`);
  }

  function clickByTitle(title) {
    const el = findEl(title);
    if (!el) return console.warn(`未找到: ${title}`);
    // 框架页面用 dispatchEvent 更稳
    el.dispatchEvent(new MouseEvent("click", {
      bubbles: true, cancelable: true, view: window,
    }));
    console.log(`点击: ${title}`);
  }

  // 面板
  const panel = document.createElement("div");
  panel.style.cssText = `
    position: fixed; top: 80px; right: 20px; width: 220px;
    background: rgba(0,0,0,.85); color: #fff; padding: 12px;
    border-radius: 12px; z-index: 999999;
    font-family: system-ui; font-size: 14px;
  `;
  panel.innerHTML = "<b>快捷键面板</b>";

  function render() {
    panel.querySelectorAll(".item").forEach(e => e.remove());
    for (const it of items) {
      const row = document.createElement("div");
      row.className = "item";
      const ok = !!findEl(it.title);
      row.style.cssText = `
        display:flex; justify-content:space-between;
        margin:6px 0; padding:4px 6px; border-radius:6px;
        background:${ok ? "rgba(0,255,0,.12)" : "rgba(255,0,0,.12)"};
        cursor:pointer;
      `;
      row.innerHTML = `<span>[${it.key}] ${it.title}</span><span>${ok ? "🟢" : "🔴"}</span>`;
      row.onclick = () => clickByTitle(it.title);
      panel.appendChild(row);
    }
    const tip = document.createElement("div");
    tip.className = "item";
    tip.style.cssText = "margin-top:10px;color:#aaa;font-size:12px;";
    tip.textContent = "F8 显示/隐藏";
    panel.appendChild(tip);
  }
  render();
  document.body.appendChild(panel);

  // 键盘监听
  document.addEventListener("keydown", e => {
    if (e.key === "F8") {
      panel.style.display = panel.style.display === "none" ? "block" : "none";
      return;
    }
    // 输入框里不触发
    const t = e.target;
    if (t instanceof HTMLInputElement ||
        t instanceof HTMLTextAreaElement ||
        t?.isContentEditable) return;

    const item = items.find(x => x.key === e.key);
    if (item) {
      e.preventDefault();
      clickByTitle(item.title);
    }
  });

  // 动态渲染的页面定时刷新状态
  setInterval(render, 2000);
})();

功能:

  • 右上角浮 220px 宽面板
  • 每项显示 [键] 标题 + 找到/未找到指示
  • 1~5 触发对应点击
  • F8 切换显示/隐藏
  • 面板项本身也能点击
  • 输入框获得焦点时不拦截(不然打字触发 bug)

打包成油猴脚本

// ==UserScript==
// @name         快捷键面板
// @match        https://your-site.com/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

(function () {
  // ... 上面的代码
})();

document-end 保证 DOM 建好之后再挂面板。SPA 页面切路由后可能面板还在但按键失效——加一个 MutationObserver 监听页面变化重新 render 即可。

复杂点击的兼容

某些 Vue / React 页面直接 el.click() 触发不到框架事件(因为它们监听的是自己合成的事件,而不是原生 DOM 事件),改用:

el.dispatchEvent(new MouseEvent("click", {
  bubbles: true,
  cancelable: true,
  view: window,
}));

冒泡是关键——很多框架的 delegation 是在文档根节点上监听的。

更进一步的想法

如果是标注工具,还能加:

  • Q/W/E/R/T/Y 左手键位(比数字键位好按)
  • 组合标签(Shift+1 = 同时点 1 和 3)
  • 自动跳下一张图(连点两个标签 → 自动 next)
  • 今日已标数统计
  • 本地保存自定义键位localStorage
  • CSV 导出统计

放在 localStorage 里存 key 配置:

const config = JSON.parse(localStorage.getItem("hotkey-config") || "[]");
if (config.length) items.splice(0, items.length, ...config);

一句话总结

Console 里几十行 JS 就能注入一个快捷键面板dispatchEvent 兼容 Vue/React 框架点击、输入框内不拦截。做高频标注 / 批量操作时效率提升明显。