做标注 / 高频点击类工作,鼠标点半天累。给页面注入一个悬浮面板 + 快捷键,数字键触发对应元素的 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 框架点击、输入框内不拦截。做高频标注 / 批量操作时效率提升明显。
