从一个熟悉的弹窗说起
每个写前端的人,大概都经历过这样一幕:表单提交成功,要给用户一个反馈,于是敲下 alert("保存成功")。功能上挑不出毛病,体验上它几乎是"反面教材"的具象化——浏览器原生样式,各个浏览器长得不一样但都不好看;弹出来的瞬间整个页面被锁死,用户除了点确定没有第二条路;提示一旦多了,还得一个一个点掉。
于是下一步通常是找一个 toast 组件。自己从零写一个带动画、带图标、能自动关闭的提示组件,认真做下来几百行起步;从组件库拽一个,前提是项目里本来就有 Vue 或 React 全家桶。可如果手上只是一个纯 HTML 静态页、一个还在跑 jQuery 的老项目,或者一段跑在别人页面里的油猴脚本,为了弹个提示把组件库整个搬进来,怎么算都有点亏。
Qmsg 就是冲着这个缝隙来的:零依赖的原生 JS 消息提示插件,一行 <script> 引入,几行代码就能用上带动画、带图标、位置可调的 toast。
Qmsg 是什么
先交代背景。Qmsg 最初出自开发者"或许吧"(jesseqin)之手,原版是一个 jQuery 插件,发布在 jQuery 插件库上。后来 WhiteSevs 用 TypeScript 把它整个重构了一遍,发布到 npm,也就是今天 GitHub 上的Qmsg。所以严格说它是一份"重构版":功能逻辑延续原版,代码全部用 TS 重写,附带完整的类型定义。
几个基本盘:当前版本 1.7.2,MIT 协议,npm 上已发布 41 个版本,运行时零依赖。体积方面,我把 UMD 压缩版下载下来看了一眼,22,450 字节,22KB 出头——无论引到哪里,都几乎不构成负担。
它提供五种消息类型(info、warning、error、success、loading)、九个弹出方位、三十来个配置项,以及一组实例方法。下面挨个说。
![图片[1]-告别alert():聊聊优雅的页面消息提示插件Qmsg-风的记忆](https://woaif.cn/wp-content/uploads/2026/09/0b25ac0b8320260905202137.webp)
几个打动我的设计
默认跑在 Shadow DOM 里
这是我最想单独拿出来讲的一点。Qmsg 默认把消息渲染进 ShadowRoot(配置项 useShadowRoot,默认 true),而不是直接挂在 document.body 下。
好处是双向的样式隔离:宿主页面的 CSS 不会污染你的消息样式,你的消息样式也不会泄漏出去影响页面。对普通项目来说这算锦上添花,但对油猴脚本是实打实的刚需——脚本跑在别人的页面里,对方页面可能有一行 .toast { display: none } 或者更刁钻的选择器,直接挂 body 的方案随时可能翻车,Shadow DOM 把这种风险隔在了门外。
打个比方:普通方案是把提示卡片直接搬进别人家客厅,主人家的装修规则(页面 CSS)随时能管到你头上;Shadow DOM 相当于在客厅里隔出一个独立的小房间,里外的规矩互不干涉。真想调整样式也有口子:style 配置项可以往 ShadowRoot 里追加自定义 CSS,customClass 可以给消息打标记,不需要跟源码较劲。
交互上的小心思
几个细节能看出作者是真的在用这个库:
- 鼠标悬停会暂停自动关闭的倒计时,移开后再继续(
listenEventToPauseAutoClose)。用户还没读完的消息,不会在眼前硬生生消失。 maxNums限制同屏消息数量,默认 5 条。手抖连点十次,页面也不会变成 toast 刷屏现场。- 来回切换浏览器标签页时,消息会自动关闭(
listenEventToCloseInstance),避免用户回到页面时面对一堆过时提示。
这三个开关默认全开,一行配置都不用写就是这样的行为。
TypeScript 重构带来的体验
整个库是 TS 写的,在编辑器里敲 Qmsg. 会得到完整的方法提示,配置对象也有类型检查。写脚本的时候不用翻文档猜参数名——toast 这种东西,写的时候没人想停下来查文档,这一点比想象中更能省心。
五分钟上手
三种引入方式
模块化项目走 npm:
# 任选其一
pnpm i qmsg
npm i qmsg
import Qmsg from "qmsg";
Qmsg.info("登录成功");
纯 HTML 页面走 CDN,一个 script 标签:
<script src="https://fastly.jsdelivr.net/npm/qmsg@latest/dist/index.umd.min.js"></script>
<script>
Qmsg.info("这是提示消息");
</script>
油猴脚本则在头部加一行 @require:
// @require https://fastly.jsdelivr.net/npm/qmsg@1.6.0/dist/index.umd.min.js
三条路殊途同归,拿到一个全局的 Qmsg 对象。
五种消息类型
一行一个,语义清晰:
Qmsg.info("常规提示,中性信息");
Qmsg.warning("注意,这里可能有问题");
Qmsg.error("操作失败了");
Qmsg.success("保存成功");
Qmsg.loading("加载中,别急");
实际长这样(在真实页面里引入后截的图,五种类型会从顶部依次堆叠弹出):
![图片[2]-告别alert():聊聊优雅的页面消息提示插件Qmsg-风的记忆](https://woaif.cn/wp-content/uploads/2026/09/275dc2e95820260905202137.webp)
九宫格弹出位置
默认从顶部弹出。position 配置项支持九个方位:topleft、top、topright、left、center、right、bottomleft、bottom、bottomright,大小写不敏感。
Qmsg.info("右下角弹出", { position: "bottomright" });
九个方位各弹一条的效果:
![图片[3]-告别alert():聊聊优雅的页面消息提示插件Qmsg-风的记忆](https://woaif.cn/wp-content/uploads/2026/09/f33ebd267120260905202136.webp)
全局配置
如果一批消息要共享同样的行为,用 Qmsg.config() 统一设置,后面的调用就不用每次都传一遍:
Qmsg.config({
showClose: true, // 显示右上角关闭图标
timeout: 5000, // 自动关闭时长 5s
});
常用配置速查
配置项总共三十来个,完整清单建议直接看Qmsg,这里挑日常用得最多的:
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
content | string | 空 | 消息内容 |
isHTML | boolean | false | 内容是否按 HTML 渲染 |
timeout | number | 2500 | 自动关闭的等待时长,毫秒 |
autoClose | boolean | true | 是否自动关闭 |
showClose | boolean | false | 是否显示右上角关闭图标 |
showIcon | boolean | true | 是否显示左侧图标 |
position | string | top | 弹出方位,九选一 |
maxNums | number | 5 | 同屏最多显示的消息条数 |
animation | boolean | true | 是否启用弹出动画 |
onClose | Function | null | 关闭时触发的回调 |
style | string | 空 | 追加进 ShadowRoot 的自定义 CSS |
customClass | string | 空 | 挂到消息上的自定义类名 |
useShadowRoot | boolean | true | 是否渲染进 ShadowRoot |
zIndex | number | 50000 | 消息的 z-index 层级 |
实例方法与 loading 的正确姿势
每次调用 Qmsg.info() 之类的方法,都会返回一个消息实例。拿住它,就能在消息弹出之后继续操作:
var aMsg = Qmsg.info("这是一条 info 消息");
aMsg.setText("改一下文案"); // 修改文本
aMsg.setHTML("<b>支持 HTML</b>"); // 修改为 HTML
aMsg.close(); // 关闭,触发 onClose 回调
aMsg.destroy(); // 销毁,不触发回调
全局还有个 Qmsg.closeAll(),把页面上所有消息一锅端,包括设置了 autoClose: false 的。
这里有个值得单独拎出来的设计:loading 默认是 autoClose: false,不会自动关闭,需要手动 close()。原因不难想——加载时长的决定权在业务手里,库里没法替你猜。常见的写法是配合异步任务:
var loading = Qmsg.loading("正在提交");
// ...做点什么,比如一个 fetch
setTimeout(function () {
loading.close();
Qmsg.success("提交完成");
}, 2000);
如果确实想让 loading 自动消失,显式传 autoClose: true 覆盖默认值即可。
油猴脚本作者的宝藏
![图片[4]-告别alert():聊聊优雅的页面消息提示插件Qmsg-风的记忆](https://woaif.cn/wp-content/uploads/2026/09/ffb6a4eadc20260905202139.webp)
Qmsg 在油猴圈子里出镜率不低,npm 的关键字里干脆直接写着 TamperMonkey、ScriptCat 和 VioletMonkey。原因在前文其实都埋了:UMD 格式天然适配 @require,一行引入;Shadow DOM 隔离,不怕宿主页面的样式干扰。
一个最短可用的脚本骨架长这样:
// ==UserScript==
// @name 我的第一个 Qmsg 脚本
// @match https://example.com/*
// @require https://fastly.jsdelivr.net/npm/qmsg@1.6.0/dist/index.umd.min.js
// @grant none
// ==/UserScript==
(function () {
Qmsg.config({ timeout: 3000 });
// 页面加载完,给个提示
window.addEventListener("load", function () {
Qmsg.success("脚本已就绪");
});
})();
比起脚本圈流行的另一种做法——往页面里注入几行 div 和 CSS 自己拼提示——Qmsg 的好处是样式稳定、类型齐全。而且作者维护得勤,写作本文时仓库最近仍有提交,兼容性上的坑大概率不需要自己踩。
顺手看了一眼源码
动笔前我翻了翻它的 src 目录,结构相当清爽,从命名就能对上职责:
| 模块 | 职责 |
|---|---|
QmsgCore | 消息创建与生命周期主流程 |
QmsgInst / InstHandler / InstStorage | 实例对象、实例操作与存储管理 |
QmsgAnimation | 进出场动画 |
QmsgCSS | 样式注入(ShadowRoot 内) |
QmsgEvent | 事件逻辑:悬停暂停、标签页切换关闭等 |
QmsgIcon | 内置 SVG 图标 |
QmsgDefaultConfig | 默认配置 |
CompatibleProcessing | 运行环境兼容处理 |
构建链是 Rollup,产出 UMD、ESM、CJS 三种格式外加一份 index.d.ts;lint 用 oxlint,浏览器兼容靠 browserslist 和 eslint-plugin-compat 把关。对一个体量不大的工具库来说,这套工程化属于"该有的都有",不是那种写着玩、半年就断更的画风。
什么情况下我会选它
总结一下选型判断。适合的场景:
- 纯静态页面、没有任何构建链的传统项目,需要一个像样的提示;
- 油猴、脚本猫这类用户脚本,要在别人的地盘上弹消息;
- 对依赖体积敏感,或者干脆有"零依赖洁癖"的项目。
不那么适合的场景:项目已经在用 Element Plus、Ant Design 或 shadcn 这类组件生态,提示组件用生态内的就好——组件库的 toast 能跟主题变量联动,还能吃到统一的设计语言,这是 Qmsg 替代不了的。
没有组件库的地方,Qmsg 是把"像样的提示"做到最便宜的方式之一;写油猴脚本的话,它几乎是无脑之选。











暂无评论内容