文档

#htmx 简介

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

要了解 htmx,首先让我们看一下一个锚标签

<a href="/blog">Blog</a>

这个锚标签告诉浏览器

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

考虑到这一点,请考虑以下 HTML 代码段

<button hx-post="/clicked"
    hx-trigger="click"
    hx-target="#parent-div"
    hx-swap="outerHTML"
>
    Click Me!
</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">Click Me!</a>

最后,htmx 版本 1 仍然受到支持,并且支持 IE11。

#1.x 到 2.x 迁移指南

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

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

#安装

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

#通过 CDN (例如 unpkg.com)

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

<script src="https://unpkg.com/[email protected]" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>

还提供了一个未压缩的版本

<script src="https://unpkg.com/[email protected]/dist/htmx.js" integrity="sha384-BBDmZzVt6vjz5YbQqZPtFZW82o8QotoM7RUp5xOxV3nSJ8u2pSdtzFAbGKzTlKtg" crossorigin="anonymous"></script>

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

#下载副本

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

下载 htmx.min.js 从 unpkg.com,将其添加到您项目中的适当目录,并使用 <script> 标签在必要的地方包含它

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

#npm

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

npm install [email protected]

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

#Webpack

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

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

如果您想使用全局 htmx 变量 (推荐),您需要将其注入到窗口范围内

  • 创建一个自定义 JS 文件
  • 将此文件导入到您的 index.js 中 (在第 2 步的导入下面)
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">
    Put To Messages
</button>

这告诉浏览器

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

#触发请求

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

  • inputtextarea & selectchange 事件中被触发
  • formsubmit 事件中被触发
  • 其他所有内容在 click 事件中被触发

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

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

<div hx-post="/mouse_entered" hx-trigger="mouseenter">
    [Here Mouse, Mouse!]
</div>

#触发器修饰符

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

<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
    [Here Mouse, Mouse!]
</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="Search..."
>
<div id="search-results"></div>

此输入将在按键事件发生 500 毫秒后发出请求,前提是输入已经更改,并将结果插入 id 为 search-resultsdiv 中。

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

#触发器过滤器

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

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

<div hx-get="/clicked" hx-trigger="click[ctrlKey]">
    Control Click Me
</div>

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

#特殊事件

htmx 提供了一些特殊事件,用于在 hx-trigger 中使用

  • load - 在元素第一次加载时触发一次
  • revealed - 在元素第一次滚动到视窗中时触发一次
  • intersect - 在元素第一次与视窗相交时触发一次。这支持两个附加选项
    • root:<selector> - 用于相交的根元素的 CSS 选择器
    • threshold:<float> - 一个介于 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 中使用 htmx-indicator 类来实现这一点。

htmx-indicator 类被定义为,具有此类的任何元素的透明度默认情况下为 0,使其不可见,但存在于 DOM 中。

当 htmx 发出请求时,它将在元素 (请求元素或另一个元素,如果指定) 上放置一个 htmx-request 类。htmx-request 类将导致具有 htmx-indicator 类在其上的子元素过渡到透明度为 1,显示指示器。

<button hx-get="/click">
    Click Me!
    <img class="htmx-indicator" src="/spinner.gif">
</button>

这里我们有一个按钮。当它被点击时,htmx-request 类将被添加到它,这将显示旋转 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">
        Click Me!
    </button>
    <img id="indicator" class="htmx-indicator" src="/spinner.gif"/>
</div>

在这里,我们通过 ID 明确调用指示器。请注意,我们也可以将类放在父级 `div` 上,并获得相同的效果。

你也可以添加 disabled 属性 到元素中,以便在使用 hx-disabled-elt 属性期间进行请求。

#目标

如果你希望响应被加载到除发出请求的元素之外的其他元素中,可以使用 hx-target 属性,它接受 CSS 选择器。回顾我们的实时搜索示例

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

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

#扩展 CSS 选择器

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

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

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

这种相对目标对于创建灵活的用户界面非常有用,而无需在你的 DOM 中添加大量 id 属性。

#交换

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

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

#变形交换

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

以下扩展可用于变形式交换

#视图过渡

新的实验性 视图过渡 API 为开发人员提供了一种在不同 DOM 状态之间创建动画过渡的方法。它仍在积极开发中,并非所有浏览器都可用,但 htmx 提供了一种使用此新 API 的方法,如果某个浏览器中不可用,则会回退到非过渡机制。

你可以使用以下方法尝试使用此新 API

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

视图过渡可以使用 CSS 进行配置,如 Chrome 文档中对此功能的概述

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

#交换选项

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

    <button hx-post="/like" hx-swap="outerHTML ignoreTitle:true">Like</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">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">Submit</button>
</form>

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

htmx 还支持以编程方式取消请求:你可以向元素发送 htmx:abort 事件以取消任何正在进行的请求

<button id="request-button" hx-post="/example">
    Issue Request
</button>
<button onclick="htmx.trigger('#request-button', 'htmx:abort')">
    Cancel Request
</button>

有关更多示例和详细信息,请参阅 hx-sync 属性页面。

#CSS 过渡

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

<div id="div1">Original Content</div>

假设此内容由 htmx 通过 ajax 请求替换为以下新内容

<div id="div1" class="red">New Content</div>

请注意两件事

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

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

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

当 htmx 交换此新内容时,它将以一种方式执行,CSS 过渡将应用于新内容,从而为你提供一个流畅的新状态过渡。

因此,总而言之,你只需保持元素的 id 在请求之间保持稳定,即可对元素使用 CSS 过渡!

你可以参阅 动画示例 以获取更多详细信息和现场演示。

#详细信息

要了解 CSS 过渡如何在 htmx 中实际工作,你必须了解 htmx 使用的底层交换和沉淀模型。

当从服务器收到新内容时,在交换新内容之前,将检查页面上现有的内容,以查找通过 id 属性匹配的元素。如果在新内容中找到与元素匹配的元素,则在交换发生之前,将旧内容的属性复制到新元素。然后交换新内容,但使用的属性值。最后,在新内容沉淀延迟后(默认情况下为 20 毫秒),交换新的属性值。有点奇怪,但这正是允许 CSS 过渡在没有开发者任何 JavaScript 的情况下工作的原理。

#带外交换

如果你想使用 id 属性直接将来自响应的内容交换到 DOM 中,可以在响应 html 中使用 hx-swap-oob 属性

<div id="message" hx-swap-oob="true">Swap me directly!</div>
Additional Content

在此响应中,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 请求,则将包含最近封闭表单的所有输入的值。

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

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

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

#文件上传

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

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

请注意,htmx 会定期触发一个 htmx:xhr:progress 事件,该事件基于上传过程中的标准 progress 事件,您可以将其挂钩以显示上传进度。

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

#额外值

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

#确认请求

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

<button hx-delete="/account" hx-confirm="Are you sure you wish to delete your account?">
    Delete My Account
</button>

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

#使用事件确认请求

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

以下是在任何具有 confirm-with-sweet-alert='true' 属性的元素上使用 sweet alert 的示例。

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

#属性继承

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

<button hx-delete="/account" hx-confirm="Are you sure?">
    Delete My Account
</button>
<button hx-put="/account" hx-confirm="Are you sure?">
    Update My Account
</button>

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

<div hx-confirm="Are you sure?">
    <button hx-delete="/account">
        Delete My Account
    </button>
    <button hx-put="/account">
        Update My Account
    </button>
</div>

现在,此 hx-confirm 属性将应用于其中的所有由 htmx 支持的元素。

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

<div hx-confirm="Are you sure?">
    <button hx-delete="/account">
        Delete My Account
    </button>
    <button hx-put="/account">
        Update My Account
    </button>
    <button hx-confirm="unset" hx-get="/">
        Cancel
    </button>
</div>

然后,前两个按钮将显示一个确认对话框,但底部的取消按钮不会显示。

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

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

#增强

Htmx 支持使用 hx-boost 属性增强常规 HTML 锚点和表单。此属性会将所有锚点标签和表单转换为 AJAX 请求,这些请求默认情况下会针对页面的主体。

这是一个例子。

<div hx-boost="true">
    <a href="/blog">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="Begin typing to search users..."
        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">Blog</a>

当用户点击此链接时,htmx 会在向 /blog 发出请求之前,对当前 DOM 进行快照并将其存储起来。然后,它会进行交换并将新位置推送到历史记录堆栈中。

当用户点击后退按钮时,htmx 会从存储中检索旧内容并将其交换回目标,从而模拟“返回”到先前状态。如果在缓存中找不到该位置,htmx 会向给定的 URL 发出 ajax 请求,并将标头 HX-History-Restore-Request 设置为 true,并期望返回整个页面所需的 HTML。或者,如果 htmx.config.refreshOnHistoryMiss 配置变量设置为 true,它将发出硬浏览器刷新。

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

#指定历史记录快照元素

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

注意:此元素需要存在于所有页面上,否则从历史记录中恢复将无法可靠地工作。

#撤消第三方库的 DOM 变异

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

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

htmx.onLoad(function (target) {
    // find all elements in the new content that should be
    // an editor and init w/ TomSelect
    var editors = target.querySelectorAll(".tomselect")
            .forEach(elt => new TomSelect(elt))
});

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

为了解决这个问题,我们需要捕获 htmx:beforeHistorySave 事件,并通过对它们调用 destroy() 来清理 TomSelect 修改。

htmx.on('htmx:beforeHistorySave', function() {
    // find all TomSelect elements
    document.querySelectorAll('.tomSelect')
            .forEach(elt => elt.tomselect.destroy()) // and call destroy() on them
})

这将使 DOM 回到原始 HTML,从而允许进行干净的快照。

#禁用历史记录快照

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

#请求和响应

Htmx 期望它发出的 AJAX 请求的响应为 HTML,通常为 HTML 片段(尽管完整的 HTML 文档与 hx-select 标签相匹配也很有用)。然后,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 by default does nothing, but is not an error
        {code:"[23]..", swap: true}, // 200 & 300 responses are non-errors and are swapped
        {code:"[45]..", swap: false, error:true}, // 400 & 500 responses are not swapped and are errors
        {code:"...", swap: false}    // catch all for any other response code
    ]

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

此数组中条目上可用于响应处理配置的字段为

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

#配置响应处理示例

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

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

<!--
  * 204 No Content by default does nothing, but is not an error
  * 2xx, 3xx and 422 responses are non-errors and are swapped
  * 4xx & 5xx responses are not swapped and are errors
  * all other responses are swapped using "..." as a catch-all
-->
<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}]}' /> <!--all responses are swapped-->

最后,值得考虑使用 响应目标 扩展,它允许您通过属性以声明方式配置响应代码的行为。

#CORS

在跨域环境中使用 htmx 时,请记住配置 Web 服务器以设置 Access-Control 标头,以便 htmx 标头在客户端可见。

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

#请求标头

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

标头描述
HX-Boosted指示请求是通过使用 hx-boost 的元素进行的
HX-Current-URL浏览器的当前 URL
HX-History-Restore-Request如果请求是在本地历史记录缓存中未命中后进行的历史记录恢复,则为“true”
HX-Prompt用户对 hx-prompt 的响应
HX-Request始终为“true”
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 - 允许您在 settle 阶段之后触发客户端事件
  • HX-Trigger-After-Swap - 允许您在 swap 阶段之后触发客户端事件

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

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

此外,对于 3xx 重定向响应代码(如 HTTP 302(重定向)),不会将上述响应标头提供给 htmx 以进行处理。相反,浏览器会在内部拦截重定向,并返回重定向 URL 的标头和响应。在可能的情况下,请使用替代响应代码(如 200),以允许返回这些响应标头。

#请求操作顺序

htmx 请求中的操作顺序为

  • 元素被触发并开始请求
    • 收集请求的值
    • htmx-request 类被应用到相应的元素
    • 然后通过 AJAX 异步发出请求
      • 收到响应后,目标元素将标记为 htmx-swapping
      • 应用可选的交换延迟(请参阅 hx-swap 属性)
      • 完成实际的内容交换
        • 从目标中删除 htmx-swapping
        • htmx-added 类添加到每个新内容
        • htmx-settling 类应用到目标
        • 完成结算延迟(默认:20 毫秒)
        • 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”来启用验证。

#验证示例

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

<form id="example-form" hx-post="/test">
    <input name="example"
           onkeyup="this.setCustomValidity('') // reset the validation on keyup"
           hx-on:htmx:validation:validate="if(this.value != 'foo') {
                    this.setCustomValidity('Please enter the value foo') // set the validation error
                    htmx.find('#example-form').reportValidity()          // report the issue
                }">
</form>

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

#动画

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

请参阅 动画指南,以详细了解可用的选项。

#扩展

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

以下是如何安装 idiomorph 扩展,它允许您为 htmx 交换使用 Idiomorph DOM 形态转换算法

<head>
  <script src="https://unpkg.com/[email protected]/dist/idiomorph-ext.min.js"></script>
</head>
<body hx-ext="morph">
  ...
  <button hx-post="/example" hx-swap="morph" hx-target="#content">
    Update Content
  </button>
  ...
</body>

首先包含扩展(应在包含 htmx.js 之后包含),然后通过 hx-ext 属性按名称引用扩展。这使您可以使用 morph 交换。

#核心扩展

htmx 支持一些“核心”扩展,这些扩展由 htmx 开发团队提供支持

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

#创建扩展

如果您有兴趣为 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(); // add a new parameter into the request
    evt.detail.headers['Authentication-Token'] = getAuthToken(); // add a new header into the request
});

在这里,我们在发送请求之前向请求添加了一个参数和标头。

#使用事件修改交换行为

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

document.body.addEventListener('htmx:beforeSwap', function(evt) {
    if(evt.detail.xhr.status === 404){
        // alert the user when a 404 occurs (maybe use a nicer mechanism than alert())
        alert("Error: Could Not Find Resource");
    } else if(evt.detail.xhr.status === 422){
        // allow 422 responses to swap as we are using this as a signal that
        // a form was submitted with bad data and want to rerender with the
        // errors
        //
        // set isError to false to avoid error logging in console
        evt.detail.shouldSwap = true;
        evt.detail.isError = false;
    } else if(evt.detail.xhr.status === 418){
        // if the response code 418 (I'm a teapot) is returned, retarget the
        // content of the response to the element with the id `teapot`
        evt.detail.shouldSwap = true;
        evt.detail.target = htmx.find("#teapot");
    }
});

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

#事件命名

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

  • 驼峰式大小写
  • 连字符式大小写

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

#日志记录

如果您在 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"));

这将把 theElement 标识的元素上发生的所有事件输出到控制台,并允许您准确地看到该元素发生了什么。

请注意,这在控制台中有效,您不能将其嵌入到网页上的脚本标签中。

最后,如果实在没有办法,您可能只想通过加载未压缩版本来调试 htmx.js。它大约有 2500 行 JavaScript 代码,所以代码量并不算多。您很可能想在 issueAjaxRequest()handleAjaxResponse() 方法中设置断点,以查看发生了什么。

如果您需要帮助,随时加入 Discord

#创建演示

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

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

只需将以下脚本标签添加到您的演示/小提琴/任何内容中即可

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

这个助手允许您通过添加带有 url 属性的 template 标签来添加模拟响应,以指示哪个 URL。该 URL 的响应将是模板的 innerHTML,从而可以轻松构建模拟响应。您可以使用 delay 属性向响应添加延迟,该属性应为一个整数,表示要延迟的毫秒数。

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

请注意,这应该只用于演示,并且不保证在长时间内有效,因为它总是会获取最新版本的 htmx 和 hyperscript!

#演示示例

以下是一个代码在实际应用中的示例

<!-- load demo environment -->
<script src="https://demo.htmx.org"></script>

<!-- post to /foo -->
<button hx-post="/foo" hx-target="#result">
    Count Up
</button>
<output id="result"></output>

<!-- respond to /foo with some dynamic content in a template tag -->
<script>
    globalInt = 0;
</script>
<template url="/foo" delay="500"> <!-- note the url and delay attributes -->
    ${globalInt++}
</template>

#脚本编写

虽然 htmx 鼓励使用超媒体方法构建 Web 应用程序,但它提供了许多用于客户端脚本编写的选项。脚本编写包含在 Web 架构的 RESTful 描述中,请参阅:按需代码。在可行的情况下,我们建议在您的 Web 应用程序中使用超媒体友好的脚本编写方法。

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

与 htmx 配合良好的脚本编写解决方案包括

  • VanillaJS - 只需使用 JavaScript 的内置功能来挂钩事件处理程序以响应 htmx 发出的事件,就可以非常有效地进行脚本编写。这是一种非常轻量级且越来越流行的方法。
  • AlpineJS - Alpine.js 提供了一套丰富的工具来创建复杂的前端脚本,包括响应式编程支持,同时仍然保持极度轻量级。Alpine 鼓励“内联脚本编写”方法,我们认为它与 htmx 配合良好。
  • jQuery - 尽管它很老,并且在某些圈子里声名狼藉,但 jQuery 与 htmx 配合良好,尤其是在已经有很多 jQuery 的旧代码库中。
  • hyperscript - Hyperscript 是一种实验性的前端脚本语言,由创建 htmx 的同一个团队创建。它旨在很好地嵌入到 HTML 中,并且可以响应和创建事件,并且与 htmx 配合良好。

我们有一整章名为“客户端脚本编写”,位于我们的书中,它介绍了如何在基于 htmx 的应用程序中集成脚本编写。

#hx-on* 属性

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

<button onclick="alert('You clicked me!')">
    Click Me!
</button>

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

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

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

<button hx-on:click="alert('You clicked me!')">
    Click Me!
</button>

因此,字符串 hx-on 后面跟着一个冒号(或一个连字符),然后是事件的名称。

当然,对于 click 事件,我们建议坚持使用标准的 onclick 属性。但是,请考虑一个使用 htmx 的按钮,它希望使用 htmx:config-request 事件向请求添加一个参数。这使用标准的 on* 属性是不可能的,但可以使用 hx-on:htmx:config-request 属性来完成。

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

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

hx-on* 属性是一种非常简单的通用嵌入式脚本编写机制。它不是更成熟的前端脚本编写解决方案(如 AlpineJS 或 hyperscript)的替代品。但是,它可以增强您在基于 htmx 的应用程序中使用 VanillaJS 的脚本编写方法。

请注意,HTML 属性不区分大小写。这意味着,不幸的是,依赖于大小写/驼峰式大小写的事件无法响应。如果您需要支持驼峰式大小写事件,我们建议使用更成熟的脚本编写解决方案,如 AlpineJS 或 hyperscript。htmx 出于这个原因,以驼峰式大小写和连字符式大小写两种方式分发所有事件。

#第三方 JavaScript

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

这方面的一个很好的例子是 SortableJS 演示

<form class="sortable" hx-post="/items" hx-trigger="end">
    <div class="htmx-indicator">Updating...</div>
    <div><input type='hidden' name='item' value='1'/>Item 1</div>
    <div><input type='hidden' name='item' value='2'/>Item 2</div>
    <div><input type='hidden' name='item' value='2'/>Item 3</div>
</form>

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

在 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 将包含 htmx 属性的内容添加到 DOM 中,您需要确保使用 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">Toggle New Content</button>
    <template x-if="show_new">
        <div id="new_content">
            <a hx-get="/server/newstuff" href="#">New Clickable</a>
        </div>
    </template>
</div>

#Web Components

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

#缓存

htmx 开箱即用地与标准的 HTTP 缓存 机制一起使用。

如果您的服务器将 Last-Modified HTTP 响应头添加到给定 URL 的响应中,浏览器将自动添加 If-Modified-Since 请求 HTTP 头到对同一 URL 的后续请求。请注意,如果您的服务器可以根据其他一些头为同一个 URL 呈现不同的内容,您需要使用 Vary 响应 HTTP 头。例如,如果您的服务器在 HX-Request 头缺失或为 false 时呈现完整的 HTML,并在 HX-Request: true 时呈现该 HTML 的片段,则需要添加 Vary: HX-Request。这会导致缓存基于响应 URL 和 HX-Request 请求头组合进行键值对,而不是仅基于响应 URL。

如果您无法(或不愿意)使用 Vary 头,您可以选择将配置参数 getCacheBusterParam 设置为 true。如果设置了此配置变量,htmx 将在它发出的 GET 请求中包含一个缓存清除参数,这将阻止浏览器将 htmx 基于和非 htmx 基于的响应缓存到同一个缓存槽中。

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

#安全

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

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

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

基于 HTML 的 Web 开发的第一个规则始终是:不要信任来自用户的输入。您应该转义所有注入到您网站的第三方、不可信的内容。这是为了防止,除其他问题外,跨站脚本攻击

在优秀的 OWASP 网站 上有关于 XSS 以及如何防止它的大量文档,包括 跨站脚本预防速查表

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

话虽如此,但人们有时会选择以更危险的方式注入 HTML,通常通过他们模板语言中的某种 raw() 机制。这可以出于正当理由完成,但如果被注入的内容来自第三方,那么它必须被清理,包括删除以 hx-data-hx 开头的属性,以及内联 <script> 标签等。

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

#htmx 安全工具

当然,错误会发生,开发人员也不完美,因此最好对您的 Web 应用程序采取分层安全方法,而 htmx 也提供了工具来帮助您保护应用程序。

让我们来看看它们。

#hx-disable

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

<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: 属性
    • hx-valsjs: 前缀
    • hx-headersjs: 前缀

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

#事件

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

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

document.body.addEventListener('htmx:validateUrl', function (evt) {
  // only allow requests to the current server as well as myserver.com
  if (!evt.detail.sameHost && evt.detail.url.hostname !== "myserver.com") {
    evt.preventDefault();
  }
});

#CSP 选项

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

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

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

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

对 CSP 的全面讨论超出了本文档的范围,但 MDN 文章 为探索这个主题提供了一个很好的起点。

#配置 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 交换

您可以直接在 javascript 中设置它们,也可以使用meta标签。

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

#结论

就是这样!

享受使用 htmx 的乐趣!您可以在无需编写太多代码的情况下实现相当多的事情