触发
「为什么只有 click 和 submit 才能触发请求?」 hx-trigger 让任何事件——甚至时间本身——成为请求的扳机。
事件与动词
缺省触发事件因元素而异:一般元素是 click,表单元素是 submit,输入框带 hx-get 时是 change。hx-trigger 可以改写它,且支持逗号分隔的多个事件:
<div hx-get="/panel" hx-trigger="mouseenter, keyup[getKey()='Escape']">…</div>触发修饰符
| 写法 | 含义 |
|---|---|
once | 只触发一次,如页面进场动画、首访引导 |
changed | 值确实变了才触发——输入框的救星 |
delay:300ms | 防抖:停手 300ms 后才发请求 |
throttle:2s | 节流:至多每 2s 发一次 |
from:selector | 只听特定元素上冒泡来的事件(跨组件协作的关键) |
target:selector | 只响应落在特定元素上的事件(事件委托) |
consume | 阻止事件继续冒泡给其他 htmx 元素 |
keyup[codes.KeyA] | 条件过滤:方括号内写 JS 表达式,为真才触发 |
delay 与 throttle 别混用:前者等事件停下,后者按节拍放行。搜索框几乎总该是 keyup changed delay:... 三连。特殊事件
load——元素进入 DOM 处理完毕即触发,惰性加载的标准姿势;revealed——元素滚入视口才触发,无限滚动与懒图的开关;intersect——基于 IntersectionObserver 的变体,可配root、threshold;every 10s——轮询定时器,仪表盘与收件箱角标的心跳;onload——每次服务器换入该元素后触发,适合换入即初始化的控件。
every 5s 轮询自服务器
…
load 时惰性加载(服务端 1.2s 慢工)
正在向服务器取正文…
左侧每 5 秒向服务器取一次当前时间;右侧的正文在元素
load事件到达时才发起请求——界面先到,内容后补。自定义事件
htmx 原生支持发布/订阅风格协作:一处 hx-trigger 听自定义事件名,另一处用响应头 HX-Trigger(见「请求」章)或 htmx.trigger() 广播之:
<!-- 收据区听说「票已订」就去刷新 -->
<div id="receipt" hx-get="/receipt" hx-trigger="ticket-booked"></div>
<!-- 购票按钮成功后,服务器在响应头里广播 -->
<!-- HTTP/1.1 200 OK
HX-Trigger: ticket-booked -->
<button hx-post="/ticket/1">购票</button>htmx 生命周期事件
每个请求沿固定轨道走完一生,处处可挂钩。v4 把事件名从驼峰改为冒号分隔(htmx:afterRequest → htmx:after:request),搭配 hx-on::after:request 或 document 级监听:
| 事件(v4 名) | 时机 | 典型用途 |
|---|---|---|
htmx:config:request | 请求发出前,配置已就绪 | 统一加 X-CSRF-Token 等请求头 |
htmx:before:request | 即将发送 | 中止之(evt.preventDefault()) |
htmx:before:swap | 换入 DOM 前 | 改写 evt.detail.ctx.text、否决换入 |
htmx:after:swap | 换入完成 | 聚焦新输入框、启动图表 |
htmx:after:request | 请求落幕(成败皆至) | 统一错误上报 |
htmx:response:error | 非预期状态码响应 | 自定义错误条 |
htmx:error | 网络层失败 / 超时 / 目标错误 | 离线提示 |
htmx:before:history:restore | 前进/后退遇上未缓存页 | 回源取页(配合 boost) |
v4 事件详情结构重排:请求头、参数、URL 现在统一挂在
evt.detail.ctx.request 下,响应文本在 evt.detail.ctx.text。旧 evt.detail.headers / evt.detail.serverResponse 直取法已失效。document.body.addEventListener("htmx:config:request", (evt) => {
evt.detail.ctx.request.headers["X-CSRF-Token"] = readMeta("csrf-token");
});