阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!

大家好!在本文中我將描述建立 Gallery 應用程式的過程。您可以放心地使用此應用程式並按您的意願進行編輯(您只能在那裡更改圖片,因為有許可證)。雖然功能不多,但是我認為,它非常適合用作工作範例。

💻 該應用程式是什麼樣的,它的功能是什麼?

該應用程式是一個小型圖像列表,可以瀏覽其頁面。介面如下:

桌面

桌面

移動的

移動的

在功能方面,您可以按一下「下一步」按鈕前往下一頁,按一下「上一頁」按鈕即可返回第一頁。

第二頁

另外,如果你點擊任何一個圖像,你就可以看到它的完整格式:

完整格式

這是該應用程式中提供的主要功能。它將使用開源模板語言HMPL來編寫。可以用星星支持一下,謝謝❤️!

https://github.com/hmpl-language/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>

看起來這裡什麼都沒有,是的,你是對的。作為內容的元件將被載入到此文件中。我們將在.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>

值得注意的是,這裡標記了兩個物體。第一個是在頁面載入時觸發,而第二個是在點擊導航按鈕後觸發。

標題.hmpl

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

在這裡,物件將從伺服器更改為 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");
  });
}

另外,在main.js中我們將描述應用程式的邏輯。這裡我們向伺服器發送請求並接收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;
}

風格簡約,只是為了讓畫廊看起來或多或少有點美觀。

另外,我通常使用很久以前製作的現成的 webpack 元件(我必須為框架製作網站),但現在沒有必要在每個點上糾結於webpack.config.js中的什麼負責什麼。該文件可在此處查看。

現在,是時候轉到後端了。

後端

建立後端時,我們現在可以冷靜地查看客戶端,並在此基礎上建立所需的路由。假設我建立了一個畫廊 - 太好了,然後我需要下載圖片並設定那裡描述的路線。

我們看到需要建立的路由是/api/images

src: "http://localhost:8000/api/images", 

現在,您只需要準備回應中發出的 HTML 標記。此外,路由的方法將是POST ,因為在RequestInitbody中,您需要傳遞具有所需值的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;

重要的是我們要根據頁面動態地產生圖像。另外,值得注意的是,影像的路徑不會指向資料夾,而是指向地址本身。在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;

在 html 檔案中,我們將只有一個包含文字Gallery App 的span ,僅此而已。原則上,可以發出POST請求並為應用程式加入多語言功能,但這又會給應用程式帶來負擔。我希望它或多或少變得簡單,但同時又美觀又實用。

現在,剩下的就是將所有這些連接到一個文件中並啟動我們的伺服器。為此,讓我們導入文件並建立一個快速應用程式:

應用程式.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}`);
});

在這裡我們設定CORS ,以便我們可以從另一個localhost連接埠發送請求,並從資料夾載入映像。具體來說,我們在這裡這樣做:

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

另外,您可以為伺服器指定PORT ,但我指定了預設值8000 。您還需要設定bodyParser以方便處理 HTML,實際上只需將路由連接到 api 即可。現在,我想您可以安全地使用該應用程式了!

結論

該應用程式即使看起來很小,但由於實現了最低限度的功能,因此顯得相當複雜。但是,這也很酷,因為有進行修改、高品質組裝和簡單的現代模組的基礎。你可以用 PHP 或其他語言來實現後端,對於客戶端來說意義不會有太大的變化,所以我認為這個應用程式甚至可以作為寵物專案。

非常感謝大家閱讀這篇文章!結果它變得相當大,即使我試圖在某些地方縮短敘述,在某些地方不涵蓋細微差別,但即使如此它仍然很多,但我希望,對你來說有趣和有用!

泰

📂 專案儲存庫

該專案位於 GitHub。您可以在那裡更詳細地了解程式碼。未來可能會有其他相同甚至更酷的專案。

https://github.com/hmpl-language/examples


原文出處:https://dev.to/hmpljs/creating-an-ssr-gallery-app-in-javascript-16kp


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:
學 JavaScript 前端,帶作品集去面試!

63 個專案實戰,寫出作品集,讓面試官眼前一亮!

立即開始免費試讀!