文件

#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。這使您牢牢地保持在 原始的網頁程式設計模型中,使用 超文字作為應用程式狀態引擎,甚至不需要真正理解這個概念。

值得一提的是,如果您願意,您可以在使用 htmx 時使用 data- 字首

<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/htmx.org@2.0.3" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>

也有提供未縮小的版本

<script src="https://unpkg.com/htmx.org@2.0.3/dist/htmx.js" integrity="sha384-BBDmZzVt6vjz5YbQqZPtFZW82o8QotoM7RUp5xOxV3nSJ8u2pSdtzFAbGKzTlKtg" crossorigin="anonymous"></script>

雖然 CDN 方法非常簡單,但您可能需要考慮 不要在生產環境中使用 CDN

#下載副本

安裝 htmx 的第二種最簡單方法是將其複製到您的專案中。

unpkg.com 下載 htmx.min.js,並將其添加到您專案中適當的目錄中,並在需要的地方使用 <script> 標籤包含它

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

#npm

對於 npm 樣式的建置系統,您可以透過 npm 安裝 htmx

npm install htmx.org@2.0.3

安裝後,您需要使用適當的工具來使用 node_modules/htmx.org/dist/htmx.js (或 .min.js)。例如,您可能會將 htmx 與一些擴充功能和專案特定的程式碼一起打包。

#Webpack

如果您使用 webpack 來管理您的 javascript

  • 透過您最喜歡的套件管理器 (例如 npm 或 yarn) 安裝 htmx
  • 將 import 新增到您的 index.js
import 'htmx.org';

如果您想要使用全域 htmx 變數 (建議使用),您需要將其注入到視窗範圍中

  • 建立一個自訂 JS 檔案
  • 將此檔案 import 到您的 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">
    Put To Messages
</button>

這告訴瀏覽器

當使用者點擊這個按鈕時,向 URL /messages 發出 PUT 請求,並將回應載入到按鈕中

#觸發請求

預設情況下,AJAX 請求是由元素的「自然」事件觸發的

  • inputtextareaselectchange 事件上觸發
  • 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:<選擇器> - 交叉點的根元素的 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">
    Click Me!
    <img class="htmx-indicator" src="/spinner.gif">
</button>

這裡我們有一個按鈕。當點擊它時,htmx-request 類別將被新增到它,這將顯示旋轉圖示元素。(我現在喜歡SVG 旋轉圖示。)

雖然 htmx-indicator 類別使用透明度來隱藏和顯示進度指示器,但如果您更喜歡其他機制,您可以建立自己的 CSS 轉換,如下所示

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

如果您想將 htmx-request 類別新增到不同的元素,您可以使用具有 CSS 選擇器的 hx-indicator 屬性來實現

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

在這裡,我們通過 id 明確地呼叫指示器。請注意,我們也可以將類別放在父 div 上,並具有相同的效果。

您也可以使用 hx-disabled-elt 屬性,在請求期間將 disabled 屬性新增到元素。

#目標

如果您希望將回應載入到發出請求的元素之外的其他元素中,您可以使用採用 CSS 選擇器的 hx-target 屬性。回顧我們的「即時搜尋」範例

<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 選擇器> 語法將找到符合給定 CSS 選擇器的最近的祖先元素或自身。(例如,closest tr 將針對最接近元素的表格列)
  • next <CSS 選擇器> 語法將在 DOM 中找到符合給定 CSS 選擇器的下一個元素。
  • previous <CSS 選擇器> 語法將在 DOM 中找到符合給定 CSS 選擇器的上一個元素。
  • find <CSS 選擇器> 將找到符合給定 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,則會回復到非轉換機制。

您可以使用以下方法來試用這個新 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>

假設此內容透過 ajax 請求由 htmx 取代為此新內容

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

請注意兩件事

  • div 在原始內容和新內容中具有相同的 id
  • red 類別已新增到新內容

在這種情況下,我們可以編寫一個從舊狀態到新狀態的 CSS 過渡效果

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

當 htmx 替換這個新內容時,它會以 CSS 過渡效果將應用於新內容的方式進行,讓您可以順暢地過渡到新狀態。

總而言之,要對元素使用 CSS 過渡效果,您只需讓其 id 在請求之間保持穩定即可!

您可以查看動畫範例以了解更多詳細資訊和實際演示。

#詳細資訊

要了解 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 會根據上傳期間的標準 progress 事件定期觸發 htmx:xhr: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 的用戶端將能夠按下 Enter 鍵並仍然進行搜尋。更好的是,您也可以新增一個「搜尋」按鈕。然後,您需要使用與 action 屬性對應的 hx-post 來更新表單,或者可能在其上使用 hx-boost

您需要在伺服器端檢查 HX-Request 標頭,以區分 htmx 驅動的請求和常規請求,以確定要向用戶端呈現的確切內容。

其他模式可以類似地調整,以實現您應用程式的漸進式增強需求。

如您所見,這需要更多思考和更多工作。它也完全排除了某些功能。這些權衡必須由您(開發人員)根據您專案的目標和受眾來進行。

協助工具是一個與漸進式增強密切相關的概念。使用諸如 hx-boost 之類的漸進式增強技術,將使您的 htmx 應用程式對廣泛的用戶更具協助工具。

基於 htmx 的應用程式與常規的非 AJAX 驅動的 Web 應用程式非常相似,因為 htmx 以 HTML 為導向。

因此,常規的 HTML 協助工具建議適用。例如

  • 盡可能使用語義 HTML (也就是,對的事物使用正確的標籤)
  • 確保焦點狀態清晰可見
  • 將文字標籤與所有表單欄位關聯
  • 使用適當的字體、對比度等來最大化應用程式的可讀性。

#Web Sockets & SSE

透過擴充功能支援 Web Sockets 和伺服器發送事件 (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 函式庫為例,它可以讓 select 元素擁有更豐富的使用者體驗。我們將設定 TomSelect 將任何具有 .tomselect 類別的 input 元素轉換為豐富的 select 元素。

首先,我們需要初始化新內容中具有該類別的元素

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 類別的 input 元素建立豐富的選取器。但是,它會改變 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 應忽略回應中的 title 標籤則為 true
  • select - 用於從回應中選取內容的 CSS 選取器
  • target - 指定回應替代目標的 CSS 選取器
  • swapOverride - 回應的替代交換機制

#配置回應處理範例

作為如何使用此配置的一個範例,請考慮伺服器端框架在發生驗證錯誤時,傳回 422 - Unprocessable Entity 回應的情況。預設情況下,htmx 將忽略該回應,因為它符合正規表示式 [45]..

使用 meta config 機制來配置 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 時,請記得配置您的網頁伺服器以設定 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 類別套用到目標
        • 完成 settle 延遲(預設值:20 毫秒)
        • DOM 已 settle
        • 從目標中移除 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 擴展,該擴展允許您使用 Idiomorph DOM 變形演算法進行 htmx 交換。

<head>
  <script src="https://unpkg.com/idiomorph@0.3.0/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 將元素載入 DOM 時,都會觸發 htmx:load 事件,這實際上等同於正常的 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"));

這將把 id 為 theElement 的元素上發生的所有事件輸出到控制台,並允許您確切了解它的狀況。

請注意,這適用於控制台,您無法將其嵌入到頁面上的腳本標籤中。

最後,如果逼不得已,您可能只想透過載入未縮小的版本來除錯 htmx.js。它大約有 2500 行 javascript,因此程式碼量並不大。您很可能想在 issueAjaxRequest()handleAjaxResponse() 方法中設定斷點,以了解發生了什麼事。

如果需要協助,也隨時可以加入 Discord

#建立演示

有時,為了演示錯誤或澄清用法,最好能夠使用像 jsfiddle 這樣的 javascript 片段網站。為了方便建立演示,htmx 會託管一個演示腳本網站,該網站將安裝

  • htmx
  • hyperscript
  • 一個請求模擬程式庫

只需將以下腳本標籤新增至您的演示/fiddle/任何內容

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

這個輔助程式允許您透過新增具有 url 屬性來指示哪個 URL 的 template 標籤來新增模擬回應。該 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 架構的 REST 式描述中,請參閱:Code-On-Demand。在可行的範圍內,我們建議在您的 Web 應用程式中使用 超媒體友善的腳本方法

htmx 和腳本解決方案之間的主要整合點是 htmx 發送和可以回應的 事件。請參閱 第三方 Javascript 區段中的 SortableJS 範例,以了解透過事件將 JavaScript 程式庫與 htmx 整合的良好範本。

與 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 元素放在一起,提供良好的行為局部性 (Locality of Behaviour, 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)。然而,它可以增強您基於 VanillaJS 的腳本方法,在您基於 htmx 的應用程式中。

請注意,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

請參閱 Web Components 範例頁面,以取得有關如何將 htmx 與 Web Component 整合的範例。

#快取

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 開發的第一條規則一直是:不要信任來自使用者的輸入。您應該逸出所有注入到您網站的第三方、不受信任的內容。這是為了防止(除其他問題外)XSS 攻擊

在出色的 OWASP 網站上有關於 XSS 以及如何防止它的廣泛文件,包括跨網站腳本防護速查表

好消息是,這是一個非常古老且廣為人知的主題,並且絕大多數伺服器端範本語言都支援內容的自動逸出,以防止出現此類問題。

話雖如此,有時人們會選擇以更危險的方式注入 HTML,通常是透過其範本語言中的某種 raw() 機制。這樣做可能有充分的理由,但是如果注入的內容來自第三方,則必須清除該內容,包括刪除以 hx-data-hx 開頭的屬性,以及內聯 <script> 標籤等。

如果您要注入原始 HTML 並自行逸出,最佳實務是白名單您允許的屬性和標籤,而不是黑名單您不允許的屬性和標籤。

#htmx 安全性工具

當然,錯誤會發生,開發人員也不是完美的,因此最好對您的 Web 應用程式採取分層的安全方法,並且 htmx 提供工具來協助保護您的應用程式。

讓我們來看一看它們。

#hx-disable

htmx 提供的第一個有助於進一步保護您的應用程式的工具是 hx-disable 屬性。此屬性將阻止處理給定元素以及其中所有元素上的所有 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: 屬性
    • 帶有 js: 前綴的 hx-vals
    • 帶有 js: 前綴的 hx-headers

請注意,透過停用 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 應用程式的工具。可用的最強大工具是內容安全性原則 (Content Security Policy, CSP)。使用 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 是否會處理在新內容中找到的 script 標籤
htmx.config.inlineScriptNonce預設為 '',表示不會將 nonce 新增至內嵌腳本
htmx.config.attributesToSettle預設為 ["class", "style", "width", "height"],在 settling 階段要處理的屬性
htmx.config.inlineStyleNonce預設為 '',表示不會將 nonce 新增至內嵌樣式
htmx.config.useTemplateFragments預設為 false,用於解析來自伺服器的內容的 HTML template 標籤(不與 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 用戶端憑證等憑證進行跨站存取控制請求
htmx.config.timeout預設為 0,請求在自動終止之前可以花費的毫秒數
htmx.config.scrollBehavior預設為 'instant',使用帶有 hx-swapshow 修飾符時的滾動行為。允許的值為 instant(滾動應立即發生,單次跳躍)、smooth(滾動應平滑地動畫化)和 auto(滾動行為由 scroll-behavior 的計算值決定)。
htmx.config.defaultFocusScroll是否應將焦點元素滾動到視圖中,預設為 false,可以使用 focus-scroll swap 修飾符覆寫。
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,是否將 boost 元素的目標滾動到視窗中。如果在 boost 元素上省略 hx-target,則目標預設為 body,導致頁面滾動到頂部。
htmx.config.triggerSpecsCache預設為 null,用於儲存已評估的觸發器規格的快取,以提高剖析效能,但會增加記憶體使用量。您可以定義一個簡單的物件來使用永不清除的快取,或使用 Proxy 物件來實作您自己的系統
htmx.config.responseHandling可以在此處設定回應狀態碼的預設 回應處理行為,以進行交換或錯誤
htmx.config.allowNestedOobSwaps預設為 true,是否處理主要回應元素內巢狀元素的 OOB 交換。請參閱巢狀 OOB 交換

您可以直接在 javascript 中設定它們,也可以使用 meta 標籤

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

#結論

就是這樣!

祝您使用 htmx 玩得開心!您可以不用寫太多程式碼就完成相當多的工作