Drupal 11: Making Interactive Elements With HTMX
15th March 2026 - 40 minutes read time
HTMX是一个JavaScript库,它允许你在不编写任何JavaScript代码的情况下进行ajax调用并创建CSS过渡效果。它通过向HTML元素添加属性来工作,然后利用这些属性设置并执行ajax请求、交换元素以及执行其他一些操作。
它在Drupal 11.3.0*版本中被添加到Drupal中,使开发人员能够使用渲染数组和HTML属性创建交互式元素。其目标是用一个围绕HTMX构建的系统取代整个ajax子系统,要完成这项任务还有很多工作要做。
* 从技术上讲,HTMX从Drupal 11.2.0版本就已经存在于Drupal中,但只是作为一个实验性库。Drupal 11.3.0版本包含了完整的HTMX库以及许多辅助类,使开发变得更加容易。
在本文中,我们将探讨HTMX如何集成到Drupal中,以及有哪些服务可以帮助你在Drupal系统中使用它。
由于本文篇幅较长,我创建了一个目录,以帮助你滚动到相关部分。
首先,让我们简要介绍一下HTMX。
An Introduction To HTMX
HTMX是一个与平台无关的框架,它允许在不编写JavaScript代码的情况下进行ajax调用和CSS过渡。
HTMX库仅16K,相比Drupal多年来一直试图移除的30K的jQuery库来说,它非常小。HTMX没有其他依赖项,所以这16K实际上就是整个库。
所谓与平台无关,是指它不关心背后是什么技术,你可以有一个由HTMX驱动的前端,而后台可以使用几乎任何技术来响应HTTP请求。HTMX向后台发送普通的Web请求,并期望后台以纯HTML响应。Drupal非常擅长生成HTML,因此非常适合作为HTMX使用的后端系统。
HTMX核心有多种功能,同时它还有一组事件钩子,插件可以利用这些钩子添加功能和特性。这意味着如果核心HTMX库不能满足你的需求,你可以扩展它并添加一些代码来满足你的要求。
从根本上说,你可以这样理解HTMX:
- 使用属性为HTML添加行为。
- 任何元素都可以发出Web请求。
- 所有响应都应该是HTML格式。
HTMX属性以 hx-* 或 data-hx-* 为前缀,但使用哪一种都可以。有许多不同的属性可用,但常用的大约只有11个,另外还有20 - 30个属性可以添加更多功能。
例如,让我们看一个不属于Drupal的HTML示例,其中包含几个HTMX属性。
<button hx-get="index.php" hx-target="#div1">Submit</button>
<div id="div1"></div>
上述示例中的属性将执行以下操作。
hx-get= 向index.php路径发送一个GET请求。hx-target= 将响应写入ID为 "div1" 的元素中。
对这个请求的响应应该是纯HTML,这意味着我们可以用如下内容进行响应。
<p>Button clicked!</p>
这将被注入到我们的div元素中,如下所示。
<div id="div1"><p>Button clicked!</p></div>
这基本上就是HTMX的核心功能,其他属性会改变这个请求和替换的核心过程的工作方式。
你可以在我创建的 HTMX with PHP示例仓库 中看到这个示例(以及除此之外的更多示例)。该仓库包含一个HTMX PHP类,它封装了HTMX请求的检测和响应,使示例更容易理解。
在Drupal内部,通过理解几个关键属性,你就可以在HTMX和Drupal方面取得很大进展。请注意,我在这里做了一些简化,但关键概念是存在的。HTMX.org参考页面 也有很好的文档,如需更多信息,请参考该页面。
HTTP Verbs
通过属性可以使用不同的HTTP动词,我们已经看到了 hx-get 属性,但还有其他一些。
hx-get执行 "get" 请求。hx-post执行 "post" 请求。hx-delete执行 "delete" 请求。hx-patch执行 "patch" 请求。hx-put执行 "put" 请求。
这允许你使用HTML通常不使用的HTTP动词(即不是GET或POST)向服务器发送请求。
hx-target
这指定了要交换的目标元素,通过CSS选择器来定义。
<button hx-get="index.php" hx-target="#div1">Submit</button>
<div id="div1"></div>
hx-trigger
定义将触发请求的操作。对于有默认触发器的元素,这个属性可以省略。链接和按钮已经有 "click" 事件,如果HTMX配置为在这些元素上执行操作,它们会将这些事件传递给HTMX。其他表单元素(如select和input元素)会生成 "change" 事件。
以下是触发器元素以几种不同方式使用的示例。
为div元素添加点击触发器。
<div hx-post="index.php" hx-trigger="click">Click</div>
请注意,这不是最佳实践,你可能还应该包含ARIA属性,以使点击操作尽可能具有可访问性。这里的示例侧重于HTMX,但HTML不应孤立存在。
触发点击事件,并添加 once 以仅允许该事件运行一次。这意味着当用户点击它时,按钮在这之后将不会再发出另一个ajax请求。
<button hx-get="index.php" hx-trigger="click once">Click</button>
为单个div标签添加加载和点击触发器。这意味着我们可以在元素加载到页面上时触发它加载内容,也可以在用户点击它时触发。
<div hx-post="index.php" hx-trigger="load, click">Load & Click</div>
revealed 触发器将在元素在浏览器窗口的视口中可见时才触发事件。
<div hx-post="index.php" hx-trigger="revealed"></div>
触发器可以添加延迟,以稍微偏移事件触发的请求。以下代码将在元素加载到页面上时触发对index.php的请求,但仅在500ms延迟之后。
<div hx-post="index.php" hx-trigger="load delay:500ms"></div>
HTMX也理解 "去抖动" 的概念。在以下示例中,我们将按键触发延迟1秒,如果用户在这段时间内按下另一个键,则之前的事件将被取消,并创建一个新的事件。这意味着我们不会触发大量事件,只触发序列中的最后一个事件。
<input hx-get="index.php" hx-trigger="keyup delay:1s" />
hx-select
此属性将使用CSS选择器从响应中选择要与目标元素交换的内容。
看下面的例子。这里我们有一个 hx-target 属性,它告诉我们结果将放置的位置,但我们还有一个 hx-select 属性。这将用于从响应中选择我们想要注入到目标元素中的元素。
<button hx-get="index.php" hx-select="#response" hx-target="#div1">Click</button>
<div id="div1"></div>
假设我们从服务器得到以下响应。
<p id="response">Button clicked!</p>
<p>Some extra content that won't get displayed.</p>
ID为 "response" 的元素将从响应中被挑选出来并用作内容,其他所有内容将被忽略。
这个属性在Drupal中尤为重要。本文后面会详细解释,当Drupal响应HTMX的请求时,它将返回一个完整的HTML页面,这意味着我们需要使用 hx-select 属性从响应中挑选出我们感兴趣的元素。
hx-select-oob
这里的 "oob" 指的是 "带外" 的概念,这意味着我们将执行与用户当时正在做的事情不一定相关的操作。这个属性允许我们对响应执行两个操作。
看下面的例子。
<button hx-get="index.php" hx-select-oob="#div1">Send Request</button>
<div id="div1"></div>
首先,我们将触发对index.php脚本的GET请求,它可能返回如下内容。
Request Sent
<div id="div1">Button clicked!</div>
按钮的文本将被替换为 "Request Sent",这是请求的第一部分。然后,ID为 "div1" 的元素将从请求中被挑选出来,并用于替换页面中相同的元素。默认情况下,HTMX将使用innerHTML策略 "交换" 元素。重要的是要记住,hx-select-oob 的默认行为是outerHTML,这意味着整个元素将被替换。
hx-select-oob 属性可以接受一个用逗号分隔的元素列表,因此以下示例将更改响应中的两个元素,只要它们在两个地方都存在。
<button hx-get="index.php" hx-select-oob="#div1,#div2">Send Request</button>
<div id="div1"></div> <div id="div2"></div>
hx-swap
这控制元素如何交换到页面中。默认情况下,这是 innerHTML,它将用响应目标替换元素的内容。将其更改为 outerHTML 将替换整个元素。你还可以将其设置为 textContent,这将替换内容而不将其解析为HTML。
其他选项包括:
beforebegin、afterbegin、beforeend、afterend- 将响应放置在元素及其子元素之前或之后。delete- 从页面中删除目标元素。none- 不处理响应(但仍处理带外项)。
hx-swap-oob
交换一个 "带外" 元素。类似于 hx-select-oob,但响应告诉HTMX要将什么交换到页面内容中。
看下面的例子。请注意,我们将 hx-swap 属性设置为 "none",这意味着我们不会对这个元素进行任何交换。
<button hx-get="index.php" hx-swap="none">Send Request</button>
<div id="div1"></div>
服务器的响应包含一个ID为 "div1" 的div,并且 hx-swap-oob 属性设置为true。
<div id="div1" hx-swap-oob="true">Button clicked!</div>
这将在页面上找到具有相同ID的元素,并将其替换为这个元素。这实际上允许我们从服务器控制页面上的元素。这非常强大。
HTMX In Drupal
HTMX支持使用 hx-* 和 data-hx-* 属性。Drupal选择使用 data-hx-* 属性作为默认值。如果你愿意,仍然可以使用 hx-* 属性,但Drupal在渲染HTMX时默认将输出 data-hx-* 样式。
Drupal Libraries
HTMX通过一个或两个Drupal库包含到页面中。这些库如下:
core/htmx- 核心HTMX库。core/drupal.htmx- 核心HTMX库以及一些用于与Drupal集成的额外脚本。
需要注意的是,你很少需要直接注入这些库。当你在Drupal中与HTMX交互时,它会自动将这些库注入到页面请求中。
core/drupal.htmx 库包含三个文件,在使用HTMX时协助Drupal。
htmx-assets.js- 它会查看Drupal的响应,并从接收到的标记中提取任何CSS和JavaScript引用。然后将这些资源注入到当前页面的头部,这意味着任何CSS或JavaScript都将成为当前页面的一部分,并用于渲染响应。这非常强大,因为这意味着我们可以在后端渲染任何我们需要的内容,然后确保它们在前端正确显示。htmx-behaviors.js- 它连接到Drupal.behaviors系统,以便HTMX所做的任何DOM更改都可以在Drupal行为中注册并用于运行事件。通过添加这个文件,我们可以确保HTMX元素与Drupal页面上现有的组件一起工作。htmx-utils.js- 这个文件包含一些供其他两个文件使用的辅助函数。
为了配合这些库,我们在后端还有一些PHP类。
Drupal Classes
\Drupal\Core\Htmx 类用作HTMX库和渲染数组属性注入的包装器。它还可以注入用于响应HTMX的头部信息。要使用它,你只需要创建一个新的 Htmx 对象,告诉它你希望元素具有哪些HTMX属性,然后将其应用到渲染元素上。
例如,这里有一个简单的例子,我们获取一个渲染数组元素,并将 data-hx-swap 和 data-hx-trigger 属性应用到该元素上。
$output['element'] = [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => 'Content'
];
$htmx = new Htmx();
$htmx->get()
->swap('afterend')
->trigger('revealed')
->applyTo($output['element']);
渲染时,这将生成以下HTML。
<p data-hx-get="/route" data-hx-swap="afterend" data-hx-trigger="revealed">Content</p>
你可以看到,这些方法映射到HTMX库中对应的属性,因此 trigger() 方法将创建一个 data-hx-trigger 属性,依此类推。
我们调用 get() 方法来设置一个GET请求。这个方法可以接受一个参数,即它应该用于执行GET请求的路由。如果我们省略这个参数(就像这里一样),则假设使用当前路由。
通过使用这种机制,我们会自动在页面附件中包含 core/drupal.htmx 库。这意味着如果你在页面中添加一个HTMX元素,那么你将自动获得在前端正确渲染和使用该元素所需的一切。
你可能还会看到如下语法,这本质上是一样的。这使用了一种称为 "实例化时的类成员访问" 的语法,自PHP 5.4版本以来就已经存在,但并不常见。
(new Htmx())
->get()
->swap('afterend')
->trigger('revealed')
->applyTo($output['element']);
除了Htmx类,我们还有一个 \Drupal\Core\Htmx\HtmxRequestInfoTrait 特性,用于检测HTMX请求和相关头部信息。这个特性需要访问 request_stack 服务和一个名为 getRequest() 的方法,以便从当前请求中提取信息。
我在这里没有详细介绍,但HTMX会发出许多不同的头部信息,你可以在代码中监听这些信息,以获取有关请求的更多信息。这可以告诉你诸如哪个元素触发了请求之类的事情。
HTMX Drupal Response
Drupal能够使用包含你创建的标记的小HTML文档来响应HTMX响应,这使用了 \Drupal\Core\Render\MainContent\HtmxRenderer 类。
你 可以 直接使用 HtmxRenderer 服务进行响应,但通常通过几种不同的机制调用它会更容易。这可以是将查询参数 _wrapper_format=drupal_htmx 添加到被调用的URL中,或者使用一个在路由参数中添加了 _htmx_route=true 选项的路由。
要将 _wrapper_format 添加到路由中,你可以在设置请求时这样做。
$htmx->get(Url::fromRoute(route_name: 'my_htmx_route', options: [
'query' => [
'_wrapper_format' => 'drupal_htmx',
],
]));
在响应请求的路由上设置 _htmx_route=true。
mymodule_controller_action_htmx:

