第四章:触发器与事件处理
---
4.1 hx-trigger 详解
hx-trigger 是 HTMX 中最重要的属性之一,它定义了何时发起请求。相比原生事件监听,它提供了更强大的控制能力。
#### 标准事件触发
<!-- 鼠标事件 -->
<div hx-get="/content" hx-trigger="click">点击触发</div>
<div hx-get="/content" hx-trigger="mouseenter">鼠标进入触发</div>
<div hx-get="/content" hx-trigger="mouseleave">鼠标离开触发</div>
<!-- 表单事件 -->
<input hx-post="/validate" hx-trigger="blur" /> <!-- 失去焦点 -->
<input hx-post="/search" hx-trigger="focus" /> <!-- 获得焦点 -->
<input hx-post="/change" hx-trigger="change" /> <!-- 值改变并失去焦点 -->
<!-- 键盘事件 -->
<input hx-get="/search" hx-trigger="keyup" /> <!-- 按键抬起 -->
<input hx-get="/search" hx-trigger="keydown" /> <!-- 按键按下 -->
<!-- 表单提交 -->
<form hx-post="/submit" hx-trigger="submit">... <!-- 表单提交(默认) -->
---
4.2 触发器修饰符
修饰符可以改变事件的默认行为:
#### once - 仅触发一次
<!-- 点击后不再触发 -->
<button hx-get="/track" hx-trigger="click once">
追踪一次
</button>
#### changed - 值变化时触发
<!-- 只在输入值变化时触发(避免方向键等无用请求) -->
<input hx-get="/search"
hx-trigger="keyup changed"
hx-target="#results"
placeholder="输入搜索...">
#### delay - 延迟触发
<!-- 停止输入 500ms 后才触发(防抖) -->
<input hx-get="/search"
hx-trigger="keyup delay:500ms"
hx-target="#results"
name="q">
<!-- 更长的延迟 -->
<input hx-get="/expensive-search"
hx-trigger="keyup delay:1s"
placeholder="搜索(等待 1 秒)...">
#### throttle - 节流
<!-- 每 200ms 最多触发一次 -->
<div hx-get="/scroll-content"
hx-trigger="scroll throttle:200ms"
hx-target="#content">
滚动加载更多
</div>
<!-- 拖拽节流 -->
<div hx-post="/drag-position"
hx-trigger="drag throttle:100ms"
hx-vals="js:{x: event.clientX, y: event.clientY}"
>拖拽我</div>
#### from - 指定事件来源
<!-- 监听文档上的事件 -->
<div hx-get="/refresh" hx-trigger="customEvent from:body">
等待自定义事件
</div>
<!-- 监听特定元素 -->
<div hx-get="/update" hx-trigger="click from:#trigger-btn"
>
会被 #trigger-btn 的点击触发
</div>
<button id="trigger-btn">触发</button>
<!-- 监听窗口事件 -->
<div hx-get="/resize" hx-trigger="resize from:window"
003e
窗口大小改变时刷新
</div>
#### target - 事件目标过滤
<!-- 只在点击特定元素时触发 -->
<div hx-get="/action" hx-trigger="click target:.btn"
003e
<span class="btn">点击我触发</span>
<span>点击我不触发</span>
</div>
#### consume - 阻止事件冒泡
<!-- 阻止事件继续传播 -->
<div onclick="console.log('父元素')">
<button hx-get="/action" hx-trigger="click consume">
点击不冒泡到父元素
</button>
</div>
#### queue - 请求队列策略
<!-- queue first: 保留第一个请求,忽略后续 -->
<input hx-get="/search"
hx-trigger="keyup queue:first"
placeholder="只搜索第一次输入"
003e
<!-- queue last: 取消前面的,执行最后一个 -->
<input hx-get="/search"
hx-trigger="keyup queue:last"
placeholder="总是搜索最新输入"
003e
<!-- queue all: 排队执行所有请求 -->
<button hx-post="/action"
hx-trigger="click queue:all">
点击多次会排队执行
</button>
---
4.3 特殊触发器
#### load - 加载时触发
<!-- 页面加载完成后自动请求 -->
<div hx-get="/init-data" hx-trigger="load">
加载中...
</div>
<!-- 带延迟的加载 -->
<div hx-get="/deferred-content"
hx-trigger="load delay:2s"
003e
2 秒后自动加载
</div>
#### revealed - 进入视口时触发
<!-- 懒加载图片 -->
<img hx-get="/image/large.jpg"
hx-trigger="revealed"
hx-swap="outerHTML"
src="placeholder.jpg"
alt="懒加载图片"
>
<!-- 无限滚动 -->
<div id="scroll-sentinel"
hx-get="/more-items"
hx-trigger="revealed"
hx-target="#item-list"
hx-swap="beforeend"
003e
滚动到底部自动加载更多
</div>
#### every - 轮询
<!-- 每 5 秒刷新状态 -->
<div hx-get="/status" hx-trigger="every 5s">
服务器状态会每 5 秒更新
</div>
<!-- 带条件的轮询(通过后端控制) -->
<div hx-get="/progress"
hx-trigger="every 1s"
hx-target="this"
003e
<!-- 后端返回空则停止轮询 -->
进度: 0%
</div>
#### intersect - 交集观察器
<!-- 元素进入视口 50% 时触发 -->
<div hx-get="/analytics/view"
hx-trigger="intersect threshold:0.5"
hx-vals='{"article_id": 123}'
003e
文章正文...(阅读统计)
</div>
<!-- 元素完全可见时触发 -->
<div hx-get="/load-more"
hx-trigger="intersect threshold:1.0"
hx-target="#content"
hx-swap="beforeend"
003e
完全可见时加载
</div>
---
4.4 事件监听与扩展
#### 自定义事件触发
<!-- 定义 HTMX 事件处理器 -->
<div hx-get="/content" hx-trigger="myCustomEvent">
等待自定义事件...
</div>
<script>
// 触发自定义事件
document.dispatchEvent(new CustomEvent('myCustomEvent'));
// 或从特定元素触发
document.getElementById('myDiv').dispatchEvent(
new CustomEvent('myCustomEvent')
);
</script>
#### HTMX 事件列表
// 生命周期事件
document.body.addEventListener('htmx:load', function(evt) {
console.log('HTMX 库加载完成');
});
// 请求前事件
document.body.addEventListener('htmx:beforeRequest', function(evt) {
console.log('请求即将发送:', evt.detail.requestConfig);
// 可以在这里阻止请求
// evt.preventDefault();
});
// 请求后事件
document.body.addEventListener('htmx:afterRequest', function(evt) {
console.log('请求完成:', evt.detail.xhr);
});
// 成功事件
document.body.addEventListener('htmx:afterOnLoad', function(evt) {
console.log('请求成功:', evt.detail.xhr.response);
});
// 错误事件
document.body.addEventListener('htmx:responseError', function(evt) {
console.error('请求错误:', evt.detail.xhr.status);
});
// 交换前事件
document.body.addEventListener('htmx:beforeSwap', function(evt) {
console.log('即将交换内容');
// 可以修改响应内容
evt.detail.serverResponse = evt.detail.serverResponse.toUpperCase();
});
// 交换后事件
document.body.addEventListener('htmx:afterSwap', function(evt) {
console.log('内容交换完成');
// 可以在这里初始化第三方库
reinitializePlugins();
});
// 历史记录事件
document.body.addEventListener('htmx:historyCacheMiss', function(evt) {
console.log('历史缓存未命中,从服务器获取');
});
---
4.5 条件触发
使用 js: 前缀执行 JavaScript 条件:
<!-- 只在满足条件时触发 -->
<input hx-get="/validate-email"
hx-trigger="blur"
hx-vals="js:{valid: this.value.includes('@')}"
hx-target="#email-error"
003e
<!-- 更复杂的条件 -->
<button hx-post="/submit"
hx-trigger="click[document.querySelector('#agree').checked]"
003e
提交(必须同意条款)
</button>
<input type="checkbox" id="agree"> 我同意条款
---
4.6 多个触发器
可以用逗号分隔多个触发器:
<!-- 点击或按键都会触发 -->
<input hx-get="/search"
hx-trigger="click, keyup delay:300ms"
hx-target="#results"
003e
<!-- 多个不同配置 -->
<div hx-get="/update"
hx-trigger="mouseenter once, click queue:last"
003e
鼠标进入触发一次,点击总是触发最新
</div>
---
4.7 触发器速查表
| 触发器 | 说明 | 常用修饰符 |
|---|---|---|
click | 点击 | once, consume |
dblclick | 双击 | - |
mouseenter | 鼠标进入 | - |
mouseleave | 鼠标离开 | - |
mousedown/up | 鼠标按下/抬起 | - |
focus | 获得焦点 | - |
blur | 失去焦点 | - |
change | 值改变 | - |
input | 输入 | delay, changed |
keyup/down/press | 键盘事件 | delay, changed, queue |
submit | 表单提交 | - |
scroll | 滚动 | throttle |
load | 加载完成 | delay |
revealed | 进入视口 | once |
intersect | 元素交集 | threshold |
every [time] | 定时轮询 | - |
[event] from:X | 监听其他元素 | from, target |
下一章预告:第五章将讲解交换策略和 DOM 操作。
---
*第四章完*