HTMX构建无重载闪烁的交互式页面
推荐超级课程:
@TOC
对于您的Web应用程序来说,React通常可能过于庞大,有时仅使用Web服务器和HTMX就能创建出具有交互性的应用程序,效果不亚于React框架。
在这篇博客文章中,我们将展示如何使用HTMX 构建无重载闪烁的交互式页面导航:
服务器设置
mkdir no-react-app
cd no-react-app
npm init -y
npm install express nunjucks
然后我们创建一个服务器文件并运行
//文件:app.js
const express = require("express")
const app = express()
const nunjucks = require('nunjucks');
nunjucks.configure("views", {
autoescape: true,
express: app
});
app.get("/", (req, res) => {
res.render("pages/home.html")
})
app.get("/users", (req, res) => {
res.render("pages/users.html")
})
app.get("/posts", (req, res) => {
res.render("pages/posts.html")
})
app.listen(3000, () => {
console.info(`应用程序运行于 http://localhost:3000`)
})
我们使用nunjucks作为模板引擎。所有模板、布局和部分文件都将存储在“views”目录中。因此,我们的项目结构将如下所示:
应用结构
app.js
views
layouts
main.html
partials
sidenav.html
pages
user.html
home.html
posts.html
因为我们在使用模板引擎,所以让我们添加一个所有视图都将继承的主布局
主布局
<!--文件:views/layouts/main.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.2/tailwind.min.css" />
<title>HTMX应用</title>
</head>
<body class="bg-gray-200">
<div class="flex h-screen">
<!-- 侧边导航 -->
{%- include('partials/sidenav.html')%}
<!-- 主内容区域 -->
<div class="w-full bg-white p-4" id="main">
{% block content %}{% endblock %}
</div>
</body>
</html>
我们已经将一个侧边导航模板组件重构为部分页面,并将其包含在我们的布局中。
侧边导航组件
<!--文件:views/partials/sidenav.html-->
<div class="w-56 bg-gray-800 text-white p-4">
<a href="/" class="block py-2 px-4 text-white hover:bg-gray-600">首页</a>
<a href="/users" class="block py-2 px-4 text-white hover:bg-gray-600">用户</a>
<a href="/posts" class="block py-2 px-4 text-white hover:bg-gray-600">帖子</a>
</div>
并创建了我们的主要页面,home.html,users.html 和 posts.html
页面
<!--views/pages/home.html-->
{% extends 'layouts/main.html' %}
{% block content %}
<h1 class="text-2xl font-bold mb-4">HTMX 导航</h1>
{% endblock %}
<!--views/pages/users.html-->
{% extends 'layouts/main.html' %}
{% block content %}
<h1 class="text-2xl font-bold mb-4">用户</h1>
{% endblock %}
<!--views/pages/posts.html-->
{% extends 'layouts/main.html' %}
{% block content %}
<h1 class="text-2xl font-bold mb-4">帖子</h1>
{% endblock %}
当我们运行服务器时,我们有了导航,但是会有整页重新加载:
我们必须通过引入一个轻量级的JavaScript库HTMX 来解决这个问题。这个库可以通过使导航更加无缝和互动来大大提升用户体验。需要注意的是,HTMX有更广泛的应用范围,但出于我们当前的目的,我们将专注于利用其能力来实现更平滑的导航。
使用HTMX逐步增强
使用HTMX的最快方式是通过CDN加载它。你可以简单地将以下代码添加到你的head标签中并开始使用:
<!--文件:views/layouts/main.html-->
...
<script src="https://unpkg.com/htmx.org@latest"></script>
<title>HTMX 应用</title>
</head>
...
现在,我们可以对侧边导航进行一个小改动
删除href属性并替换为hx-get属性。当用户点击这个链接时,会发出一个HTTP GET请求。
将hx-target属性添加到每个锚标签或锚标签的父div上。hx-target属性允许你指定一个元素,用于替换hx-get的响应。
给每个锚标签添加hx-push-url=”true”。hx-push-url属性允许你将一个URL推入浏览器位置历史记录。这会创建一个新的历史记录条目,允许使用浏览器的后退和前进按钮进行导航。
这样做的作用是:我们声明性地指示HTMX库在点击锚标签时进行服务器调用,并将响应插入到id为‘main’的div中
<div class="w-56 bg-gray-800 text-white p-4" hx-target="#main" >
<a hx-get="/" hx-push-url="true" class="block py-2 px-4 text-white hover:bg-gray-600">首页</a>
<a hx-get="/users" hx-push-url="true" class="block py-2 px-4 text-white hover:bg-gray-600">用户</a>
<a hx-get="/posts" hx-push-url="true" class="block py-2 px-4 text-white hover:bg-gray-600">帖子</a>
</div>我们现在有以下内容。
我们解决了闪烁问题,并且如果我们要分享导航,URL会正确地推送至新URL
使应用支持HTMX
我们需要确定每个进入的服务器请求是否为HTMX调用。如果是,我们必须指示模板引擎跳过使用布局,并简单地返回该模板的HTML。为了实现这一点,我们需要融入特定的中间件!
//文件:app.js
...
app.use((req, res, next) => {
res.locals.useLayout = req.headers["hx-request"] !== "true";
next();
})
app.listen(3000, () => {
console.info(`应用运行于 http://localhost:3000`)
})
仅当未检测到HTMX请求时使用布局。
<!--文件:views/layouts/main.html-->
{% if useLayout %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.2/tailwind.min.css" />
<script src="https://unpkg.com/htmx.org@latest"></script>
<title>HTMX应用</title>
</head>
<body class="bg-gray-200">
<div class="flex h-screen">
<!-- 侧边导航 -->
{%- include('partials/sidenav.html')%}
<!-- 主内容区域 -->
<div class="w-full bg-white p-4" id="main">
{% endif %}
{% block content %}{% endblock %}
{% if useLayout %}
</div>
</body>
</html>
{% endif %}
我们成功实现了一个无缝且无重载闪烁的导航体验。