文档

htmx 简述

htmx 是一个库,它允许您直接从 HTML 访问现代浏览器功能,而不是使用 JavaScript。

要理解 htmx,首先来看一个锚标签:

<a href="/blog">博客</a>

这个锚标签告诉浏览器:

“当用户点击此链接时,向 ‘/blog’ 发出 HTTP GET 请求,并将响应内容加载到浏览器窗口中”。

考虑到这一点,请看下面的 HTML 片段:

<button hx-post="/clicked"
    hx-trigger="click"
    hx-target="#parent-div"
    hx-swap="outerHTML">
    点击我!
</button>

这告诉 htmx:

“当用户点击此按钮时,向 ‘/clicked’ 发出 HTTP POST 请求,并使用响应中的内容替换 DOM 中 id 为 parent-div 的元素”

htmx 扩展并泛化了 HTML 作为超文本的核心思想,直接在语言中开启了许多更多可能性:

  • 现在任何元素,而不仅仅是锚和表单,都可以发出 HTTP 请求
  • 现在任何事件,而不仅仅是点击或表单提交,都可以触发请求
  • 现在任何 HTTP 动词,而不仅仅是 GETPOST,都可以使用
  • 现在任何元素,而不仅仅是整个窗口,都可以作为请求更新的目标

请注意,当使用 htmx 时,在服务器端,您通常会用 HTML 而不是 JSON 进行响应。这将您牢牢保持在 原始 Web 编程模型 中,使用 超文本作为应用状态的引擎,甚至不需要真正理解这个概念。

值得一提的是,如果您喜欢,您可以使用 data- 前缀来使用 htmx:

<a data-hx-post="/click">点击我!</a>

如果您理解了 htmx 周围的概念,并想查看库的奇异之处,请参阅我们的 QUIRKS 页面。

1.x 到 2.x 迁移指南

htmx 的 1 版 仍然受支持并支持 IE11,但 htmx 的最新版本是 2.x。

如果您从 htmx 1.x 迁移到 htmx 2.x,请参阅 htmx 1.x 迁移指南

如果您从 intercooler.js 迁移到 htmx,请参阅 intercooler 迁移指南

安装

Htmx 是一个无依赖的、面向浏览器的 JavaScript 库。这意味着使用它就像在文档头部添加一个 <script> 标签一样简单。不需要构建系统来使用它。

通过 CDN(例如 jsDelivr)

使用 htmx 的最快方式是通过 CDN 加载它。您只需将此添加到您的 head 标签中即可开始:

<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js" integrity="sha384-ZBXiYtYQ6hJ2Y0ZNoYuI+Nq5MqWBr+chMrS/RkXpNzQCApHEhOt2aY8EJgqwHLkJ" crossorigin="anonymous"></script>

未压缩版本也可用:

<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.js" integrity="sha384-yWakaGAFicqusuwOYEmoRjLNOC+6OFsdmwC2lbGQaRELtuVEqNzt11c2J711DeCZ" crossorigin="anonymous"></script>

虽然 CDN 方法极其简单,但您可能需要考虑 在生产环境中不使用 CDN

下载副本

安装 htmx 的下一个最简单方法是将它复制到您的项目中。

从 jsDelivr 下载 htmx.min.js 从 jsDelivr 并将其添加到项目中的适当目录,然后在需要的地方使用 <script> 标签包含它:

<script src="/path/to/htmx.min.js"></script>

npm

对于 npm 风格的构建系统,您可以通过 npm 安装 htmx:

npm install htmx.org@2.0.7

安装后,您需要使用适当的工具来使用 node_modules/htmx.org/dist/htmx.js(或 .min.js)。例如,您可以将 htmx 与一些扩展和项目特定代码捆绑。

Webpack

如果您使用 webpack 管理您的 JavaScript:

  • 通过您喜欢的包管理器(例如 npm 或 yarn)安装 htmx
  • 将 import 添加到您的 index.js
import 'htmx.org';

如果您想使用全局 htmx 变量(推荐),您需要将其注入到 window 作用域中:

  • 创建一个自定义 JS 文件
  • 将此文件导入到您的 index.js(在步骤 2 的 import 下面)
import 'path/to/my_custom.js';
  • 然后将此代码添加到文件中:
window.htmx = require('htmx.org');
  • 最后,重新构建您的捆绑包

AJAX

htmx 的核心是一组属性,允许您直接从 HTML 发出 AJAX 请求:

属性描述
hx-get向给定 URL 发出 GET 请求
hx-post向给定 URL 发出 POST 请求
hx-put向给定 URL 发出 PUT 请求
hx-patch向给定 URL 发出 PATCH 请求
hx-delete向给定 URL 发出 DELETE 请求

这些属性中的每一个都接受一个 URL 来发出 AJAX 请求。当元素被 触发 时,元素将向给定 URL 发出指定类型的请求:

<button hx-put="/messages">
    更新消息
</button>

这告诉浏览器:

当用户点击此按钮时,向 URL /messages 发出 PUT 请求,并将响应加载到按钮中

触发请求

默认情况下,AJAX 请求由元素的“自然”事件触发:

  • inputtextareaselectchange 事件上触发
  • formsubmit 事件上触发
  • 其他所有元素由 click 事件触发

如果您想要不同的行为,您可以使用 hx-trigger 属性来指定哪个事件将导致请求。

这里有一个 div,当鼠标进入时向 /mouse_entered 发出 POST 请求:

<div hx-post="/mouse_entered" hx-trigger="mouseenter">
    [这里,老鼠,老鼠!]
</div>

触发器修饰符

触发器还可以有一些额外的修饰符来改变其行为。例如,如果您希望请求只发生一次,您可以使用触发器的 once 修饰符:

<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
    [这里,老鼠,老鼠!]
</div>

触发器可以使用的其他修饰符包括:

  • changed - 仅当元素的值发生变化时才发出请求
  • delay:<时间间隔> - 在发出请求前等待给定的时间量(例如 1s)。如果事件再次触发,倒计时将重置。
  • throttle:<时间间隔> - 在发出请求前等待给定的时间量(例如 1s)。与 delay 不同,如果在时间限制到达前发生新事件,该事件将被丢弃,因此请求将在时间段结束时触发。
  • from:<CSS 选择器> - 在不同元素上监听事件。这可用于键盘快捷键。请注意,如果页面发生变化,此 CSS 选择器不会重新评估。

您可以使用这些属性来实现许多常见的 UX 模式,例如 活动搜索

<input type="text" name="q"
    hx-get="/trigger_delay"
    hx-trigger="keyup changed delay:500ms"
    hx-target="#search-results"
    placeholder="搜索...">
<div id="search-results"></div>

此输入将在按键抬起事件后 500 毫秒发出请求,如果输入已更改,并将结果插入到 id 为 search-resultsdiv 中。

hx-trigger 属性中可以指定多个触发器,用逗号分隔。

触发器过滤器

您还可以通过在事件名称后使用方括号来应用触发器过滤器,括号中包含一个将要评估的 JavaScript 表达式。如果表达式评估为 true,则事件将触发,否则不会。

这里是一个仅在元素的 Control-Click 上触发的示例:

<div hx-get="/clicked" hx-trigger="click[ctrlKey]">
    Control 点击我
</div>

ctrlKey 这样的属性将首先针对触发事件解析,然后针对全局作用域。this 符号将设置为当前元素。

特殊事件

htmx 为 hx-trigger 提供了一些特殊事件:

  • load - 当元素首次加载时触发一次
  • revealed - 当元素首次滚动到视口中时触发一次
  • intersect - 当元素首次与视口相交时触发一次。这支持两个附加选项:
    • root:<选择器> - 相交的根元素的 CSS 选择器
    • threshold:<浮点数> - 介于 0.0 和 1.0 之间的浮点数,表示触发事件的相交量

如果您有高级用例,您还可以使用自定义事件来触发请求。

轮询

如果您希望元素轮询给定的 URL 而不是等待事件,您可以使用 hx-trigger 属性的 every 语法:

<div hx-get="/news" hx-trigger="every 2s"></div>

这告诉 htmx:

每 2 秒,向 /news 发出 GET 请求,并将响应加载到 div 中

如果您想从服务器响应停止轮询,您可以响应 HTTP 响应代码 286,元素将取消轮询。

加载轮询

htmx 中实现轮询的另一种技术是“加载轮询”,其中元素指定一个 load 触发器以及延迟,并用响应替换自身:

<div hx-get="/messages"
    hx-trigger="load delay:1s"
    hx-swap="outerHTML">
</div>

如果 /messages 端点继续返回这样设置的 div,它将每秒“轮询”回 URL。

加载轮询在轮询有一个端点时很有用,此时轮询终止,例如当您向用户显示 进度条 时。

请求指示器

发出 AJAX 请求时,通常最好让用户知道正在发生什么,因为浏览器不会给他们任何反馈。您可以通过使用 htmx-indicator 类在 htmx 中实现这一点。

htmx-indicator 类定义为默认不透明度为 0,使其在 DOM 中不可见但存在。

当 htmx 发出请求时,它会将 htmx-request 类添加到一个元素(请求元素本身或指定的另一个元素)。htmx-request 类将导致其上的 htmx-indicator 类的子元素过渡到不透明度为 1,显示指示器。

<button hx-get="/click">
    点击我!
    <img class="htmx-indicator" src="/spinner.gif">
</button>

这里我们有一个按钮。当它被点击时,htmx-request 类将被添加到它,这将揭示 spinner gif 元素。(我现在喜欢 SVG 加载器。)

虽然 htmx-indicator 类使用不透明度来隐藏和显示进度指示器,但如果您更喜欢其他机制,您可以像这样创建自己的 CSS 过渡:

.htmx-indicator{
    display:none;
}
.htmx-request .htmx-indicator{
    display:inline;
}
.htmx-request.htmx-indicator{
    display:inline;
}

如果您希望 htmx-request 类添加到不同的元素,您可以使用 hx-indicator 属性和 CSS 选择器来实现:

<div>
    <button hx-get="/click" hx-indicator="#indicator">
        点击我!
    </button>
    <img id="indicator" class="htmx-indicator" src="/spinner.gif"/>
</div>

这里我们通过 id 明确调用指示器。请注意,我们也可以将类放在父 div 上,并产生相同效果。

您还可以在请求持续时间内使用 hx-disabled-elt 属性向元素添加 disabled 属性

目标

如果您希望响应加载到发出请求的不同元素中,您可以使用 hx-target 属性,它接受一个 CSS 选择器。回顾我们的实时搜索示例:

<input type="text" name="q"
    hx-get="/trigger_delay"
    hx-trigger="keyup delay:500ms changed"
    hx-target="#search-results"
    placeholder="搜索...">
<div id="search-results"></div>

您可以看到搜索结果将加载到 div#search-results 中,而不是输入标签中。

扩展 CSS 选择器

hx-target 和大多数接受 CSS 选择器的属性支持“扩展” CSS 语法:

  • 您可以使用 this 关键字,它表示 hx-target 属性所在的元素是目标
  • closest <CSS 选择器> 语法将找到匹配给定 CSS 选择器的 最近 祖先元素或自身。(例如 closest tr 将针对元素最近的表格行)
  • next <CSS 选择器> 语法将在 DOM 中找到匹配给定 CSS 选择器的下一个元素。
  • previous <CSS 选择器> 语法将在 DOM 中找到匹配给定 CSS 选择器的前一个元素。
  • find <CSS 选择器> 将找到匹配给定 CSS 选择器的第一个子后代元素。(例如 find tr 将针对元素第一个子后代行)

此外,CSS 选择器可以用 </> 字符包裹,模仿 hyperscript 的 查询字面量 语法。

这样的相对目标对于在不使用大量 id 属性的情况下创建灵活的用户界面很有用。

交换

htmx 提供了几种将返回的 HTML 交换到 DOM 中的不同方式。默认情况下,内容替换目标元素的 innerHTML。您可以通过使用 hx-swap 属性并使用以下任何值来修改此行为:

名称描述
innerHTML默认,将内容放入目标元素内部
outerHTML用返回的内容替换整个目标元素
afterbegin将内容前置到目标内部的第一个子元素之前
beforebegin将内容前置到目标的父元素中的目标之前
beforeend将内容追加到目标内部的最后一个子元素之后
afterend将内容追加到目标的父元素中的目标之后
delete无论响应如何都删除目标元素
none不追加响应中的内容(带外交换响应头 仍将被处理)

形态交换

除了上述标准交换机制之外,htmx 还通过扩展支持 形态 交换。形态交换尝试将新内容 合并 到现有 DOM 中,而不是简单替换它。它们通常通过在交换操作期间就地变异现有节点来更好地保留焦点、视频状态等,但代价是更多的 CPU。

以下扩展可用于形态风格的交换:

视图过渡

新的实验性 View Transitions API 为开发者提供了一种在不同 DOM 状态之间创建动画过渡的方法。它仍在积极开发中,并非所有浏览器都可用,但 htmx 提供了一种与此新 API 合作的方式,如果 API 在给定浏览器中不可用,则回退到非过渡机制。

您可以使用以下方法实验此新 API:

  • htmx.config.globalViewTransitions 配置变量设置为 true 以对所有交换使用过渡
  • hx-swap 属性中使用 transition:true 选项
  • 如果由于上述任何配置导致元素交换将被过渡,您可以捕获 htmx:beforeTransition 事件并在其上调用 preventDefault() 以取消过渡。

视图过渡可以使用 CSS 配置,如 Chrome 文档中所述

您可以在 动画示例 页面上看到视图过渡示例。

交换选项

hx-swap 属性支持许多选项来调整 htmx 的交换行为。例如,默认情况下 htmx 将交换新内容中找到的任何 title 标签的标题。您可以通过将 ignoreTitle 修饰符设置为 true 来关闭此行为:

    <button hx-post="/like" hx-swap="outerHTML ignoreTitle:true">点赞</button>

hx-swap 上可用的修饰符包括:

选项描述
transitiontruefalse,是否为此交换使用视图过渡 API
swap要使用的交换延迟(例如 100ms),在清除旧内容和新内容插入之间
settle要使用的结算延迟(例如 100ms),在新内容插入和结算之间
ignoreTitle如果设置为 true,新内容中的任何标题将被忽略,不会更新文档标题
scrolltopbottom,将目标元素滚动到其顶部或底部
showtopbottom,将目标元素的顶部或底部滚动到视图中

所有交换修饰符出现在指定交换样式之后,并用冒号分隔。

有关这些选项的更多详细信息,请参阅 hx-swap 文档。

同步

通常您希望协调两个元素之间的请求。例如,您可能希望一个元素的请求取代另一个元素的请求,或等待另一个元素的请求完成。

htmx 提供了一个 hx-sync 属性来帮助您实现这一点。

考虑表单提交和单个输入验证请求之间的竞争条件,在此 HTML 中:

<form hx-post="/store">
    <input id="title" name="title" type="text"
        hx-post="/validate"
        hx-trigger="change">
    <button type="submit">提交</button>
</form>

不使用 hx-sync,填写输入并立即提交表单将触发两个并行请求到 /validate/store

在输入上使用 hx-sync="closest form:abort" 将监视表单上的请求,并在输入请求飞行中如果存在表单请求或启动表单请求时中止输入请求:

<form hx-post="/store">
    <input id="title" name="title" type="text"
        hx-post="/validate"
        hx-trigger="change"
        hx-sync="closest form:abort">
    <button type="submit">提交</button>
</form>

这以声明方式解决了两个元素之间的同步问题。

htmx 还支持以编程方式取消请求:您可以向元素发送 htmx:abort 事件来取消任何飞行中的请求:

<button id="request-button" hx-post="/example">
    发出请求
</button>
<button onclick="htmx.trigger('#request-button', 'htmx:abort')">
    取消请求
</button>

更多示例和详细信息可以在 hx-sync 属性页面 上找到。

CSS 过渡

htmx 使使用 CSS 过渡 变得容易,而无需 JavaScript。考虑此 HTML 内容:

<div id="div1">原始内容</div>

想象此内容通过 ajax 请求被 htmx 替换为这个新内容:

<div id="div1" class="red">新内容</div>

注意两件事:

  • div 在原始和新内容中具有 相同 的 id
  • red 类已被添加到新内容中

鉴于这种情况,我们可以编写从旧状态到新状态的 CSS 过渡:

.red {
    color: red;
    transition: all ease-in 1s ;
}

当 htmx 交换此新内容时,它将以使 CSS 过渡适用于新内容的方式进行交换,从而为您提供一个到新状态的平滑过渡。

因此,总结一下,要为元素使用 CSS 过渡,您只需在请求中保持其 id 稳定!

您可以在 动画示例 中看到更多细节和实时演示。

细节

要理解 CSS 过渡在 htmx 中如何实际工作,您必须理解 htmx 使用的底层交换和结算模型。

当从服务器接收到新内容时,在内容交换之前,页面现有内容将被检查以匹配 id 属性的元素。如果在新内容中为元素找到匹配项,则在交换发生之前,将旧内容的属性复制到新元素上。然后交换新内容,但使用 属性值。最后,在“结算”延迟(默认 20ms)后交换新属性值。有点疯狂,但这就是允许开发者无需任何 JavaScript 即可使用 CSS 过渡的原因。

带外交换

如果您希望从响应直接使用 id 属性将内容交换到 DOM 中,您可以在 响应 HTML 中使用 hx-swap-oob 属性:

<div id="message" hx-swap-oob="true">直接交换我!</div>
额外内容

在此响应中,div#message 将直接交换到匹配的 DOM 元素中,而额外内容将以正常方式交换到目标中。

您可以使用此技术在其他请求上“搭便车”更新。

问题表格

表格元素与带外交换结合时可能会有问题,因为根据 HTML 规范,许多元素无法独立存在于 DOM 中(例如 <tr><td>)。

要避免此问题,您可以使用 template 标签来封装这些元素:

<template>
  <tr id="message" hx-swap-oob="true"><td>Joe</td><td>Smith</td></tr>
</template>

选择要交换的内容

如果您希望从响应 HTML 中选择要交换到目标的子集,您可以使用 hx-select 属性,它接受 CSS 选择器并从响应中选择匹配元素。

您还可以通过使用 hx-select-oob 属性来挑选内容用于带外交换,该属性接受要挑选和交换的元素 ID 列表。

在交换期间保留内容

如果您希望跨交换保留内容(例如,即使发生交换,您也希望视频播放器保持播放),您可以使用 hx-preserve 属性应用于您希望保留的元素。

参数

默认情况下,导致请求的元素如果有值,将包含其值。如果元素是表单,它将包含其中的所有输入值。

与 HTML 表单一样,输入的 name 属性用作 htmx 发送的请求中的参数名称。

此外,如果元素导致非 GET 请求,则将包含关联表单的所有输入值(通常这是最近的封闭表单,但如果例如使用 <button form="associated-form">,则可能不同)。

如果您希望包含其他元素的值,您可以使用 hx-include 属性和所有您希望包含在请求中的元素值的 CSS 选择器。

如果您希望过滤掉某些参数,您可以使用 hx-params 属性。

最后,如果您希望以编程方式修改参数,您可以使用 htmx:configRequest 事件。

文件上传

如果您希望通过 htmx 请求上传文件,您可以将 hx-encoding 属性设置为 multipart/form-data。这将使用 FormData 对象提交请求,该对象将正确地将文件包含在请求中。

请注意,根据您的服务器端技术,您可能需要以非常不同的方式处理具有此类型正文内容的请求。

请注意,htmx 在上传期间基于标准 progress 事件定期触发 htmx:xhr:progress 事件,您可以钩入它来显示上传进度。

有关更高级的表单模式,包括 进度条错误处理,请参阅 示例部分

额外值

您可以使用 hx-vals(JSON 格式的名称-表达式对)和 hx-vars 属性(动态计算的逗号分隔的名称-表达式对)在请求中包含额外值。

确认请求

通常您会在发出请求之前确认操作。htmx 支持 hx-confirm 属性,它允许您使用简单的 JavaScript 对话框确认操作:

<button hx-delete="/account" hx-confirm="您确定要删除您的帐户吗?">
    删除我的帐户
</button>

使用事件,您可以实现更复杂的确认对话框。确认示例 显示了如何使用 sweetalert2 库确认 htmx 操作。

使用事件确认请求

通过 htmx:confirm 事件 进行确认的另一个选项。此事件在请求的 每个 触发器上触发(不仅仅是具有 hx-confirm 属性的元素),并可用于实现异步确认请求。

这里是一个使用 sweet alert 的示例,针对任何具有 confirm-with-sweet-alert='true' 属性的元素:

document.body.addEventListener('htmx:confirm', function(evt) {
  if (evt.target.matches("[confirm-with-sweet-alert='true']")) {
    evt.preventDefault();
    swal({
      title: "您确定吗?",
      text: "您确定您确定吗?",
      icon: "warning",
      buttons: true,
      dangerMode: true,
    }).then((confirmed) => {
      if (confirmed) {
        evt.detail.issueRequest();
      }
    });
  }
});

属性继承

htmx 中的大多数属性都是继承的:它们适用于它们所在的元素以及任何子元素。这允许您将属性“提升”到 DOM 中以避免代码重复。考虑以下 htmx:

<button hx-delete="/account" hx-confirm="您确定吗?">
    删除我的帐户
</button>
<button hx-put="/account" hx-confirm="您确定吗?">
    更新我的帐户
</button>

这里我们有一个重复的 hx-confirm 属性。我们可以将此属性提升到父元素:

<div hx-confirm="您确定吗?">
    <button hx-delete="/account">
        删除我的帐户
    </button>
    <button hx-put="/account">
        更新我的帐户
    </button>
</div>

hx-confirm 属性现在将适用于其中的所有 htmx 驱动元素。

有时您希望撤销此继承。考虑如果我们为此组有一个取消按钮,但不希望它被确认。我们可以像这样在其上添加一个 unset 指令:

<div hx-confirm="您确定吗?">
    <button hx-delete="/account">
        删除我的帐户
    </button>
    <button hx-put="/account">
        更新我的帐户
    </button>
    <button hx-confirm="unset" hx-get="/">
        取消
    </button>
</div>

然后上面的两个按钮将显示确认对话框,但下面的取消按钮不会。

可以使用 hx-disinherit 属性在每个元素和每个属性基础上禁用继承。

如果您希望完全禁用属性继承,您可以将 htmx.config.disableInheritance 配置变量设置为 true。这将禁用继承作为默认值,并允许您使用 hx-inherit 属性显式指定继承。

提升

Htmx 通过 hx-boost 属性支持“提升”常规 HTML 锚和表单。此属性将所有锚标签和表单转换为 AJAX 请求,默认针对页面的 body。

这里是一个示例:

<div hx-boost="true">
    <a href="/blog">博客</a>
</div>

此 div 中的锚标签将向 /blog 发出 AJAX GET 请求,并将响应交换到 body 标签中。

渐进式增强

hx-boost 的一个特性是如果 JavaScript 未启用,它会优雅降级:链接和表单继续工作,它们只是不使用 ajax 请求。这被称为 渐进式增强,它允许更广泛的受众使用您的站点功能。

其他 htmx 模式也可以适应以实现渐进式增强,但它们需要更多思考。

考虑 活动搜索 示例。如其所写,它不会优雅降级:没有启用 JavaScript 的人将无法使用此功能。这是为了简单起见,以保持示例尽可能简短。

但是,您可以将 htmx 增强的输入包装在表单元素中:

<form action="/search" method="POST">
    <input class="form-control" type="search"
        name="search" placeholder="开始输入以搜索用户..."
        hx-post="/search"
        hx-trigger="keyup changed delay:500ms, search"
        hx-target="#search-results"
        hx-indicator=".htmx-indicator">
</form>

这样,启用 JavaScript 的客户端仍然会获得不错的活动搜索 UX,但未启用 JavaScript 的客户端可以按回车键并仍然搜索。更好了,您还可以添加一个“搜索”按钮。您需要使用镜像 action 属性的 hx-post 更新表单,或者在其上使用 hx-boost

您需要在服务器端检查 HX-Request 头,以区分 htmx 驱动的请求和常规请求,以确定要向客户端渲染什么。

其他模式也可以类似地适应以实现您的应用程序的渐进式增强需求。

如您所见,这需要更多思考和更多工作。它还完全排除了某些功能。这些权衡必须由您,开发者,根据您的项目目标和受众做出。

可访问性 是与渐进式增强密切相关的概念。使用渐进式增强技术如 hx-boost 将使您的 htmx 应用程序对广泛的用户更具可访问性。

基于 htmx 的应用程序与正常的、非 AJAX 驱动的 Web 应用程序非常相似,因为 htmx 是面向 HTML 的。

因此,正常的 HTML 可访问性推荐适用。例如:

  • 尽可能使用语义 HTML(即正确的事物使用正确的标签)
  • 确保焦点状态清晰可见
  • 为所有表单字段关联文本标签
  • 使用适当的字体、对比度等最大化应用程序的可读性

WebSockets 和 SSE

WebSockets 和服务器发送事件 (SSE) 通过扩展支持。请参阅 SSE 扩展WebSocket 扩展 页面以了解更多。

历史记录支持

Htmx 提供了一个简单的机制来与 浏览器历史记录 API 交互:

如果您希望给定的元素将其请求 URL 推入浏览器导航栏并将页面当前状态添加到浏览器的历史记录中,请包含 hx-push-url 属性:

<a hx-get="/blog" hx-push-url="true">博客</a>

当用户点击此链接时,htmx 将在向 /blog 发出请求之前快照当前 DOM 并存储它。然后它执行交换并将新位置推入历史记录栈。

当用户按下后退按钮时,htmx 将从存储中检索旧内容并将其交换回目标,模拟“返回”到先前状态。如果位置不在缓存中,htmx 将向给定 URL 发出 ajax 请求,并设置头 HX-History-Restore-Request 为 true,并期望返回整个页面所需的 HTML。您应该始终将 htmx.config.historyRestoreAsHxRequest 设置为 false,以防止 HX-Request 头,然后可以安全地用于返回部分响应。或者,如果 htmx.config.refreshOnHistoryMiss 配置变量设置为 true,它将发出硬浏览器刷新。

注意: 如果您将 URL 推入历史记录,您 必须 能够导航到该 URL 并获取完整页面!用户可以将 URL 复制并粘贴到电子邮件或新标签页中。此外,当从历史记录恢复页面时,如果页面不在历史记录缓存中,htmx 将需要整个页面。

指定历史记录快照元素

默认情况下,htmx 将使用 body 来获取和恢复历史记录快照。这通常是正确的,但如果您希望使用更窄的元素进行快照,您可以使用 hx-history-elt 属性来指定不同的元素。

小心:此元素需要出现在所有页面上,否则从历史记录恢复将无法可靠工作。

通过第三方库撤销 DOM 变异

如果您使用第三方库并希望使用 htmx 历史记录功能,您需要在快照拍摄之前清理 DOM。让我们考虑 Tom Select 库,它使 select 元素成为更丰富的用户体验。让我们设置 TomSelect 将任何具有 .tomselect 类的输入元素转换为丰富的 select 元素。

首先,我们需要在新的内容中初始化具有该类的元素:

htmx.onLoad(function (target) {
    // 在新内容中找到所有应为
    // 编辑器的元素并使用 TomSelect 初始化
    var editors = target.querySelectorAll(".tomselect")
            .forEach(elt => new TomSelect(elt))
});

这将为所有具有 .tomselect 类的输入元素创建丰富的选择器。但是,它变异了 DOM,我们不希望将该变异保存到历史记录缓存中,因为当历史记录内容加载回屏幕时,TomSelect 将被重新初始化。

要处理此问题,我们需要捕获 htmx:beforeHistorySave 事件并通过调用 destroy() 来清理 TomSelect 变异:

htmx.on('htmx:beforeHistorySave', function() {
    // 找到所有 TomSelect 元素
    document.querySelectorAll('.tomSelect')
            .forEach(elt => elt.tomselect.destroy()) // 并在它们上调用 destroy()
})

这将使 DOM 恢复到原始 HTML,从而允许干净的快照。

禁用历史记录快照

可以通过在当前文档中的任何元素或 htmx 加载到当前文档中的任何 HTML 片段上将 hx-history 属性设置为 false 来禁用 URL 的历史记录快照。这可用于防止敏感数据进入 localStorage 缓存,这对于共享/公共计算机很重要。历史记录导航将按预期工作,但恢复时 URL 将从服务器请求而不是本地历史记录缓存。

请求和响应

Htmx 期望其发出的 AJAX 请求的响应为 HTML,通常是 HTML 片段(尽管与 hx-select 标签匹配的完整 HTML 文档也很有用)。然后 htmx 将返回的 HTML 交换到指定目标并使用指定的交换策略。

有时您可能希望在交换中什么都不做,但仍然触发客户端事件(见下文)。

对于这种情况,默认情况下,您可以返回 204 - No Content 响应代码,htmx 将忽略响应的内容。

在服务器返回错误响应(例如 404 或 501)的情况下,htmx 将触发 htmx:responseError 事件,您可以处理它。

在连接错误的情况下,将触发 htmx:sendError 事件。

配置响应处理

您可以通过变异或替换 htmx.config.responseHandling 数组来配置 htmx 的上述行为。此对象是一个 JavaScript 对象的集合,定义如下:

    responseHandling: [
        {code:"204", swap: false},   // 204 - No Content 默认什么都不做,但不是错误
        {code:"[23]..", swap: true}, // 200 和 300 响应是非错误并被交换
        {code:"[45]..", swap: false, error:true}, // 400 和 500 响应不被交换并且是错误
        {code:"...", swap: false}    // 捕获所有其他响应代码
    ]

当 htmx 接收到响应时,它将按顺序迭代 htmx.config.responseHandling 数组,并测试给定对象的 code 属性,当作为正则表达式处理时,是否匹配当前响应。如果条目匹配当前响应代码,它将用于确定是否以及如何处理响应。

此数组中条目的响应处理配置可用字段包括:

  • code - 表示将针对响应代码测试的正则表达式的字符串。
  • swap - 如果响应应交换到 DOM 中,则为 true,否则为 false
  • error - 如果 htmx 应将此响应视为错误,则为 true
  • ignoreTitle - 如果 htmx 应忽略响应中的 title 标签,则为 true
  • select - 用于从响应中选择内容的 CSS 选择器
  • target - 指定响应替代目标的 CSS 选择器
  • swapOverride - 响应的替代交换机制

配置响应处理示例

作为使用此配置的示例,考虑服务器端框架在验证错误发生时响应 422 - Unprocessable Entity 的情况。默认情况下,htmx 将忽略响应,因为它匹配正则表达式 [45]..

使用 元配置 机制配置 responseHandling,我们可以添加以下配置:

<!--
  * 204 No Content 默认什么都不做,但不是错误
  * 2xx、3xx 和 422 响应是非错误并被交换
  * 4xx 和 5xx 响应不被交换并且是错误
  * 使用 "..." 作为捕获所有交换所有其他响应
-->
<meta
	name="htmx-config"
	content='{
        "responseHandling":[
            {"code":"204", "swap": false},
            {"code":"[23]..", "swap": true},
            {"code":"422", "swap": true},
            {"code":"[45]..", "swap": false, "error":true},
            {"code":"...", "swap": true}
        ]
    }'
/>

如果您希望交换所有内容,无论 HTTP 响应代码如何,您可以使用此配置:

<meta name="htmx-config" content='{"responseHandling": [{"code":".*", "swap": true}]}' /> <!--所有响应都被交换-->

最后,值得考虑使用 Response Targets 扩展,它允许您通过属性声明性地配置响应代码的行为。

CORS

在使用跨源上下文中的 htmx 时,请记住配置您的 Web 服务器设置 Access-Control 头,以便 htmx 头在客户端可见。

查看 htmx 实现的所有请求和响应头。

请求头

htmx 在请求中包含了许多有用的头:

描述
HX-Boosted表示请求是通过使用 hx-boost 的元素发出的
HX-Current-URL浏览器的当前 URL
HX-History-Restore-Request如果请求是为了本地历史记录缓存未命中后的历史记录恢复,则为 “true”
HX-Prompthx-prompt 的用户响应
HX-Request始终为 “true”,除非在历史记录恢复请求上如果 htmx.config.historyRestoreAsHxRequest 禁用
HX-Target如果存在,则为目标元素的 id
HX-Trigger-Name如果存在,则为触发元素的 name
HX-Trigger如果存在,则为触发元素的 id

响应头

htmx 支持一些 htmx 特定的响应头:

  • HX-Location - 允许您执行不进行完整页面重新加载的客户端重定向
  • HX-Push-Url - 将新 URL 推入历史记录栈
  • HX-Redirect - 可用于执行到新位置的客户端重定向
  • HX-Refresh - 如果设置为 “true”,客户端将进行完整页面刷新
  • HX-Replace-Url - 替换位置栏中的当前 URL
  • HX-Reswap - 允许您指定响应将如何被交换。请参阅 hx-swap 以获取可能的值
  • HX-Retarget - 更新内容更新的目标到页面上不同元素的 CSS 选择器
  • HX-Reselect - 允许您选择响应中哪一部分用于交换的 CSS 选择器。覆盖触发元素上的现有 hx-select
  • HX-Trigger - 允许您触发客户端事件
  • HX-Trigger-After-Settle - 允许您在结算步骤后触发客户端事件
  • HX-Trigger-After-Swap - 允许您在交换步骤后触发客户端事件

有关 HX-Trigger 头的更多信息,请参阅 HX-Trigger 响应头

通过 htmx 提交表单的好处是不再需要 Post/Redirect/Get 模式。在服务器成功处理 POST 请求后,您不需要返回 HTTP 302 (Redirect)。您可以直接返回新的 HTML 片段。

此外,对于像 HTTP 302 (Redirect) 这样的 3xx 重定向响应代码,上述响应头不会提供给 htmx 处理。相反,浏览器将内部拦截重定向并返回来自重定向 URL 的头和响应。尽可能使用像 200 这样的替代响应代码,以允许返回这些响应头。

请求操作顺序

htmx 请求的操作顺序是:

  • 元素被触发并开始请求
    • 为请求收集值
    • htmx-request 类应用到适当元素
    • 然后通过 AJAX 异步发出请求
      • 收到响应时,将目标元素标记为 htmx-swapping
      • 应用可选的交换延迟(请参阅 hx-swap 属性)
      • 执行实际的内容交换
        • 从目标中移除 htmx-swapping
        • htmx-added 类添加到每个新内容片段
        • htmx-settling 类应用到目标
        • 执行结算延迟(默认:20ms)
        • DOM 被结算
        • 从目标中移除 htmx-settling
        • 从每个新内容片段中移除 htmx-added

您可以使用 htmx-swappinghtmx-settling 类来创建页面之间的 CSS 过渡

验证

Htmx 与 HTML5 验证 API 集成,如果可验证输入无效,则不会为表单发出请求。这适用于 AJAX 请求以及 WebSocket 发送。

Htmx 在验证周围触发事件,可用于钩入自定义验证和错误处理:

  • htmx:validation:validate - 在调用元素的 checkValidity() 方法之前调用。可用于添加自定义验证逻辑
  • htmx:validation:failed - 当 checkValidity() 返回 false 时调用,表示无效输入
  • htmx:validation:halted - 当由于验证错误未发出请求时调用。具体错误可在 event.detail.errors 对象中找到

非表单元素默认在发出请求之前不验证,但您可以通过将 hx-validate 属性设置为 “true” 来启用验证。

正常的浏览器表单提交会自动向用户警报任何验证错误,并自动将焦点放在第一个无效输入上。由于向后兼容性的原因,htmx 默认不向用户报告验证,您应该始终通过将 htmx.config.reportValidityOfForms 设置为 true 来启用此选项,以恢复默认浏览器行为。

验证示例

这里是一个使用 hx-on 属性捕获 htmx:validation:validate 事件的输入示例,并要求输入的值为 foo

<form id="example-form" hx-post="/test">
    <input name="example"
           onkeyup="this.setCustomValidity('') // 在 keyup 上重置验证"
           hx-on:htmx:validation:validate="if(this.value != 'foo') {
                    this.setCustomValidity('请输入值 foo') // 设置验证错误
                    htmx.find('#example-form').reportValidity()          // 报告问题
                }">
</form>

请注意,所有客户端验证必须在服务器端重新执行,因为它们始终可以被绕过。

动画

Htmx 允许您在许多情况下仅使用 HTML 和 CSS 使用 CSS 过渡

有关可用选项的更多详细信息,请参阅 动画指南

扩展

htmx 提供了一个 扩展 机制,允许您自定义库的行为。扩展在 JavaScript 中 定义,然后通过 hx-ext 属性启用。

核心扩展

htmx 支持几个“核心”扩展,由 htmx 开发团队支持:

您可以在 扩展 页面上看到所有可用扩展。

安装扩展

安装由他人创建的 htmx 扩展的最快方式是通过 CDN 加载它们。请记住,始终在扩展之前包含核心 htmx 库并 启用扩展。例如,如果您想使用 response-targets 扩展,您可以将此添加到您的 head 标签:

<head>
    <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js" integrity="sha384-ZBXiYtYQ6hJ2Y0ZNoYuI+Nq5MqWBr+chMrS/RkXpNzQCApHEhOt2aY8EJgqwHLkJ" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/htmx-ext-response-targets@2.0.2" integrity="sha384-UMuM7P2CPg9i2/dfvBlAeqjXITmEWe9k17Mp9X07Z4jXPN21Ychng569t+sUL8oa" crossorigin="anonymous"></script>
</head>
<body hx-ext="extension-name">
    ...

未压缩版本也可在 https://cdn.jsdelivr.net/npm/htmx-ext-extension-name/dist/extension-name.js 处获得(用扩展名称替换 extension-name)。

虽然 CDN 方法简单,但您可能需要考虑 在生产环境中不使用 CDN。安装 htmx 扩展的下一个最简单方法是将它们简单复制到您的项目中。从 https://cdn.jsdelivr.net/npm/htmx-ext-extension-name 下载扩展(用扩展名称替换 extension-name),例如 https://cdn.jsdelivr.net/npm/htmx-ext-response-targets。然后将其添加到项目中的适当目录,并在需要的地方使用 <script> 标签包含它。

对于 npm 风格的构建系统,您可以通过 npm 安装 htmx 扩展(用扩展名称替换 extension-name):

npm install htmx-ext-extension-name

安装后,您需要使用适当的工具来捆绑 node_modules/htmx-ext-extension-name/dist/extension-name.js(或 .min.js)。例如,您可以将扩展与来自 node_modules/htmx.org/dist/htmx.js 的 htmx 核心和项目特定代码捆绑。

如果您使用捆绑器管理您的 JavaScript(例如 Webpack、Rollup):

  • 通过 npm 安装 htmx.orghtmx-ext-extension-name(用扩展名称替换 extension-name
  • 将两个包导入到您的 index.js
import `htmx.org`;
import `htmx-ext-extension-name`; // 用扩展名称替换 `extension-name` 

注意:Idiomorph 不遵循 htmx 扩展的命名约定。使用 idiomorph 而不是 htmx-ext-idiomorph。例如,https://cdn.jsdelivr.net/npm/idiomorphnpm install idiomorph

注意:托管在此仓库之外的社区扩展可能有不同的安装说明。请检查相应仓库以获取设置指导。

启用扩展

要启用扩展,请在 <body> 或另一个 HTML 元素上添加 hx-ext="extension-name" 属性(用扩展名称替换 extension-name)。扩展将应用于所有子元素。

以下示例显示了如何启用 response-targets 扩展,允许您基于 HTTP 响应代码指定不同的目标元素进行交换。

<body hx-ext="response-targets">
    ...
    <button hx-post="/register" hx-target="#response-div" hx-target-404="#not-found">
        注册!
    </button>
    <div id="response-div"></div>
    <div id="not-found"></div>
    ...
</body>

创建扩展

如果您有兴趣为 htmx 添加自己的扩展,请 参阅扩展文档

事件和日志

Htmx 有一个广泛的 事件机制,它也充当日志系统。

如果您想注册给定的 htmx 事件,您可以使用

document.body.addEventListener('htmx:load', function(evt) {
    myJavascriptLib.init(evt.detail.elt);
});

或者,如果您更喜欢,您可以使用以下 htmx 助手:

htmx.on("htmx:load", function(evt) {
    myJavascriptLib.init(evt.detail.elt);
});

htmx:load 事件每次由 htmx 加载到 DOM 中的元素都会触发,并且有效地等同于正常的 load 事件。

htmx 事件的一些常见用途包括:

使用事件初始化第三方库

使用 htmx:load 事件初始化内容如此常见,以至于 htmx 提供了一个助手函数:

htmx.onLoad(function(target) {
    myJavascriptLib.init(target);
});

这与第一个示例做同样的事情,但更干净一些。

使用事件配置请求

您可以处理 htmx:configRequest 事件,以便在发出 AJAX 请求之前修改它:

document.body.addEventListener('htmx:configRequest', function(evt) {
    evt.detail.parameters['auth_token'] = getAuthToken(); // 在请求中添加新参数
    evt.detail.headers['Authentication-Token'] = getAuthToken(); // 在请求中添加新头
});

这里我们在发送请求之前向请求添加参数和头。

使用事件修改交换行为

您可以处理 htmx:beforeSwap 事件,以便修改 htmx 的交换行为:

document.body.addEventListener('htmx:beforeSwap', function(evt) {
    if(evt.detail.xhr.status === 404){
        // 当发生 404 时警报用户(也许使用比 alert() 更好的机制)
        alert("错误:找不到资源");
    } else if(evt.detail.xhr.status === 422){
        // 允许 422 响应交换,因为我们使用它作为信号
        // 表单提交了坏数据并希望与错误重新渲染
        //
        // 设置 isError 为 false 以避免在控制台中记录错误
        evt.detail.shouldSwap = true;
        evt.detail.isError = false;
    } else if(evt.detail.xhr.status === 418){
        // 如果返回响应代码 418(我是茶壶),则将
        // 响应的内容重新针对 id 为 `teapot` 的元素
        evt.detail.shouldSwap = true;
        evt.detail.target = htmx.find("#teapot");
    }
});

这里我们处理了一些 400 级错误响应代码,这些代码通常不在 htmx 中执行交换。

事件命名

请注意,所有事件都以两种不同的名称触发

  • 驼峰大小写
  • Kebab 大小写

因此,例如,您可以监听 htmx:afterSwaphtmx:after-swap。这有助于与其他库的互操作性。例如,Alpine.js 需要 kebab 大小写。

日志

如果您在 htmx.logger 上设置日志记录器,每个事件都会被记录。这对于故障排除非常有用:

htmx.logger = function(elt, event, data) {
    if(console) {
        console.log(event, elt, data);
    }
}

调试

使用 htmx(或任何其他声明性语言)的声明性和事件驱动编程可以是一种美妙且高效的活动,但与命令式方法相比,一个缺点是调试起来可能更棘手。

弄清楚为什么 没有 发生某事,例如,如果您不知道技巧,可能会很困难。

嗯,这里有一些技巧:

您可以使用第一个调试工具是 htmx.logAll() 方法。这将记录 htmx 触发的每个事件,并允许您确切看到库正在做什么。

htmx.logAll();

当然,这不会告诉您 htmx 为什么 没有做某事。您可能也不知道 DOM 元素触发了 什么 事件作为触发器。要解决此问题,您可以使用浏览器控制台中可用的 monitorEvents() 方法:

monitorEvents(htmx.find("#theElement"));

这将向控制台吐出 id 为 theElement 的元素上发生的所有事件,并允许您确切看到它发生了什么。

请注意,这 从控制台工作,您不能将其嵌入到页面上的脚本标签中。

最后,迫不得已,您可能想通过加载未压缩版本来调试 htmx.js。它是大约 2500 行 JavaScript,所以不是不可逾越的代码量。您很可能想在 issueAjaxRequest()handleAjaxResponse() 方法中设置断点,以查看发生了什么。

并且随时欢迎加入 Discord 如果您需要帮助。

创建演示

有时,为了演示 bug 或澄清用法,能够使用像 jsfiddle 这样的 JavaScript 片段站点很不错。为了方便轻松创建演示,htmx 托管了一个演示脚本站点,它将安装:

  • htmx
  • hyperscript
  • 一个请求模拟库

只需将以下脚本标签添加到您的演示/fiddle/whatever:

<script src="https://demo.htmx.org"></script>

此助手允许您通过添加具有 url 属性指示哪个 URL 的 template 标签来添加模拟响应。该 url 的响应将是 template 的 innerHTML,使其易于构建模拟响应。您可以使用 delay 属性向响应添加延迟,该属性应为指示延迟毫秒数的整数

您可以使用 ${} 语法在模板中嵌入简单表达式。

请注意,这仅应用于演示,并且绝不会保证长时间工作,因为它总是获取最新版本的 htmx 和 hyperscript!

演示示例

以下是代码在行动中的示例:

<!-- 加载演示环境 -->
<script src="https://demo.htmx.org"></script>

<!-- 发布到 /foo -->
<button hx-post="/foo" hx-target="#result">
    计数向上
</button>
<output id="result"></output>

<!-- 在 template 标签中使用一些动态内容响应 /foo -->
<script>
    globalInt = 0;
</script>
<template url="/foo" delay="500"> <!-- 注意 url 和 delay 属性 -->
    ${globalInt++}
</template>

脚本

虽然 htmx 鼓励使用超媒体方法构建 Web 应用程序,但它为客户端脚本提供了许多选项。脚本包含在 Web 架构的 REST-ful 描述中,请参阅:Code-On-Demand。尽可能可行,我们推荐在您的 Web 应用程序中使用 超媒体友好 的脚本方法:

htmx 和脚本解决方案之间的主要集成点是 htmx 发送并可以响应的 事件。有关通过事件将 JavaScript 库与 htmx 集成的良好模板,请参阅 第三方 JavaScript 部分中的 SortableJS 示例。

与 htmx 配对良好的脚本解决方案包括:

  • VanillaJS - 简单地使用 JavaScript 的内置功能钩入事件处理程序以响应 htmx 发出的事件可以很好地工作。这是一种极其轻量且日益流行的方法。
  • AlpineJS - Alpine.js 提供了一组丰富的工具来创建复杂的 front-end 脚本,包括响应式编程支持,同时保持极其轻量。Alpine 鼓励我们认为与 htmx 配对良好的“内联脚本”方法。
  • jQuery - 尽管其年龄和在某些圈子中的声誉,jQuery 与 htmx 配对非常好,特别是在已经有很多 jQuery 的旧代码库中。
  • hyperscript - Hyperscript 是一种由创建 htmx 的同一团队创建的实验性 front-end 脚本语言。它设计为很好地嵌入 HTML,既响应又创建事件,并与 htmx 配对非常好。

我们在 我们的书 中有一个名为 “客户端脚本” 的章节,探讨了如何将脚本集成到您的基于 htmx 的应用程序中。

hx-on* 属性

HTML 允许通过 onevent 属性 嵌入内联脚本,例如 onClick

<button onclick="alert('您点击了我!')">
    点击我!
</button>

此功能允许脚本逻辑与逻辑适用的 HTML 元素共置,提供良好的 行为局部性 (LoB)。不幸的是,HTML 只允许 on* 属性用于固定数量的 特定 DOM 事件(例如 onclick),并且不提供响应元素上任意事件的泛化机制。

为了解决此缺点,htmx 提供 hx-on* 属性。这些属性允许您以保留标准 on* 属性 LoB 的方式响应任何事件。

如果我们想使用 hx-on 属性响应 click 事件,我们将这样写:

<button hx-on:click="alert('您点击了我!')">
    点击我!
</button>

因此,字符串 hx-on,后跟冒号(或破折号),然后是事件名称。

对于 click 事件,当然,我们推荐坚持使用标准的 onclick 属性。但是,考虑一个希望使用 htmx:config-request 事件向请求添加参数的 htmx 驱动按钮。这将无法使用标准的 on* 属性实现,但可以使用 hx-on:htmx:config-request 属性实现:

<button hx-post="/example"
        hx-on:htmx:config-request="event.detail.parameters.example = 'Hello Scripting!'">
    发布我!
</button>

这里在发出之前将 example 参数添加到 POST 请求中,值为 ‘Hello Scripting!’。

另一个用例是使用 afterRequest 事件在成功请求上 重置用户输入,避免需要像带外交换这样的东西。

hx-on* 属性是一种非常简单的泛化嵌入式脚本机制。它 不是 更全面开发的 front-end 脚本解决方案如 AlpineJS 或 hyperscript 的替代品。但是,它可以增强在您的 htmx 驱动应用程序中使用基于 VanillaJS 的脚本方法。

请注意,HTML 属性是 不区分大小写 的。这意味着不幸的是,依赖大写/驼峰大小写的イベント无法响应。如果您需要支持驼峰大小写事件,我们推荐使用更全面的功能脚本解决方案如 AlpineJS 或 hyperscript。htmx 正是为此原因而以驼峰大小写和 kebab 大小写分派所有事件。

第三方 JavaScript

Htmx 与第三方库集成得相当好。如果库在 DOM 上触发事件,您可以使用这些事件从 htmx 触发请求。

一个很好的示例是 SortableJS 示例

<form class="sortable" hx-post="/items" hx-trigger="end">
    <div class="htmx-indicator">正在更新...</div>
    <div><input type='hidden' name='item' value='1'/>项目 1</div>
    <div><input type='hidden' name='item' value='2'/>项目 2</div>
    <div><input type='hidden' name='item' value='2'/>项目 3</div>
</form>

与 Sortable 一样,与大多数 JavaScript 库一样,您需要在某个时候初始化内容。

在 jQuery 中,您可能这样实现:

$(document).ready(function() {
    var sortables = document.body.querySelectorAll(".sortable");
    for (var i = 0; i < sortables.length; i++) {
        var sortable = sortables[i];
        new Sortable(sortable, {
            animation: 150,
            ghostClass: 'blue-background-class'
        });
    }
});

在 htmx 中,您将改用 htmx.onLoad 函数,并且您将仅从新加载的内容中选择,而不是整个文档:

htmx.onLoad(function(content) {
    var sortables = content.querySelectorAll(".sortable");
    for (var i = 0; i < sortables.length; i++) {
        var sortable = sortables[i];
        new Sortable(sortable, {
            animation: 150,
            ghostClass: 'blue-background-class'
        });
    }
})

这将确保当 htmx 将新内容添加到 DOM 中时,可排序元素被正确初始化。

如果 JavaScript 添加到 DOM 中的内容具有 htmx 属性,您需要确保使用 htmx.process() 函数初始化此内容。

例如,如果您使用 fetch API 获取一些数据并将其放入 div 中,并且该 HTML 中有 htmx 属性,您需要添加对 htmx.process() 的调用,如下所示:

let myDiv = document.getElementById('my-div')
fetch('http://example.com/movies.json')
    .then(response => response.text())
    .then(data => { myDiv.innerHTML = data; htmx.process(myDiv); } );

一些第三方库从 HTML 模板元素创建内容。例如,Alpine JS 使用模板上的 x-if 属性来有条件地添加内容。此类模板最初不是 DOM 的一部分,并且如果它们包含 htmx 属性,则在加载后需要调用 htmx.process()。以下示例使用 Alpine 的 $watch 函数来查找会触发条件内容的数值变化:

<div x-data="{show_new: false}"
    x-init="$watch('show_new', value => {
        if (show_new) {
            htmx.process(document.querySelector('#new_content'))
        }
    })">
    <button @click = "show_new = !show_new">切换新内容</button>
    <template x-if="show_new">
        <div id="new_content">
            <a hx-get="/server/newstuff" href="#">新可点击</a>
        </div>
    </template>
</div>

Web Components

有关如何将 htmx 与 Web Components 集成的示例,请参阅 Web Components 示例 页面。

缓存

htmx 开箱即用地支持标准的 HTTP 缓存 机制。

如果您的服务器为给定 URL 的响应添加 Last-Modified HTTP 响应头,浏览器将自动为同一 URL 的下一个请求添加 If-Modified-Since 请求 HTTP 头。请注意,如果您的服务器可以根据其他头为同一 URL 渲染不同的内容,您需要使用 Vary 响应 HTTP 头。例如,如果您的服务器在缺少 HX-Request 头或 false 时渲染完整 HTML,并在 HX-Request: true 时渲染该 HTML 的片段,您需要添加 Vary: HX-Request。这会导致缓存基于响应 URL 和 HX-Request 请求头的复合键,而不是仅基于响应 URL。始终禁用 htmx.config.historyRestoreAsHxRequest,以便这些历史完整 HTML 请求不会与部分片段响应缓存。

如果您无法(或不愿)使用 Vary 头,您可以替代地将配置参数 getCacheBusterParam 设置为 true。如果设置了此配置变量,htmx 将在其发出的 GET 请求中包含一个缓存破坏参数,这将防止浏览器将基于 htmx 和非基于 htmx 的响应缓存在同一缓存槽中。

htmx 也如预期般与 ETag 一起工作。请注意,如果您的服务器可以为同一 URL 渲染不同的内容(例如,取决于 HX-Request 头的值),服务器需要为每个内容生成不同的 ETag

安全性

htmx 允许您直接在 DOM 中定义逻辑。这有许多优势,最大的是 行为局部性,这使您的系统更容易理解和维护。

然而,此方法的一个问题是安全性:由于 htmx 增加了 HTML 的表达能力,如果恶意用户能够将 HTML 注入您的应用程序,他们可以利用 htmx 的这种表达能力来实现恶意目的。

规则 1:转义所有用户内容

HTML 基于 Web 开发的第一个规则一直是:不要信任来自用户的输入。您应该转义注入到站点中的所有第三方不受信任的内容。这是为了防止诸如 XSS 攻击 等问题的发生。

有关 XSS 及其预防的广泛文档可在优秀的 OWASP 网站 上找到,包括 跨站脚本预防备忘单

好消息是,这是一个非常古老且众所周知的话题,绝大多数服务器端模板语言支持 自动转义 内容以防止此类问题。

话虽如此,有时人们选择更危险地注入 HTML,通常通过其模板语言中的某种 raw() 机制。这可以出于好的原因完成,但如果注入的内容来自第三方,则 必须 进行清理,包括移除以 hx-data-hx 开头的属性,以及内联 <script> 标签等。

如果您正在注入原始 HTML 并进行自己的转义,最佳实践是 白名单 您允许的属性和标签,而不是黑名单您不允许的那些。

htmx 安全工具

当然,bug 会发生,开发者并不完美,因此为您的 Web 应用程序采用分层安全方法是好的,htmx 也提供了工具来帮助保护您的应用程序。

让我们来看看它们。

hx-disable

htmx 提供的第一个工具是 hx-disable 属性,用于进一步保护您的应用程序。此属性将防止处理给定元素上的所有 htmx 属性,以及其内部的所有元素。因此,例如,如果您在模板中包含原始 HTML 内容(再次,这不推荐!),则可以将一个带有 hx-disable 属性的 div 放置在内容周围:

<div hx-disable>
    <%= raw(user_content) %>
</div>

htmx 将不会处理该内容中找到的任何与 htmx 相关的属性或功能。此属性无法通过注入进一步内容来禁用:如果在元素的父层次结构中任何地方找到 hx-disable 属性,则 htmx 将不会处理它。

hx-history

另一个安全考虑是 htmx 历史记录缓存。您可能有包含敏感数据的页面,您不希望它们存储在用户的 localStorage 缓存中。您可以通过在页面上的任何地方包含 hx-history 属性并将其值设置为 false 来从历史记录缓存中省略给定页面。

配置选项

htmx 还提供了与安全性相关的配置选项:

  • htmx.config.selfRequestsOnly - 如果设置为 true,则仅允许对当前文档相同域的请求
  • htmx.config.allowScriptTags - htmx 将处理它加载的新内容中找到的 <script> 标签。如果您希望禁用此行为,可以将此配置变量设置为 false
  • htmx.config.historyCacheSize - 可以设置为 0 以避免在 localStorage 缓存中存储任何 HTML
  • htmx.config.allowEval - 可以设置为 false 以禁用 htmx 依赖 eval 的所有功能:
    • 事件过滤器
    • hx-on: 属性
    • js: 前缀的 hx-vals
    • js: 前缀的 hx-headers

请注意,通过禁用 eval() 移除的所有功能都可以使用您自己的自定义 JavaScript 和 htmx 事件模型重新实现。

事件

如果您希望允许对当前主机之外的一些域的请求,但不完全开放,您可以使用 htmx:validateUrl 事件。此事件将在 detail.url 槽中提供请求 URL,以及 sameHost 属性。

您可以检查这些值,如果请求无效,则在事件上调用 preventDefault() 以防止发出请求。

document.body.addEventListener('htmx:validateUrl', function (evt) {
  // 只允许对当前服务器以及 myserver.com 的请求
  if (!evt.detail.sameHost && evt.detail.url.hostname !== "myserver.com") {
    evt.preventDefault();
  }
});

CSP 选项

浏览器还提供了进一步保护您的 Web 应用程序的工具。最强大的工具是 内容安全策略 (CSP)。使用 CSP,您可以告诉浏览器,例如,不向非源主机发出请求,不评估内联脚本标签等。

以下是一个在 meta 标签中的 CSP 示例:

    <meta http-equiv="Content-Security-Policy" content="default-src 'self';">

这告诉浏览器“仅允许连接到原始(源)域”。这与 htmx.config.selfRequestsOnly 是多余的,但采用分层安全方法是合理的,实际上在处理应用程序安全时是理想的。

有关 CSP 的完整讨论超出了本文档的范围,但 MDN 文章 提供了探索此主题的良好起点。

CSRF 预防

CSRF 令牌的分配和检查通常是后端责任,但 htmx 可以支持使用 hx-headers 属性自动返回 CSRF 令牌与每个请求。该属性需要添加到发出请求的元素或其祖先元素之一。这使得 htmlbody 元素成为将 CSRF 令牌添加到 HTTP 请求头的有效全局载体,如下所示。

注意:hx-boost 不更新 <html><body> 标签;如果使用此功能与 hx-boost,请确保在 被替换的元素上包含 CSRF 令牌。许多 Web 框架支持自动将 CSRF 令牌作为隐藏输入插入到 HTML 表单中。这在可能时被鼓励。

<html lang="en" hx-headers='{"X-CSRF-TOKEN": "CSRF_TOKEN_INSERTED_HERE"}'>
    :
</html>
    <body hx-headers='{"X-CSRF-TOKEN": "CSRF_TOKEN_INSERTED_HERE"}'>
        :
    </body>

上述元素在 HTML 文档中通常是唯一的,并且应该易于在模板中定位。

配置 htmx

Htmx 有一些配置选项,可以通过编程方式或声明方式访问。它们列在下面:

配置变量信息
htmx.config.historyEnabled默认值为 true,仅用于测试
htmx.config.historyCacheSize默认值为 10
htmx.config.refreshOnHistoryMiss默认值为 false,如果设置为 true,htmx 将在历史记录未命中时发出完整页面刷新,而不是使用 AJAX 请求
htmx.config.defaultSwapStyle默认值为 innerHTML
htmx.config.defaultSwapDelay默认值为 0
htmx.config.defaultSettleDelay默认值为 20
htmx.config.includeIndicatorStyles默认值为 true(确定是否加载指示器样式)
htmx.config.indicatorClass默认值为 htmx-indicator
htmx.config.requestClass默认值为 htmx-request
htmx.config.addedClass默认值为 htmx-added
htmx.config.settlingClass默认值为 htmx-settling
htmx.config.swappingClass默认值为 htmx-swapping
htmx.config.allowEval默认值为 true,可用于禁用 htmx 对某些功能(例如触发器过滤器)的 eval 使用
htmx.config.allowScriptTags默认值为 true,确定 htmx 是否处理在新内容中找到的脚本标签
htmx.config.inlineScriptNonce默认值为 '',表示不会向内联脚本添加 nonce
htmx.config.attributesToSettle默认值为 ["class", "style", "width", "height"],在结算阶段要结算的属性
htmx.config.inlineStyleNonce默认值为 '',表示不会向内联样式添加 nonce
htmx.config.useTemplateFragments默认值为 false,用于从服务器解析内容的 HTML 模板标签(不兼容 IE11!)
htmx.config.wsReconnectDelay默认值为 full-jitter
htmx.config.wsBinaryType默认值为 blob,通过 WebSocket 连接接收的 二进制数据类型
htmx.config.disableSelector默认值为 [hx-disable], [data-hx-disable],htmx 不会处理其上或父元素具有此属性的元素
htmx.config.withCredentials默认值为 false,允许使用凭证(如 cookie、授权头或 TLS 客户端证书)的跨站 Access-Control 请求
htmx.config.timeout默认值为 0,请求在自动终止前可以持续的毫秒数
htmx.config.scrollBehavior默认值为 ‘instant’,使用 hx-swapshow 修饰符时的滚动行为。允许的值为 instant(滚动应立即以单次跳跃发生)、smooth(滚动应平滑动画)和 auto(滚动行为由 scroll-behavior 的计算值确定)。
htmx.config.defaultFocusScroll如果焦点元素应滚动到视图中,默认值为 false,并可以使用 focus-scroll 交换修饰符覆盖。
htmx.config.getCacheBusterParam默认值为 false,如果设置为 true,htmx 将以格式 org.htmx.cache-buster=targetElementId 将目标元素追加到 GET 请求中
htmx.config.globalViewTransitions如果设置为 true,htmx 将在交换新内容时使用 View Transition API。
htmx.config.methodsThatUseUrlParams默认值为 ["get", "delete"],htmx 将通过在 URL 中编码这些方法的参数来格式化请求,而不是请求正文
htmx.config.selfRequestsOnly默认值为 true,是否仅允许对当前文档相同域的 AJAX 请求
htmx.config.ignoreTitle默认值为 false,如果设置为 true,htmx 在新内容中找到 title 标签时不会更新文档标题
htmx.config.disableInheritance在 htmx 中禁用属性继承,然后可以使用 hx-inherit 属性覆盖
htmx.config.scrollIntoViewOnBoost默认值为 true,是否将提升元素的靶滚动到视口中。如果提升元素上省略 hx-target,则目标默认为 body,导致页面滚动到顶部。
htmx.config.triggerSpecsCache默认值为 null,用于存储已评估的触发器规范的缓存,提高解析性能但以更多内存使用为代价。您可以定义一个简单对象来使用永不清除的缓存,或使用 代理对象 实现您自己的系统
htmx.config.responseHandling默认 响应处理 行为可以在这里配置响应状态代码,以交换或错误
htmx.config.allowNestedOobSwaps默认值为 true,是否处理主响应元素内部嵌套元素的 OOB 交换。请参阅 嵌套 OOB 交换
htmx.config.historyRestoreAsHxRequest默认值为 true,是否将历史记录缓存未命中完整页面重新加载请求视为 “HX-Request” 通过返回此响应头。当使用 HX-Request 头可选返回部分响应时,应始终禁用此选项

您可以在 JavaScript 中直接设置它们,或者使用 meta 标签:

<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>

结论

就是这样!

享受 htmx 吧!您可以在不编写大量代码的情况下 完成相当多的工作