一个常驻所有网页的悬浮「瑞士军刀」——单击平滑返回顶部,悬停展开快捷菜单,并能挂载插件扩展能力(内置 GitHub 下载加速、划词翻译、定时提醒,也可导入用户插件)。
releases/download)archive/refs/heads)UserConfig 面板配置(两处入口共享同一份配置)dist/rocket_btn/,新建脚本,粘贴 dist/rocket_btn/rocket_btn.js 的全部内容并保存。dist/rocket_btn/README.md)与开发文档(dist/rocket_btn/docs/),随脚本一起分发。配置项定义在脚本头部 ==UserConfig== 块中,可在 ScriptCat 管理面板中修改;也可长按返回顶部按钮在页面内快速修改(两端同步,读写同一组 key)。
| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
githubAccel |
checkbox | true |
是否开启 GitHub 镜像加速下载 |
backTop |
checkbox | true |
是否显示返回顶部按钮 |
alwaysShow |
checkbox | false |
是否总是显示返回顶部按钮(开启后不随滚动自动隐藏) |
noBtnBg |
checkbox | false |
是否只显示图标(不显示按钮背景:背景/边框/阴影透明,仅保留图标与点击区域) |
iconScale |
number | 1 |
图标大小倍率(1x = 26px,允许小数截断 1 位,范围 0.6~4),按钮尺寸随图标等比例增大 |
btnAnim |
text | none |
按钮图标动画:none(无)/ breathe(呼吸)/ spin(旋转)/ pulse(脉冲) |
btnRotate |
number | 0 |
按钮旋转角度 0~360°,超出范围自动收敛 |
quickMenu |
checkbox | false |
是否启用快捷菜单(桌面 hover 展开,触摸端双击) |
menuSlots |
number | 4 |
快捷菜单槽位数量 2~8,超出范围自动收敛 |
customIcon |
textarea | 空 | 当前生效的自定义按钮图标;留空按「选中的 → 列表第一个 → 默认图标」fallback 生效 |
iconList |
内部列表 | 空 | 已配置过的图标列表(倒序,新加在前;默认图标常驻末尾、不可删除;面板内管理,保存后持久化) |
disabledSites |
textarea | 空 | 不显示返回顶部按钮的站点,每行一个域名(支持子域名匹配,如填 baidu.com 会屏蔽 www.baidu.com) |
脚本通过 GM_getValue / GM_setValue 读写以下 key:
general.githubAccel
general.backTop
general.alwaysShow
general.noBtnBg
general.iconScale
general.btnAnim
general.btnRotate
general.customIcon
general.iconList
general.quickMenu
general.menuSlots
general.disabledSites
面向最终用户的完整操作指南。
dist/rocket_btn/,新建脚本,粘贴 dist/rocket_btn/rocket_btn.js 的全部内容并保存。| 操作 | 效果 |
|---|---|
| 单击 | 平滑返回页面顶部 |
| 长按(600ms) | 打开快速设置面板 |
| 垂直拖动 | 调整按钮上下位置(水平位置固定,位置自动记忆) |
| 双击 / 悬停 | 展开快捷菜单(需在设置中启用,见「插件系统」) |
| 鼠标悬停 | 按钮高亮反馈 |
✓ 的为当前镜像,选择后自动切换并同步到左侧按钮。默认镜像源:gh-proxy.com、gh-proxy.org、ghproxy.com(mirror.ghproxy.com)。
自定义镜像:设置面板 → 插件管理 → 展开「GitHub 下载加速」→「镜像列表」,每行一个、格式「名称 URL」(只写 URL 则名称取主机名),保存后立即生效;留空恢复默认。
打开方式:长按右下角火箭按钮 600ms。
| 分组 | 配置项 | 说明 |
|---|---|---|
| GitHub 加速设置 | 启用 GitHub 加速下载按钮 | 关闭后 GitHub 页面不再显示镜像加速按钮 |
| 返回顶部按钮设置 | 启用全局返回顶部按钮 | 关闭后所有页面不显示按钮 |
| 关闭智能隐藏 | 开启后按钮不随滚动隐藏(等价于「总是显示」) | |
| 只显示图标 | 背景/边框/阴影透明,仅保留图标与点击区域 | |
| 图标设置 | 图标大小倍率 | 输入倍率(小数,截断 1 位,范围 0.6~4)或点「2倍」「默认」快捷设置 |
| 按钮动画 | 图标动画预设:无 / 呼吸 / 旋转 / 脉冲(GIF 动图不受影响;系统开启「减少动态效果」时自动禁用) | |
| 旋转 | 主按钮旋转角度 0~360°(步进 15,带「0」重置按钮) | |
| 自定义按钮图标和缩放 | 输入框 + 「+」 | 输入 base64 添加图标到列表并预览 |
| 「🖼」上传图片 | 选择图片自动转 base64 并直接加入列表 | |
| 图标列表 | 点击预览切换、× 移除、默认图标常驻末尾 | |
| 快捷菜单设置 | 启用快捷功能 | 开启后桌面 hover 展开菜单、触摸端双击兜底(默认关闭) |
| 设置快捷菜单数量 | 槽位数量 2~8 | |
| 快捷菜单槽位绑定 | 逐槽位选择要绑定的插件(保存后生效) | |
| 在以下站点禁用 | 站点列表 | 每行一个域名,填写后这些站点不显示按钮;也可点「添加当前网站」 |
底部按钮:取消(放弃本次修改)/ 保存(立即生效,无需刷新页面)。
插件是脚本的功能扩展单元:完整 JS 逻辑 + 声明式 JSON UI,在权限白名单内运行(未声明的 API 不可用)。
以下三个插件随脚本自带(编译进主脚本,不受页面 CSP 限制,GitHub 等严格站点也可用),不可卸载:
| 插件 | 版本 | 类型 | 权限 | 功能 |
|---|---|---|---|---|
| GitHub 下载加速 | v1.0.0 | feature |
dom, storage | GitHub 下载链接旁注入「下载 ▾」镜像分体按钮;镜像列表可配置 |
| 快捷翻译 | v0.2.0 | shortcut |
selection, network, storage | 选中网页文本后激活:中英互译(MyMemory 免费接口),结果以 Toast 展示、可一键复制 |
| 提醒助手 | v1.5.0 | feature |
storage, broadcast | 按规则定时弹提醒,所有打开的标签页同步弹出;详见下方规则格式 |
设置面板 → 插件管理 页签:
.js/.txt 插件文件(内容填入输入框),或直接粘贴插件代码(含 ==Plugin== 头),点「立即安装」。安装成功后出现在插件列表。shortcut)插件;一个插件只能绑定一个槽位。general.quickMenu)。提醒助手 — 「提醒列表」每行一条,格式 规则|通知内容:
每 45 分钟|⏰ 到点啦:起来喝水
09:30|📅 到点啦:晨会
每周一,周五 18:00|💼 到点啦:写周报
2026-09-20 15:00|✅ 该交材料了
支持的规则:每 N 秒/分钟/小时(循环)、HH:MM(每天)、每周X[,周Y…] HH:MM(每周)、YYYY-MM-DD HH:MM(一次性)。另有两个设置控制通知自动关闭秒数(0 = 不自动关闭)。
快捷翻译 — 「MyMemory API Key」(可选):留空使用匿名接口(有每日配额),填写后提升配额。
GitHub 下载加速 — 「镜像列表」:见 §3 自定义镜像。
能够借助AI的能力,快速开发你想要的插件并立即在你本地尝试新功能。
name: rocket-btn-plugin
description: 为「火箭按钮」用户脚本(ScriptCat/Tampermonkey)编写插件。当用户想给火箭按钮添加快捷功能(翻译、剪贴板、网页工具等)、询问该脚本的插件开发协议、或提到 "ctx API / 插件权限 / declareSettings / ==Plugin==" 等关键词时使用。插件是符合 ==Plugin== 头 + ctx 白名单 API 协议的独立 JS 文件,供用户导入脚本设置面板使用。
给「火箭按钮」脚本写可导入的插件。插件 = 单文件 JS + ==Plugin== 头 + ctx 白名单 API;UI 用 toast / 声明式 JSON,不直接碰 DOM。
// ==Plugin==
// @id com.example.mytool ← 必填,唯一
// @name 我的工具 ← 必填
// @version 0.1.0
// @type shortcut | feature ← shortcut=点菜单槽位激活; feature=后台(监听选中等)
// @permission selection,network ← 用了哪些 ctx API 就声明哪些
// @description 一句话说明
// ==/Plugin==
> 💡 **建议加 `@icon`**:data URL(base64 或 SVG)或图片 URL 均可。不加的话快捷菜单/插件列表会显示「首字占位」灰块(功能不受影响,但不美观)。
(function (ctx) {
ctx.onActivate(async () => {
/* shortcut 激活时执行 */
});
})(ctx);
| 能力 | API | 权限 |
|---|---|---|
| 读选中文本 | ctx.getSelectedText() |
selection |
| 剪贴板 | ctx.copyText(t) / ctx.readClipboard() |
clipboard |
| 轻提示 | ctx.toast(text, opts) → id |
无 |
| 关提示 | ctx.dismissToast(id) |
无 |
| 发请求(绕过 CORS) | ctx.httpRequest(url, opts) → {ok,status,text} |
network |
| 设置读写 | ctx.getValue(k, d) / ctx.setValue(k, v)(与 declareSettings 同 key,跨域名一致) |
storage |
| 声明设置项 | ctx.declareSettings([...]) |
无 |
| 生命周期 | ctx.onActivate(cb) / ctx.onDispose(cb) |
无 |
| 选中联动 | ctx.onSelectionChange(cb) |
selection |
| 面板 / 菜单 | ctx.openPanel(req) / ctx.openMenu() |
ui |
| 声明式 UI | ctx.ui.render(...) |
ui |
toast opts:{ title, type:'info'|'success'|'warning'|'error', timeout:ms(0=不自动关), copyable:bool }
// ==Plugin==
// @id com.example.md-quote
// @name 复制为引用
// @type shortcut
// @permission selection,clipboard
// ==/Plugin==
(function (ctx) {
ctx.onActivate(async () => {
const t = ((await ctx.getSelectedText()) || "").trim();
if (!t) {
ctx.toast("未选中内容", { type: "warning" });
return;
}
const quote = t
.split("\n")
.map((l) => "> " + l)
.join("\n");
await ctx.copyText(quote);
ctx.toast("已复制为引用", { type: "success" });
});
})(ctx);
@permission 就声明哪个(速查表第 3 列);漏了它是 undefined,调用报错。(function(ctx){...}) 只注册回调;长任务用 async/setTimeout,别写同步死循环。})(ctx);。onDispose 里清理。toast 会刷屏——建议去重(如记录已通知的 key)并告知用户策略。页面加载早期(document-end 后立即)toast 可能被页面刷新/继续加载打断(一闪而过)——建议延迟通知(setTimeout 1-2s)或加长 timeout。@icon:data URL(SVG/base64)或图片 URL。不加则快捷菜单/插件列表/toast 显示首字占位灰块(功能不受影响但不美观)。label 写清用途与取值范围(如「每页条数(1-100)」),并给出合理 default;不要留空标签或含义不明的 key。@permission(少一个用不了,多一个用户会顾虑)。timeout: 0)用 dismissToast 及时替换,别堆一堆。ctx.toast(..., { type: "error" })),不要把原始报错抛给用户。onDispose 中释放,避免长期驻留。@description 里更佳)。==Plugin== 头含 id/name/type/permission})(ctx);@icon(否则显示首字占位)以下是完整协议细节与示例(普通插件用不到时可跳过):
按 manifest 权限提供;未声明的 API 在 ctx 上是 undefined(调用报错,被主脚本隔离)。
| API | 说明 | 权限 |
|---|---|---|
ctx.manifest |
只读插件元数据 | - |
ctx.getSelectedText() → Promise |
读取选中文本 | selection |
ctx.readClipboard() → Promise |
读剪贴板 | clipboard |
ctx.copyText(text) → Promise |
写剪贴板 | clipboard |
ctx.getValue<T>(k, d) → T |
读插件设置(sc: 前缀存储,域名级) |
storage |
ctx.setValue(k, v) |
写插件设置 | storage |
ctx.openPanel(req) → Promise<{ok,reason?}> |
请求打开面板(主脚本策略决定) | ui |
ctx.closePanel() |
关闭面板 | ui |
ctx.openMenu() / ctx.closeMenu() |
打开/关闭快捷菜单 | ui |
ctx.toast(text, opts?) → id |
轻提示;返回 id 供 dismissToast | - |
ctx.dismissToast(id) |
关闭指定 toast | - |
ctx.httpRequest(url, opts?) → Promise<{ok,status,text}> |
HTTP(GM_xmlhttpRequest 桥,绕过 CORS) | network |
ctx.onActivate(cb) |
shortcut 激活时调用 | - |
ctx.onDeactivate(cb) |
停用时调用 | - |
ctx.onEvent(name, cb) |
声明式 UI 事件 | - |
ctx.onSelectionChange(cb) |
选中文本变化(feature 联动) | selection |
ctx.onDispose(cb) |
卸载清理 | - |
ctx.declareSettings([...]) |
声明设置项(设置面板渲染表单) | - |
ctx.ui.render(kind, spec) / ctx.ui.close() |
声明式 UI | ui |
ctx.toast(text, {
title: "附加标题", // 默认标题=插件名;传了则拼接「插件名 附加标题」
type: "success", // info | success | warning | error(颜色区分)
timeout: 2500, // ms;0 = 不自动关闭(需手动点叉)
copyable: true, // 显示复制按钮(一键复制 text)
reverse: false, // 堆叠顺序:true 最新在底部
broadcast: true, // 跨标签页:所有打开的标签页都弹出(需在 @permissions 声明 broadcast,否则降级为当前页)
});
ctx.getValue(key)(翻译 apiKey 即如此,无需刷新);GM_addValueChangeListener("sc:plugin:<id>.settings", ...) 热更新(GitHub 镜像列表即如此)。ctx.toast(text, { broadcast: true })(所有标签页弹出)。unsafe-eval 的站点软沙箱不可用(用户插件会「加载失败」),内置插件不受影响。undefined,调用会抛 TypeError(被 try-catch 兜底 → 插件「加载失败」或回调失败,不影响主脚本)。例外:broadcast 未声明时仅降级为当前页 toast,不报错。MutationObserver。broadcast toast。unsafe-eval 站点用户插件软沙箱不可用(加载失败),内置插件不受影响。用户插件受软沙箱限制(CSP + 不能碰全局 GM);如果你要写的功能必须跑在 GitHub 等严格 CSP 站点,或需要 GM_addValueChangeListener 做设置热更新,就写成内置插件(编译进主脚本的真实 TS 模块,init(ctx) 直接调用)。
apps/rocket_btn/src/builtin/<name>.ts,导出同步函数:export function initMyPlugin(ctx: Record<string, unknown>): void {
const c = ctx as {
declareSettings: (s: unknown[]) => void;
getValue: <T>(k: string, d: T) => T;
setValue: (k: string, v: unknown) => void;
toast: (t: string, o?: Record<string, unknown>) => number;
onDispose: (cb: () => void) => void;
};
c.declareSettings([{ key: "list", label: "配置(每行一项)", type: "textarea", default: "" }]);
let timer: ReturnType<typeof setInterval> | null = null;
// ...主体逻辑:定时器/监听器都在这里建立
// 内置插件可直接用全局 GM_*:设置热更新(保存设置不会重启主体,见「开发边界」第 1 条)
let listenerId: number | null = null;
try {
if (typeof GM_addValueChangeListener === "function") {
listenerId = GM_addValueChangeListener(
"sc:plugin:com.flowsaro.myplugin.settings", // 注意:sc: + plugin:<id>.settings
() => {
/* 重新读 ctx.getValue 并重建状态 */
},
);
}
} catch {}
c.onDispose(() => {
if (timer) clearInterval(timer);
if (listenerId != null && typeof GM_removeValueChangeListener === "function") {
try {
GM_removeValueChangeListener(listenerId);
} catch {}
}
});
}
apps/rocket_btn/src/builtin-plugins.ts 的 BUILTIN_PLUGINS 加一条(id 用 com.flowsaro.*,必带 builtin: true,permissions 与实际调用的 ctx API 一致):{
manifest: {
id: "com.flowsaro.myplugin",
name: "我的插件",
version: "1.0.0",
type: "feature", // feature 页载即预热执行;shortcut 需绑定菜单槽位
permissions: ["storage", "broadcast"],
icon: MY_ICON, // data:image/svg+xml,...(不加则显示首字占位)
description: "一句话说明(含权限与配置方式)",
builtin: true,
},
init: initMyPlugin,
}
apps/rocket_btn/src/builtin/__tests__/<name>.test.ts,用桩 ctx(vi.useFakeTimers() 控时):const ctx = {
declareSettings: (s: unknown[]) => settings.push(...(s as never[])),
getValue: <T>(k: string, d: T): T => (k in store ? (store[k] as T) : d),
setValue: (k: string, v: unknown) => {
store[k] = v;
},
toast: () => 0,
onDispose: (cb: () => void) => disposeCbs.push(cb),
};
initMyPlugin(ctx as unknown as Record<string, unknown>);
GM_*(用户插件的软沙箱不能);但也建议 typeof GM_xxx === "function" 兜底 + try/catch,宿主不提供时降级而非崩。text 是单行 <input>:多行内容(规则列表、镜像列表)必须声明 type: "textarea",否则用户输不进换行。type: "feature" 会页面加载即预热执行(preheatFeatures()),所以主体要做站点自判断(如 if (!/(^|\.)github\.com$/.test(location.hostname)) return;)并在开头就 declareSettings(要在 return 之前调用,否则非目标站点用户看不到设置项)。broadcast 是独立权限:ctx.toast(text, { broadcast: true }) 让所有标签页同步弹出;未声明该权限时静默降级为当前页,不报错。定时提醒类插件建议加。const c = ctx as {...} 显式声明用到的方法,不用 any(仓库 TS 严格模式)。pnpm test(含内置插件单测)+ pnpm type-check + pnpm lint + pnpm build:all;产物 dist/rocket_btn/rocket_btn.js 约 115 KB 量级(每加一个内置插件约 +5 KB)。const res = await ctx.httpRequest(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
data: "q=hello",
timeout: 10000, // 默认 10000
});
// res = { ok: boolean, status: number, text: string }(text 为响应体,JSON 需自行 parse)
ctx.declareSettings([
{
key: "apiKey", // 存储 key(ctx.getValue('apiKey'))
label: "API Key(可选)", // 表单标签
type: "text", // 见下方「支持的表单控件」
placeholder: "留空使用默认",
default: "", // 默认值
options: [{ label: "A", value: "a" }], // select 时必填
},
]);
| type | 渲染 | 备注 |
|---|---|---|
text |
单行输入 | 普通文本 |
password |
密码输入 | 敏感信息 |
textarea |
多行输入 | 多值/长文本(如镜像列表,每行一项) |
select |
下拉选择 | 需提供 options: [{label,value}] |
checkbox |
复选框 | default 传 boolean |
⚠️ 设置项只支持上述 5 种表单控件,不能用 JSON UI 原语(
panel/button/list/badge/...仅用于ctx.ui.render面板)。 需要复杂交互的设置 → 用「设置项 + 一个按钮触发面板」的组合。
导入(解析 manifest 存 GM)
→ 首次使用(懒加载执行主体,注册回调)
→ shortcut:绑定槽位点击 → activate
→ feature:预热执行 + selection 联动等
→ dispose(卸载/覆盖更新时):执行 onDispose 清理
插件用 JSON 声明 UI(需要 ui 权限),主脚本负责渲染,插件不碰 DOM。
ctx.ui.render("panel", {
type: "panel",
title: "标题",
body: [
{ type: "text", content: "内容", style: { size: "lg" } },
{ type: "divider" },
{ type: "button", label: "复制", event: "copy" },
],
});
// 事件回调
ctx.onEvent("copy", () => ctx.copyText("..."));
panel / text / button / input / select / checkbox / textarea / list / tabs / divider / badge / icon / image / link
style: { weight: 'normal'|'bold', color: 'primary'|'secondary'|'accent'|'danger', size: 'sm'|'md'|'lg' }
{
type: 'panel',
title: '设置',
body: [
{ type: 'text', content: '说明文字', style: { size: 'sm', color: 'secondary' } },
{ type: 'divider' },
{
type: 'list',
items: [
{ type: 'button', label: '执行', event: 'run' },
{ type: 'button', label: '复制', event: 'copy' },
],
},
],
}
多数插件用
ctx.toast就够了;只有需要复杂展示/交互时才用ui.render('panel')。
// ==Plugin==
// @id com.example.translate
// @name 快捷翻译
// @version 0.1.0
// @type shortcut
// @permission selection,network,storage
// @description 翻译选中文本(MyMemory 免费接口,可选 API Key 提配额)
// ==/Plugin==
(function (ctx) {
ctx.declareSettings([
{ key: "apiKey", label: "MyMemory API Key(可选)", type: "text", placeholder: "留空匿名" },
]);
ctx.onActivate(async () => {
const t = ((await ctx.getSelectedText()) || "").trim();
if (!t) {
ctx.toast("未选中内容", { type: "warning" });
return;
}
const loadingId = ctx.toast("翻译中…", { type: "info", timeout: 0 }); // 常驻
try {
const isCn = /[\u4e00-\u9fa5]/.test(t);
const langpair = isCn ? "zh-CN|en" : "en|zh-CN";
let url =
"https://api.mymemory.translated.net/get?q=" +
encodeURIComponent(t) +
"&langpair=" +
langpair;
const key = ctx.getValue("apiKey", "");
if (key) url += "&de=" + encodeURIComponent(key);
const res = await ctx.httpRequest(url);
if (!res.ok) {
ctx.dismissToast(loadingId);
ctx.toast("失败(" + res.status + ")", { type: "error" });
return;
}
const data = JSON.parse(res.text);
const result = data && data.responseData && data.responseData.translatedText;
ctx.dismissToast(loadingId);
if (result) ctx.toast(result, { type: "success", copyable: true, timeout: 0 });
else ctx.toast("未获取到结果", { type: "warning" });
} catch (e) {
ctx.dismissToast(loadingId);
ctx.toast("翻译失败", { type: "error" });
}
});
})(ctx);
// ==Plugin==
// @id com.example.selection-notify
// @name 选中提示
// @type feature
// @permission selection
// ==/Plugin==
(function (ctx) {
let last = "";
ctx.onSelectionChange((text) => {
const t = (text || "").trim();
if (!t || t === last) return;
last = t;
const preview = t.length > 24 ? t.slice(0, 24) + "…" : t;
ctx.toast("选中:" + preview, { timeout: 2000 });
});
})(ctx);
Q: 怎么给Github链接加速?
Q: 按钮不显示?
Q: 智能隐藏模式下按钮看不见,但鼠标滑过去会弹出快捷菜单?
Q: 怎么恢复默认图标?
Q: 保存后要刷新页面吗?
Q: 按钮位置能左右移动吗?
Q: 怎么让按钮一直在屏幕上?
Q: 不想看到按钮背景/边框,只要图标?
Q: 图标太小/太大?
Q: 插件导入后提示「加载失败」?
new Function):GitHub 等严格 CSP 站点上用户插件可能无法运行,属预期限制;内置插件不受影响。脚本会以 Toast 提示加载失败原因。Q: 快捷菜单里点「+」没反应?
GM_getValue / GM_setValue 支持);Shadow DOM 挂载需支持 attachShadow(Chrome 53+ / Firefox 63+ / Safari 10+)http:// / https:// 页面(@include * 兜底)All Rights Reserved.