實現一個前端動態模塊組件(Vite+原生JS)

1. 引言

在前面的文章《使用Vite創建一個動態網頁的前端項目》中我們實現了一個動態網頁。不過這個動態網頁的實用價值并不高,在真正實際的項目中我們希望的是能實現一個動態的模塊組件。具體來說,就是有一個頁面控件同時在多個頁面中使用,那么我們肯定想將這個頁面控件封裝起來,以便每個頁面需要的時候調用一下就可以生成。注意,這個封裝起來模塊組件應該要包含完整的HTML+JavaScript+CSS,并且要根據從后端訪問的數據來動態填充頁面內容。其實像VUE這樣的前端框架就是這種設計思路,同時這也是GUI程序開發的常見思維模式。

2. 實現

2.1 項目組織

在這里筆者實現的例子是一個博客網站上的分類專欄控件。分類專欄是一般通過后端獲取的,但是這里筆者就將其模擬成直接域內獲取一個數據categories.json,里面的內容如下:

[{"firstCategory": {"articleCount": 4,"iconAddress": "三維渲染.svg","name": "計算機圖形學"},"secondCategories": [{"articleCount": 2,"iconAddress": "opengl.svg","name": "OpenGL/WebGL"},{"articleCount": 2,"iconAddress": "專欄分類.svg","name": "OpenSceneGraph"},{ "articleCount": 0, "iconAddress": "threejs.svg", "name": "three.js" },{ "articleCount": 0, "iconAddress": "cesium.svg", "name": "Cesium" },{ "articleCount": 0, "iconAddress": "unity.svg", "name": "Unity3D" },{"articleCount": 0,"iconAddress": "unrealengine.svg","name": "Unreal Engine"}]},{"firstCategory": {"articleCount": 4,"iconAddress": "計算機視覺.svg","name": "計算機視覺"},"secondCategories": [{"articleCount": 0,"iconAddress": "圖像處理.svg","name": "數字圖像處理"},{"articleCount": 0,"iconAddress": "特征提取.svg","name": "特征提取與匹配"},{"articleCount": 0,"iconAddress": "目標檢測.svg","name": "目標檢測與分割"},{ "articleCount": 4, "iconAddress": "SLAM.svg", "name": "三維重建與SLAM" }]},{"firstCategory": {"articleCount": 11,"iconAddress": "地理信息系統.svg","name": "地理信息科學"},"secondCategories": []},{"firstCategory": {"articleCount": 31,"iconAddress": "代碼.svg","name": "軟件開發技術與工具"},"secondCategories": [{ "articleCount": 2, "iconAddress": "cplusplus.svg", "name": "C/C++" },{ "articleCount": 19, "iconAddress": "cmake.svg", "name": "CMake構建" },{ "articleCount": 2, "iconAddress": "Web開發.svg", "name": "Web開發" },{ "articleCount": 7, "iconAddress": "git.svg", "name": "Git" },{ "articleCount": 1, "iconAddress": "linux.svg", "name": "Linux開發" }]}
]

這個數據的意思是將分類專類分成一級分類專欄和二級分類專欄,每個專欄都有名稱、文章數、圖標地址屬性,這樣便于我們填充到頁面中。

新建一個components目錄,在這個目錄中新建category.html、category.js、category.css這三個文件,正如前文所說的,我們希望這個模塊組件能同時具有結構、行為和樣式的能力。這樣,這個項目的文件組織結構如下所示:

my-native-js-app
├── public
│ └── categories.json
├── src
│ ├── components
│ │ ├── category.css
│ │ ├── category.html
│ │ └── category.js
│ └── main.js
├── index.html
└── package.json

2.2 具體解析

先看index.html頁面,代碼如下所示:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><link rel="icon" type="image/svg+xml" href="/vite.svg" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Vite App</title></head><body><div id="app"><div id="category-section-placeholder"></div></div><script type="module" src="/src/main.js"></script></body>
</html>

基本都沒有什么變化,只是增加了一個名為category-section-placeholder的元素,這個元素會用來掛接在js中動態創建的分類專欄目錄元素。

接下來看main.js文件:

import './components/category.js'

里面其實啥都沒干,只是引入了一個category模塊。那么就看一下這個category.js文件:

import "./category.css";// 定義一個變量來存儲獲取到的分類數據
let categoriesJson = null;// 使用MutationObserver監聽DOM變化
const observer = new MutationObserver((mutations) => {mutations.forEach((mutation) => {if (mutation.type === "childList" &&mutation.target.id === "category-section-placeholder") {// 在這里調用函數來填充數據populateCategories(categoriesJson);}});
});// 配置觀察選項
const config = { childList: true, subtree: true };// 開始觀察目標節點
const targetNode = document.getElementById("category-section-placeholder");
observer.observe(targetNode, config);// 獲取分類數據
async function fetchCategories() {try {const backendUrl = import.meta.env.VITE_BACKEND_URL;const response = await fetch("/categories.json");if (!response.ok) {throw new Error("網絡無響應");}categoriesJson = await response.json();// 加載Category.html內容fetch("/src/components/category.html").then((response) => response.text()).then((data) => {document.getElementById("category-section-placeholder").innerHTML =data;}).catch((error) => {console.error("Failed to load Category.html:", error);});} catch (error) {console.error("獲取分類專欄失敗:", error);}
}// 填充分類數據
function populateCategories(categories) {if (!categories || !Array.isArray(categories)) {console.error("Invalid categories data:", categories);return;}const categoryList = document.querySelector(".category-list");categories.forEach((category) => {const categoryItem = document.createElement("li");categoryItem.innerHTML = `<a href="#" class="category-item"><img src="category/${category.firstCategory.iconAddress}" alt="${category.firstCategory.name}" class="category-icon"><span class="category-name">${category.firstCategory.name} <span class="article-count">${category.firstCategory.articleCount}篇</span></span>`;if (category.secondCategories.length != 0) {categoryItem.innerHTML += `        <ul class="subcategory-list">${category.secondCategories.map((subcategory) => `<li><a href="#" class="subcategory-item"><img src="category/${subcategory.iconAddress}" alt="${subcategory.name}" class="subcategory-icon"><span class="subcategory-name">${subcategory.name} <span class="article-count">${subcategory.articleCount}篇</span></span></a></li>`).join("")}</ul></a>`;}categoryList.appendChild(categoryItem);});
}// 確保DOM完全加載后再執行
document.addEventListener("DOMContentLoaded", fetchCategories);

這個文件里面的內容比較多,那么我們就按照代碼的執行順序進行講解。

document.addEventListener("DOMContentLoaded", fetchCategories);表示當index.html這個頁面加載成功后,就執行fetchCategories這個函數。在這個函數通過fetch接口獲取目錄數據,通過也通過fetch接口獲取category.html。category.html中的內容很簡單:

<div class="category-section"><h3>分類專欄</h3><ul class="category-list"></ul>
</div>

fetch接口是按照文本的方式來獲取category.html的,在這里的document.getElementById("category-section-placeholder").innerHTML = data;表示將這段文本序列化到category-section-placeholder元素的子節點中。程序執行到這里并沒有結束,通過對DOM的變化監聽,繼續執行populateCategories函數,如下所示:

// 使用MutationObserver監聽DOM變化
const observer = new MutationObserver((mutations) => {mutations.forEach((mutation) => {if (mutation.type === "childList" &&mutation.target.id === "category-section-placeholder") {// 在這里調用函數來填充數據populateCategories(categoriesJson);}});
});// 配置觀察選項
const config = { childList: true, subtree: true };// 開始觀察目標節點
const targetNode = document.getElementById("category-section-placeholder");
observer.observe(targetNode, config);

populateCategories的具體實現思路是:現在分類專欄的數據已經有了,根節點元素category-list也已經知道,剩下的就是通過數據來拼接HTML字符串,然后序列化到category-list元素的子節點下。代碼如下所示:


const categoryList = document.querySelector(".category-list");categories.forEach((category) => {
const categoryItem = document.createElement("li");
categoryItem.innerHTML = `<a href="#" class="category-item"><img src="category/${category.firstCategory.iconAddress}" alt="${category.firstCategory.name}" class="category-icon"><span class="category-name">${category.firstCategory.name} <span class="article-count">${category.firstCategory.articleCount}篇</span></span>`;
if (category.secondCategories.length != 0) {categoryItem.innerHTML += `        <ul class="subcategory-list">${category.secondCategories.map((subcategory) => `<li><a href="#" class="subcategory-item"><img src="category/${subcategory.iconAddress}" alt="${subcategory.name}" class="subcategory-icon"><span class="subcategory-name">${subcategory.name} <span class="article-count">${subcategory.articleCount}篇</span></span></a></li>`).join("")}</ul></a>`;
}
categoryList.appendChild(categoryItem);

其實思路很簡單對吧?最后根據需要實現組件的樣式,category.css文件如下所示:

/* Category.css */
.category-section {background-color: #fff;border: 1px solid #e0e0e0;border-radius: 8px;padding: 1rem;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);font-family: Arial, sans-serif;max-width: 260px;/* 確保不會超出父容器 */overflow: hidden;/* 處理溢出內容 */
}.category-section h3 {font-size: 1.2rem;color: #333;border-bottom: 1px solid #e0e0e0;padding-bottom: 0.5rem;margin: 0 0 1rem;text-align: left;/* 向左對齊 */
}.category-list {list-style: none;padding: 0;margin: 0;
}.category-list li {margin: 0.5rem 0;
}.category-item,
.subcategory-item {display: flex;align-items: center;text-decoration: none;color: #333;transition: color 0.3s ease;
}.category-item:hover,
.subcategory-item:hover {color: #007BFF;
}.category-icon,
.subcategory-icon {width: 24px;height: 24px;margin-right: 0.5rem;
}.category-name,
.subcategory-name {/* font-weight: bold; */display: flex;justify-content: space-between;width: 100%;color:#000
}.article-count {color: #000;font-weight: normal;   
}.subcategory-list {list-style: none;padding: 0;margin: 0.5rem 0 0 1.5rem;
}.subcategory-list li {margin: 0.25rem 0;
}.subcategory-list a {text-decoration: none;color: #555;transition: color 0.3s ease;
}.subcategory-list a:hover {color: #007BFF;
}

最后顯示的結果如下圖所示:

圖1 分類專欄組件的顯示結果

3. 結語

總結一下前端動態模塊組件的實現思路:JavaScript代碼永遠是主要的,HTML頁面就好比是JavaScript的處理對象,過程就跟你用C++/Java/C#/Python讀寫文本文件一樣,其實沒什么不同。DOM是瀏覽器解析處理HTML文檔的對象模型,但是本質上HTML是個文本文件(XML文件),需要做的其實就是將HTML元素、CSS元素以及動態數據組合起來,一個動態模塊組件就實現了。最后照葫蘆畫瓢,依次實現其他的組件模塊在index.html中引入,一個動態頁面就組合起來了。

實現代碼

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/82373.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82373.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82373.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

NTFS0x90屬性和0xa0屬性和0xb0屬性的一一對應關系是index_entry中的index_node中VCN和runlist和bitmap

第一部分&#xff1a; 0: kd> dt _FILE_RECORD_SEGMENT_HEADER 0xc1241400 Ntfs!_FILE_RECORD_SEGMENT_HEADER 0x000 MultiSectorHeader : _MULTI_SECTOR_HEADER 0x008 Lsn : _LARGE_INTEGER 0x80e74aa 0x010 SequenceNumber : 5 0x012 Referen…

PCB 通孔是電容性的,但不一定是電容器

哼&#xff1f;……這是什么意思&#xff1f;…… 多年來&#xff0c;流行的觀點是 PCB 通孔本質上是電容性的&#xff0c;因此可以用集總電容器進行建模。雖然當信號的上升時間大于或等于過孔不連續性延遲的 3 倍時&#xff0c;這可能是正確的&#xff0c;但我將向您展示為什…

Flutter 3.32 新特性

2天前&#xff0c;Flutter發布了最新版本3.32&#xff0c;我們來一起看下29到32有哪些變化。 簡介 歡迎來到Flutter 3.32&#xff01;此版本包含了旨在加速開發和增強應用程序的功能。準備好在網絡上進行熱加載&#xff0c;令人驚嘆的原生保真Cupertino&#xff0c;以及與Fir…

漢諾塔超級計算機數據區結構和源代碼詳細設計

### 數據區結構與源代碼詳細設計 基于"滿秩二叉樹"存儲模型的設計理念&#xff0c;我設計了以下數據區結構和實現方案&#xff1a; #### 1. 滿秩二叉樹存儲模型 **數據結構設計**&#xff1a; python class TreeNode: """二叉樹節點結構&#xff0c…

GitHub Copilot 現已支持 AI Coding Agent

VS Code 開始越來越像 Cursor 和 WindSurf 了。 這周,GitHub 發布了一個新的編程代理,直接嵌入到 GitHub 中。當你將 GitHub 問題分配給 Copilot 或在 VS Code 中提示它時,該代理會啟動一個由 GitHub Actions 驅動的安全且完全可定制的開發環境。 這一公告來自微軟首席執行…

【辰輝創聚生物】FGF信號通路相關蛋白:解碼生命調控的關鍵樞紐

在生命科學的探索旅程中&#xff0c;成纖維細胞生長因子&#xff08;Fibroblast Growth Factor&#xff0c;FGF&#xff09;信號通路猶如精密儀器中的核心齒輪&#xff0c;驅動著眾多生命活動的有序進行。FGF 信號通路相關蛋白作為該通路的重要組成部分&#xff0c;其結構與功能…

算法的學習筆記— 構建乘積數組(牛客JZ66)

構建乘積數組 1. 問題背景與描述 1.1 題目來源與鏈接 本題來源于NowCoder在線編程平臺&#xff0c;是劍指Offer系列面試題中的經典問題。題目鏈接為&#xff1a;NowCoder。該問題在算法面試中出現頻率較高&#xff0c;主要考察數組操作和數學思維。 1.2 問題描述與要求 給…

SpringBoot+ELK 搭建日志監控平臺

ELK 簡介 ELK&#xff08;Elasticsearch, Logstash, Kibana&#xff09;是一個目前主流的開源日志監控平臺。由三個主要組件組成的&#xff1a; Elasticsearch&#xff1a; 是一個開源的分布式搜索和分析引擎&#xff0c;可以用于全文檢索、結構化檢索和分析&#xff0c;它構建…

python36

仔細回顧一下神經網絡到目前的內容&#xff0c;沒跟上進度的同學補一下進度。 作業&#xff1a;對之前的信貸項目&#xff0c;利用神經網絡訓練下&#xff0c;嘗試用到目前的知識點讓代碼更加規范和美觀。 # 先運行之前預處理好的代碼 import pandas as pd import pandas as pd…

SGlang 推理模型優化(PD架構分離)

一、技術背景 隨著大型語言模型&#xff08;LLM&#xff09;廣泛應用于搜索、內容生成、AI助手等領域&#xff0c;對模型推理服務的并發能力、響應延遲和資源利用效率提出了前所未有的高要求。與模型訓練相比&#xff0c;推理是一個持續進行、資源消耗巨大的任務&#xff0c;尤…

模型實戰(28)之 yolov5分類模型 訓練自己的數據集

模型實戰(28)之 yolov5分類模型 訓練自己的數據集 本文以手寫數字數據集為例總結YOLO分類模型如何訓練自己的數據集,關于數據集的預處理可以看這篇:https://blog.csdn.net/yohnyang/article/details/148209978?spm=1001.2014.3001.5502 yolov5曾是在 2021-2023 年十分流行…

醫學寫作人才管理策略

1. 人才選擇:精準定位核心能力 1.1 人才篩選標準 1.1.1 硬性要求 初創生物制藥公司醫學寫作崗位對專業背景要求嚴格,候選人需具備醫學、藥學或生物學碩士及以上學歷,博士優先。同時,熟悉ICH、FDA/EMA等法規指南是必備條件,且至少有1-3年醫學寫作經驗,或相關領域如臨床研…

Axure酒店管理系統原型

酒店管理系統通常被設計為包含多個模塊或界面&#xff0c;以支持酒店運營的不同方面和參與者。其中&#xff0c;管理端和商戶端是兩個核心組成部分&#xff0c;它們各自承擔著不同的職責和功能。 軟件版本&#xff1a;Axure RP 9 預覽地址&#xff1a;https://556i1e.axshare.…

云原生安全之HTTP協議:從基礎到實戰的安全指南

&#x1f525;「炎碼工坊」技術彈藥已裝填&#xff01; 點擊關注 → 解鎖工業級干貨【工具實測|項目避坑|源碼燃燒指南】 一、基礎概念&#xff1a;HTTP協議的核心要素 HTTP&#xff08;HyperText Transfer Protocol&#xff09;是云原生應用中客戶端與服務器通信的基礎協議&a…

怎樣解決photoshop閃退問題

檢查系統資源&#xff1a;在啟動 Photoshop 之前&#xff0c;打開任務管理器檢查 CPU 和內存的使用情況。如果發現資源占用過高&#xff0c;嘗試關閉不必要的程序或重啟計算機以釋放資源。更新 Photoshop 版本&#xff1a;確保 Photoshop 是最新版本。Adobe 經常發布更新以修復…

修復ubuntu server筆記本合蓋導致的無線網卡故障

下班回到家發現走時還好的局域網 ubuntu server 24 連不上了&#xff0c;趕緊打開筆記本查看下原因&#xff0c;發現控制臺出了一堆看不懂的內容&#xff1a; 根據搜索結果&#xff0c;筆記本合蓋導致無線網卡故障可能與電源管理設置和系統休眠策略有關&#xff0c;以下是具體…

CMake指令:find_package()在Qt中的應用

目錄 1.簡介 2.Qt 核心組件與常用模塊 3.配置模式的工作流程 4.完整示例&#xff1a;構建 Qt GUI 應用 5.常見問題與解決方案 6.總結 1.簡介 在 CMake 中使用 find_package(Qt) 是集成 Qt 庫的核心步驟。Qt 從 5.x 版本開始全面支持 配置模式&#xff08;Config Mode&…

Docker 鏡像調試最佳實踐

當你已經構建了一個 Docker 鏡像&#xff0c;但運行它的容器啟動后立即退出&#xff08;通常是因為服務異常或配置錯誤&#xff09;&#xff0c;你仍然可以通過以下幾種方式進入鏡像內部進行調試。 ? 最佳實踐&#xff1a;如何對一個“啟動即退出”的鏡像進行命令行調試&#…

使用Java制作貪吃蛇小游戲

在這篇文章中&#xff0c;我將帶你一步步實現一個經典的貪吃蛇小游戲。我們將使用Java語言和Swing庫來構建這個游戲&#xff0c;它包含了貪吃蛇游戲的基本功能&#xff1a;蛇的移動、吃食物、計分以及游戲結束判定。 游戲設計思路 貪吃蛇游戲的基本原理是&#xff1a;玩家控制…

【linux】umask權限掩碼

umask這個接口在一些程序初始化的時候經常會見到&#xff0c;處于安全性&#xff0c;可以縮小進程落盤文件的權限。 1、linux文件系統的權限規則 文件的默認權限由系統決定&#xff08;通常是 0666&#xff0c;即所有人可讀可寫&#xff09;。 目錄的默認權限通常是 0777&am…