htmx-indicator:加载状态指示器
工作机制
htmx 在发出请求的期间,会自动向触发元素及其最近的父元素添加 htmx-request CSS 类;同时,会向 hx-indicator 指向的元素(或触发元素自身)添加 htmx-request 类并显示它。
默认情况下,带有 htmx-indicator 类的元素是不可见的(opacity: 0),当请求进行中时变为可见(opacity: 1)。
/* htmx 内置的 CSS(简化版)*/
.htmx-indicator {
opacity: 0;
transition: opacity 200ms ease-in;
}
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
opacity: 1;
}
/* 请求时按钮变灰 */
.htmx-request {
cursor: not-allowed;
opacity: 0.6;
}
基础用法
<!-- 方式1:指示器是触发元素的后代 -->
<button hx-get="/api/data" hx-target="#result">
<span>加载数据</span>
<span class="htmx-indicator"> ⏳</span>
</button>
<!-- 方式2:hx-indicator 指向外部元素 -->
<button
hx-get="/api/data"
hx-target="#result"
hx-indicator="#global-spinner"
>加载</button>
<div id="global-spinner" class="htmx-indicator spinner">
加载中...
</div>
<!-- 方式3:表单提交时按钮显示加载状态 -->
<form hx-post="/api/save">
<input name="title" required/>
<button type="submit">
<span class="btn-text">保存</span>
<span class="htmx-indicator">保存中...</span>
</button>
</form>
自定义加载动画
/* CSS 旋转动画 spinner */
.spinner {
display: inline-block;
width: 16px; height: 16px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 全局顶部进度条(类似 GitHub/YouTube) */
#progress-bar {
position: fixed; top: 0; left: 0;
height: 3px; background: #3d9cf0;
width: 0; transition: width 0.3s;
z-index: 9999;
}
.htmx-request #progress-bar {
width: 70%;
transition: width 2s ease-out;
}
hx-confirm:确认对话框
hx-confirm 在请求发出前弹出一个原生 confirm() 对话框。只有用户点击"确定"后才发出请求,点击"取消"则中断操作。
<!-- 简单确认 -->
<button
hx-delete="/api/account"
hx-confirm="确定要永久删除账号吗?此操作无法撤销!"
>
删除账号
</button>
<!-- 使用模板语法(需要服务端或 JS 动态生成) -->
<button
hx-delete="/api/files/report.pdf"
hx-confirm="确认删除文件 report.pdf?"
hx-target="closest .file-row"
hx-swap="outerHTML"
>
删除
</button>
自定义确认对话框(替代原生 confirm)
原生 confirm() 样式简陋,htmx 支持通过事件拦截实现自定义对话框:
<!-- 自定义对话框 -->
<dialog id="confirm-dialog">
<p id="confirm-msg"></p>
<button id="confirm-yes">确认</button>
<button onclick="this.closest('dialog').close()">取消</button>
</dialog>
<script>
// 拦截 htmx 的确认事件,显示自定义对话框
document.addEventListener('htmx:confirm', function(e) {
e.preventDefault(); // 阻止默认的 confirm() 弹窗
const dialog = document.getElementById('confirm-dialog');
document.getElementById('confirm-msg').textContent = e.detail.question;
dialog.showModal();
document.getElementById('confirm-yes').onclick = function() {
dialog.close();
e.detail.issueRequest(true); // 真正发出请求
};
});
</script>
hx-disable:禁用处理
hx-disable 属性告诉 htmx 忽略该元素及其子元素上的所有 hx-* 属性,就像 htmx 不存在一样。这对于在某些区域保持传统表单行为、或者在内容编辑器中防止意外触发非常有用:
<!-- 整个区域禁用 htmx(如富文本编辑器内容) -->
<div hx-disable>
<!-- 这里的 hx-* 属性完全被忽略 -->
<button hx-get="/this-will-NOT-work">不会触发</button>
</div>
<!-- 排除某个表单不使用 htmx,保留原生提交 -->
<body hx-boost="true">
<!-- 大多数表单通过 htmx 提交 -->
<form hx-post="/api/data">...</form>
<!-- 这个表单使用传统提交(文件下载、OAuth 等) -->
<form action="/auth/google" method="POST" hx-disable>
<button>Google 登录</button>
</form>
</body>
请求队列管理
当用户快速多次触发同一个请求时(如连续点击按钮),htmx 的队列策略决定如何处理并发请求:
<!-- 防重复提交:第一次点击后锁定,直到服务器响应 -->
<button
hx-post="/api/order/submit"
hx-trigger="click queue:first"
hx-indicator="#submit-spinner"
>
下单
<span id="submit-spinner" class="htmx-indicator">处理中...</span>
</button>
<!-- 搜索框:只保留最新输入的搜索 -->
<input
hx-get="/search"
hx-trigger="keyup delay:200ms queue:last"
hx-target="#results"
/>
htmx 生命周期事件
htmx 在请求生命周期的各阶段触发自定义 DOM 事件,可以用 JavaScript 监听这些事件实现精细控制:
event.preventDefault() 可以取消请求。<script>
// 全局错误处理:服务器错误时显示 toast 通知
document.addEventListener('htmx:responseError', function(e) {
const status = e.detail.xhr.status;
const msg = status === 422
? '数据验证失败,请检查输入'
: status === 403
? '没有权限执行此操作'
: `请求失败 (${status})`;
showToast(msg, 'error');
});
// 请求完成后重新初始化第三方组件
document.addEventListener('htmx:afterSwap', function(e) {
// 例如重新初始化 Prism.js 代码高亮
if (typeof Prism !== 'undefined') {
Prism.highlightAllUnder(e.detail.elt);
}
});
</script>
htmx 默认只在 2xx 响应时更新 DOM(因为 4xx/5xx 表示错误)。若希望 422 验证错误的响应内容(错误提示 HTML)也能更新 DOM,需要在 htmx:beforeSwap 事件中手动允许:
document.addEventListener('htmx:beforeSwap', function(e) {
if (e.detail.xhr.status === 422) {
e.detail.shouldSwap = true; // 允许更新 DOM
e.detail.isError = false; // 不触发 responseError
}
});
htmx 事件系统与 JavaScript 集成的核心要点:① htmx 生命周期事件遵循 htmx:beforeRequest → htmx:afterRequest → htmx:afterSwap → htmx:afterSettle 顺序,可在任意环节挂载行为;② hx-on::after-request 是内联事件的简写语法(双冒号 = htmx: 前缀),适合简单操作如提交后清空表单;③ htmx.trigger(el, 'eventName') 可从 JS 主动触发元素的 htmx 请求;htmx.ajax() 可完全用 JS 控制 htmx 请求;④ 服务器通过 HX-Trigger 响应头触发客户端事件,实现后端→前端通信(如弹出 Toast 通知);⑤ htmx 默认只在 2xx 时 swap DOM;422 验证错误需在 htmx:beforeSwap 中手动设置 shouldSwap = true;⑥ htmx:afterSwap 事件中重新初始化第三方库(如代码高亮、日期选择器),因为新注入的 DOM 不会自动触发库的初始化。