目标元素与内容交换策略
目标元素与内容交换策略
发起请求只是第一步,更重要的是响应内容如何、在哪里被插入页面。htmx 通过 hx-target 和 hx-swap 两个核心属性,提供了极其灵活的内容更新控制机制。本文将深入讲解这两个属性的所有用法。
hx-target:指定更新目标
hx-target 决定服务器返回的 HTML 应该更新哪个元素。如果未指定,默认更新触发请求的元素本身。
CSS 选择器
最直接的方式是使用 CSS 选择器:
<button hx-get="/api/notification-count" hx-target="#badge">
刷新通知
</button>
<span id="badge">0</span>支持任意有效的 CSS 选择器:
<!-- ID 选择器 -->
<div hx-get="/content" hx-target="#main"></div>
<!-- 类选择器 -->
<div hx-get="/content" hx-target=".sidebar"></div>
<!-- 属性选择器 -->
<div hx-get="/content" hx-target="[data-region='header']"></div>
<!-- 组合选择器 -->
<div hx-get="/content" hx-target="#list > li:first-child"></div>特殊关键字
htmx 提供了几个便利的关键字,避免编写复杂的选择器:
| 关键字 | 含义 | 示例 |
|---|---|---|
this | 触发请求的元素本身 | hx-target="this" |
closest <selector> | 最近的匹配祖先元素 | hx-target="closest tr" |
find <selector> | 在元素内部查找匹配子元素 | hx-target="find .result" |
next | 紧邻的下一个兄弟元素 | hx-target="next" |
previous | 紧邻的上一个兄弟元素 | hx-target="previous" |
this:更新自身
<button hx-get="/api/toggle/123"
hx-target="this"
hx-swap="outerHTML">
启用
</button>点击后按钮本身被服务器返回的新 HTML 替换(可能变为”禁用”状态)。
closest:更新最近祖先
<ul>
<li>
任务一
<button hx-delete="/api/tasks/1"
hx-target="closest li"
hx-swap="delete">
删除
</button>
</li>
</ul>closest li 找到按钮所在的最近的 <li> 元素。hx-swap="delete" 会删除该元素,无需服务器返回内容。
提示:
closest在处理列表项操作时特别有用。每个删除按钮都可以使用相同的hx-target="closest li",不需要为每个项设置唯一 ID。
find:在内部查找
<div hx-get="/api/dashboard" hx-target="find .metrics">
<div class="metrics">加载中...</div>
</div>find .metrics 在当前元素内部查找 .metrics 子元素进行更新,而不是更新整个容器。
next / previous:兄弟元素
<button hx-get="/api/details" hx-target="next">
展开详情
</button>
<div class="details" style="display:none;"></div>next 选中紧邻的下一个兄弟元素,非常适合展开/折叠场景。
<div class="preview">内容预览</div>
<button hx-get="/api/full" hx-target="previous">
查看完整内容
</button>hx-swap:控制交换策略
hx-swap 定义了响应内容如何插入到目标元素中。默认策略是 innerHTML(替换目标元素的内部内容)。
八种交换策略
| 策略 | 行为描述 | 示例场景 |
|---|---|---|
innerHTML | 替换目标元素的内部 HTML(默认) | 更新内容区域 |
outerHTML | 替换目标元素本身 | 替换列表项 |
beforebegin | 在目标元素之前插入(同级) | 在列表前添加项 |
afterbegin | 在目标元素内部的开始处插入 | 在列表顶部添加 |
beforeend | 在目标元素内部的末尾处插入 | 在列表底部添加 |
afterend | 在目标元素之后插入(同级) | 在元素后插入新元素 |
delete | 删除目标元素,忽略响应 | 删除操作 |
none | 不插入任何内容 | 仅触发事件 |
innerHTML(默认)
<div id="content" hx-get="/api/page/2">
第一页内容
</div>请求返回的 HTML 替换 #content 的内部内容,<div id="content"> 标签本身保留。
outerHTML
<div id="item-1" hx-get="/api/item/1/edit" hx-swap="outerHTML">
<span>任务 1</span>
<button>编辑</button>
</div>返回的 HTML 替换整个 #item-1 元素。常用于将只读视图替换为编辑表单,或反之。
四种 sibling 插入策略
假设有以下结构:
<div id="container">
<p>现有内容</p>
</div>服务器返回:<p>新内容</p>
| 策略 | 结果 |
|---|---|
beforebegin | <p>新内容</p><div id="container">...</div> |
afterbegin | <div id="container"><p>新内容</p><p>现有内容</p></div> |
beforeend | <div id="container"><p>现有内容</p><p>新内容</p></div> |
afterend | <div id="container">...</div><p>新内容</p> |
beforeend:无限滚动列表
<ul id="items" hx-get="/api/more" hx-trigger="revealed" hx-swap="beforeend">
<li>项目 1</li>
<li>项目 2</li>
</ul>当列表滚动到视口时(revealed 触发器),加载更多项目并追加到列表末尾。
delete:删除操作
<button hx-delete="/api/items/42"
hx-target="closest .card"
hx-swap="delete"
hx-confirm="确定删除?">
🗑️
</button>hx-swap="delete" 直接删除目标元素(最近的 .card),不需要服务器返回任何内容。
none:纯事件触发
<button hx-post="/api/track"
hx-swap="none"
hx-on::after-request="alert('记录已保存')">
记录点击
</button>hx-swap="none" 表示不更新任何 DOM,只执行事件处理。适合日志记录、分析统计等场景。
hx-select:选择部分内容
有时服务器返回的 HTML 包含完整页面,但你只需要其中一部分。hx-select 可以从响应中提取指定内容。
基础用法
<div hx-get="/full-page" hx-select="#sidebar-content" hx-target="#sidebar">
刷新侧边栏
</div>服务器返回完整 HTML 页面,但 htmx 只提取其中 #sidebar-content 的内容插入到页面的 #sidebar 中。
排除内容
结合 :not() 排除不需要的部分:
<div hx-get="/article/123"
hx-select=".content:not(.ads)"
hx-target="#reader">
加载文章
</div>高级交换修饰符
hx-swap 支持多个修饰符,进一步控制交换行为:
过渡修饰符
| 修饰符 | 说明 |
|---|---|
swap:N | 内容移除与新内容插入之间的延迟(毫秒) |
settle:N | 新内容插入后 CSS 过渡的完成时间(毫秒) |
<div hx-get="/api/update"
hx-swap="innerHTML swap:100ms settle:300ms">
更新
</div>swap:100ms 表示旧内容移除和新内容插入之间有 100ms 间隔,让你有时间添加过渡动画。
滚动修饰符
| 修饰符 | 说明 |
|---|---|
scroll:top | 交换后滚动到目标元素顶部 |
scroll:bottom | 交换后滚动到目标元素底部 |
show:top | 交换后确保目标元素顶部在视口内 |
show:bottom | 交换后确保目标元素底部在视口内 |
<div hx-get="/api/messages"
hx-target="#chat"
hx-swap="beforeend scroll:bottom">
加载新消息
</div>新消息追加到聊天区域后,自动滚动到底部。
<form hx-post="/api/comments"
hx-target="#comments"
hx-swap="beforeend show:bottom">
<!-- 发表评论后,确保最新评论可见 -->
</form>聚焦修饰符
| 修饰符 | 说明 |
|---|---|
focus-scroll:true / focus-scroll:false | 交换后是否保持焦点元素的滚动位置 |
<div hx-get="/api/live-data"
hx-trigger="every 5s"
hx-swap="innerHTML focus-scroll:false">
<!-- 定时刷新,但不因为焦点变化而滚动页面 -->
</div>完整交换配置示例
<div id="feed"
hx-get="/api/posts"
hx-trigger="revealed"
hx-target="#feed"
hx-swap="beforeend swap:50ms settle:200ms show:bottom"
hx-select=".post-item"
hx-indicator=".loading">
<article class="post-item">...</article>
</div>
<div class="loading htmx-indicator">加载更多...</div>这个配置实现了完整的无限滚动体验:
hx-trigger="revealed"— 元素进入视口时触发hx-swap="beforeend"— 新内容追加到末尾swap:50ms settle:200ms— 提供动画过渡时间窗口show:bottom— 保持滚动位置合理hx-select=".post-item"— 只提取文章条目hx-indicator— 显示加载状态
完整示例:待办事项列表
<ul id="todo-list">
<li>
<span>学习 htmx</span>
<button hx-delete="/api/todos/1"
hx-target="closest li"
hx-swap="delete"
hx-confirm="确定完成并删除?">
完成
</button>
</li>
</ul>
<form hx-post="/api/todos"
hx-target="#todo-list"
hx-swap="beforeend"
hx-on::after-request="this.reset()">
<input type="text" name="text" placeholder="新任务..." required />
<button type="submit">添加</button>
</form>| 交互 | 目标 | 交换策略 | 效果 |
|---|---|---|---|
| 添加任务 | #todo-list | beforeend | 追加到列表末尾 |
| 完成任务 | closest li | delete | 删除该行 |
总结
hx-target 和 hx-swap 是 htmx 中最强大的属性组合,掌握它们意味着你可以精确控制页面更新的每一个细节:
hx-target— 支持 CSS 选择器和this/closest/find/next/previous关键字,灵活定位更新目标hx-swap— 8 种交换策略覆盖所有插入场景,配合修饰符实现过渡动画和滚动控制hx-select— 从响应中提取部分内容,适配非专门为 htmx 设计的后端接口
下一篇文章将深入探讨 触发器与修饰符,理解 hx-trigger 如何精确控制请求的时机和条件。
评论
Written by
AI-Writer