发布于 2025-12-08 0 阅读
0

🌷 使用 JavaScript 创建 SSR 图库应用 HMPL 上的示例应用程序列表

🌷 使用 JavaScript 创建 SSR 画廊应用

HMPL 上的示例应用程序列表

大家好!本文将介绍如何创建一个 Gallery 应用。您可以放心地使用此应用并根据需要进行编辑(由于需要许可证,因此只能更改图片)。虽然它功能不多,但我认为它非常适合用作工作示例。

💻 该应用程序是什么样的?它的功能是什么?

该应用程序是一个小型图像列表,可以导航到其中的页面。界面如下所示:

桌面

桌面

移动的

移动的

在功能方面,您可以单击“下一步”按钮进入下一页,单击“上一页”按钮可以返回第一页。

第二页

此外,如果您单击任意图像,您都可以看到其完整格式:

完整格式

这是该应用程序的主要功能。它将使用开源模板语言 HMPL 编写。您可以点个星标支持一下,谢谢 ❤️!

💎 明星 HMPL ★

👀 应用程序的细微之处

此应用程序的主要特性之一是,所有图片(例如标题文本)均来自服务器。也就是说,我们不会在客户端的站点存储库中存储 10 张图片。它们全部来自服务器。我们可以通过以下方法实现这一点:将主要内容的 HTML 存储在服务器上,然后在客户端将其输出到一些单元格中,这样实际上占用的磁盘空间很小。

请记住,当你从远程存储库克隆文件时,如果代码很少,克隆视频或图像可能会花费大量时间。这里也是如此。这是此类应用程序的主要优势之一。

另外,在客户端浏览器上,如果我们考虑应用程序的加载,当用户首次进入网站时,它可能会加载几秒钟。他可以关闭此资源并转到另一个资源,因此从成本角度来看,这在某些情况下可以节省预算。

嗯

这种方法是面向服务器的,但不是服务器端渲染,因为组件是在客户端渲染的,机器人不会看到结果。

无论如何,如今确实存在这样一种创建网站的方法,它非常便捷,并且有其优势。如今有很多库实现了类似的功能。HMPL 就是其中之一。

🛠 开发过程和代码本身

首先,您需要选择编写应用程序的平台。我们所说的平台指的是后端的Express.js和通常的Node.js,而在客户端上,我们将使用一个简单的Webpack程序包。

客户端

关于从哪里开始,有很多不同的方法。从服务器开始,或者从客户端开始。在我们的例子中,最好从客户端开始,因为我们知道在服务器上已经生成了图片列表和标题,但如何最好地将它们集成到 DOM 中——这正是我们首先需要解决的问题。

让我们继续查看原始 HTML 文件:

索引.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Gallery App</title>
  </head>
  <body></body>
</html>
Enter fullscreen mode Exit fullscreen mode

看起来这里什么都没有,是的,你说得对。作为内容的组件将被加载到这个文件中。我们将在.hmpl扩展文件中创建它们,这会稍微扩展 HTML 的功能。

为此,我们将创建一个components文件夹来存储这些文件。我们将通过 JavaScript 将每个文件连接到页面。它们的标记如下:

画廊.hmpl

<div>
  <div class="gallery-initial" id="gallery-initial">
    { 
      { 
        src: "http://localhost:8000/api/images", 
        method: "POST" 
      } 
    }
  </div>
  <div class="gallery" id="gallery">
    { 
      { 
        src: "http://localhost:8000/api/images", 
        after:
        "click:.navigation-button", 
        method: "POST" 
      } 
    }
  </div>

  <div class="pagination">
    <button class="navigation-button" data-page="1" id="previous" disabled>
      Previous
    </button>
    <button class="navigation-button" data-page="2" id="next">Next</button>
  </div>

  <div class="modal" id="modal">
    <img
      src="https://raw.githubusercontent.com/hmpl-language/media/refs/heads/main/logo.png"
      alt=""
    />
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

值得注意的是,这里标记了两个对象,第一个对象在页面加载时触发,第二个对象在点击导航按钮后触发。

标题.hmpl

<h1 id="title">{{ src: "http://localhost:8000/api/title" }}</h1>
Enter fullscreen mode Exit fullscreen mode

在这里,对象将从服务器转换为 HTML。现在,它们应该连接起来。为此,将它们导入到 main.js 中:

import "./index.scss";
import GalleryTemplate from "./components/Gallery/Gallery.hmpl";
import TitleTemplate from "./components/Title/Title.hmpl";

const { response: Title } = TitleTemplate();

const { response: Gallery } = GalleryTemplate(({ request: { event } }) => {
  return {
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      page: event ? Number(event.target.getAttribute("data-page")) : 1,
    }),
  };
});

document.body.append(Title);
document.body.append(Gallery);

const gallery = document.querySelector("#gallery");
const galleryInitial = document.querySelector("#gallery-initial");
const modal = document.querySelector("#modal");
const modalImg = modal.querySelector("img");
const navigationButtons = document.querySelectorAll(".navigation-button");

const setActive = (e) => {
  if (e.target.tagName === "IMG") {
    modalImg.src = e.target.src;
    modal.classList.add("active");
  }
};

modal.addEventListener("click", () => {
  modal.classList.remove("active");
});

galleryInitial.addEventListener("click", (e) => {
  setActive(e);
});

gallery.addEventListener("click", (e) => {
  setActive(e);
});

for (let i = 0; i < navigationButtons.length; i++) {
  const btn = navigationButtons[i];
  btn.addEventListener("click", () => {
    if (!galleryInitial.classList.contains("hidden"))
      galleryInitial.classList.add("hidden");
    btn.setAttribute("disabled", "");
    navigationButtons[i === 0 ? 1 : 0].removeAttribute("disabled");
  });
}
Enter fullscreen mode Exit fullscreen mode

另外,main.js我们将描述应用程序的逻辑。在这里,我们向服务器发送请求并接收HTML。我们尚未准备好HTML,但会在开发过程中进行准备。由于服务器HTML位于div块中,因此我们可以轻松地将组件添加到DOM中,而无需等待响应。

这里需要在disabled按钮的属性之间添加另一个开关。理想情况下,应该从服务器获取页面数量并专注于此,但由于应用程序本身很小,并且所有常量都已预先确定,因此最好不要添加额外的代码来使其过载。顺便说一下,图像更改本身会在向 API 发出请求后自动进行。

并且还需要在点击时显示图像 - 这是通过在包装标签上挂一个事件并确定如果点击图像,则必须相应地激活该块来完成的。

我们包含的样式如下:

索引.scss

body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: #f4f4f4;
}

h1 {
  margin: 20px 0;
  color: #333;
}

.gallery-initial.active {
  display: flex;
}

.gallery,
.gallery-initial {
  display: flex;
  gap: 20px;
  width: 90%;
  max-width: 1000px;

  @media (max-width:1023px) {
   display: grid;
   grid-template-columns: repeat(2, 1fr);
   max-width: unset;
   justify-content: center;
   align-items: center;
   width: 100%;
  }
}

.hidden {
  display: none;
}

.gallery img,
.gallery-initial img {
  width: 150px;
  height: 100px;
  border-radius: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  cursor: pointer;
  transition: transform 0.2s;
}

.gallery img:hover,
.gallery-initial img:hover {
  transform: scale(1.05);
}

.modal {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.8);
  justify-content: center;
  align-items: center;
}

.modal img {
  max-width: 90%;
  max-height: 90%;
  border-radius: 10px;
}

.modal.active {
  display: flex;
}

.pagination {
  margin: 20px 0;
  display: flex;
  gap: 10px;
  align-items: center;
  justify-content: center;
}

.pagination button {
  padding: 10px 20px;
  border: none;
  background-color: #333;
  color: #fff;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.pagination button:hover {
  background-color: #555;
}

.pagination button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}
Enter fullscreen mode Exit fullscreen mode

风格简约,只是为了让画廊看起来或多或少比较美观。

另外,我通常使用很久以前制作的现成的 webpack 程序集(我必须为该框架制作一个网站),但现在没有必要纠结于每个点上哪个负责哪个webpack.config.js。该文件可以在这里查看。

现在,是时候转到后端了。

后端

创建后端后,我们现在可以冷静地查看客户端,并在此基础上创建所需的路由。假设我创建了一个图库——太棒了,那么我需要下载图片并设置图库中描述的路由。

我们看到需要创建的路线是/api/images

src: "http://localhost:8000/api/images", 
Enter fullscreen mode Exit fullscreen mode

现在,您只需准备响应中发出的 HTML 标记即可。此外,路由的方法将是POST,因为您需要在 中body传递RequestInit所需page的值。让我们设置一个类似的路由:

路线/post.js

const express = require("express");
const expressRouter = express.Router();

const imagePaths = [
  "http://localhost:8000/images/img1.jpg",
  "http://localhost:8000/images/img2.jpg",
  "http://localhost:8000/images/img3.jpg",
  "http://localhost:8000/images/img4.jpg",
  "http://localhost:8000/images/img5.jpg",
  "http://localhost:8000/images/img6.jpg",
  "http://localhost:8000/images/img7.jpg",
  "http://localhost:8000/images/img8.jpg",
  "http://localhost:8000/images/img9.jpg",
  "http://localhost:8000/images/img10.jpg",
];

const imagesController = (req, res) => {
  const { page } = req.body;

  if (!page || isNaN(page)) {
    return res.status(400).send("Page number error");
  }

  const pageNumber = parseInt(page);
  const itemsPerPage = 5;
  const startIndex = (pageNumber - 1) * itemsPerPage;
  const endIndex = startIndex + itemsPerPage;

  if (startIndex >= imagePaths.length || pageNumber < 1) {
    return res.status(404).send("Page not found");
  }

  const imagesForPage = imagePaths.slice(startIndex, endIndex);

  const htmlResponse = `
      ${imagesForPage
        .map((img, index) => `<img src="${img}" alt="Image${index}"/>`)
        .join("\n")}
  `;

  res.send(htmlResponse);
};

expressRouter.post("/images", imagesController);

module.exports = expressRouter;
Enter fullscreen mode Exit fullscreen mode

重要的是,我们要根据页面动态生成图像。另外,值得注意的是,图像的路径不会指向文件夹,而是指向地址本身。在app.js文件中,我们将这样做,以便从文件夹加载图像。

现在,它非常简单。当我们发出GET获取标题的请求时,我们会发送一个简单的 HTML 文件。代码如下:

路线/get.js

const express = require("express");
const expressRouter = express.Router();
const path = require("path");

const titleController = (req, res) => {
  res.sendFile(path.join(__dirname, "../components/GET/title.html"));
};

expressRouter.use("/title", titleController);

module.exports = expressRouter;
Enter fullscreen mode Exit fullscreen mode

在 html 文件中,我们只需要一个span包含“Gallery App”文本的 HTML 文件即可。理论上,可以发起POST请求并添加多语言功能,但这又会给应用程序带来负担。我希望它简洁明了,同时兼具美观和实用性。

现在,剩下的就是将所有这些连接到一个文件中并启动我们的服务器。为此,让我们导入文件并创建一个 express 应用程序:

应用程序.js

const express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const cors = require("cors");

const PORT = 8000;
const app = express();

const getRoutes = require("./routes/get");
const postRoutes = require("./routes/post");

app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors({ origin: true, credentials: true }));

const imagesFolder = path.join(__dirname, "./images");
app.use("/images", express.static(imagesFolder));

app.use(express.static(path.join(__dirname, "src")));

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "src/index.html"));
});

app.use("/api", getRoutes);
app.use("/api", postRoutes);

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

我们在这里进行设置,CORS以便可以从另一个端口发送请求localhost,并从文件夹加载图像。具体来说,我们在这里执行此操作:

const imagesFolder = path.join(__dirname, "./images");
app.use("/images", express.static(imagesFolder));
Enter fullscreen mode Exit fullscreen mode

另外,您可以指定PORT服务器,但我指定了默认的8000。您还需要进行配置bodyParser以便于使用 HTML,实际上,只需将路由连接到 API 即可。现在,我想您可以安全地使用该应用程序了!

📜结论

这个应用程序,即使看似很小,考虑到只实现了最低限度的功能,也显得相当复杂。但这也很酷,因为它有修改的空间、高质量的组件和简洁的现代模块。你可以用 PHP 或其他语言实现后端,对客户端来说,意义不会有太大变化,所以我认为这个应用程序甚至可以作为一个宠物项目。

非常感谢大家阅读这篇文章!虽然我尝试在某些地方缩短叙述,避免涉及细节,但文章篇幅确实很长,但我希望它能对你们有趣且有用!

ty

📂 项目仓库

该项目位于 GitHub 上。您可以在那里更详细地了解代码。未来可能会有其他类似甚至更酷的项目。

GitHub 徽标 hmpl 语言/示例

HMPL 上的示例应用程序列表

HMPL 上的示例应用程序列表

此存储库包含使用 HMPL 模板语言编写的测试应用程序列表。您可以放心地使用它们并进行修改(只需替换图片)。

图库应用程序

一个具有分页功能的花卉图库应用。图片以及应用本身的名称均从服务器上传。

照片 1 照片 2 照片 3

登陆页面

登陆页面的组件完全位于服务器上,因此原始应用程序文件只有几千字节。

照片 4 照片 5




鏂囩珷鏉ユ簮锛�https://dev.to/hmpljs/creating-an-ssr-gallery-app-in-javascript-16kp