使用JavaScript和HTML动态显示多个同结构元素的教程
在网页开发中,我们经常会遇到需要动态展示多个结构相同的元素的需求,比如商品列表、用户评论、待办事项等。如果手动编写每个元素的HTML代码,不仅效率低下,后期维护也会非常麻烦。本文将介绍如何通过JavaScript结合HTML,实现同结构元素的动态渲染,让开发过程更高效。
基础原理说明
动态显示同结构元素的核心思路是:先准备一份数据集合,再定义一个生成单个元素结构的函数,最后遍历数据集合,调用生成函数产出所有元素并插入到页面的指定容器中。整个过程不需要手动编写重复的HTML代码,只需要维护数据即可。
这里用到的HTML标签主要是容器类标签,比如 <div> 或者 <ul>,用来承载动态生成的内容。JavaScript部分会用到数组遍历方法、DOM操作相关方法,下面我们通过具体示例来演示完整实现过程。
示例:动态渲染商品列表
假设我们需要展示一个商品列表,每个商品包含商品名称、价格和库存三个信息,结构完全一致。首先我们先编写基础的HTML结构,只需要准备一个空的容器即可:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态商品列表</title>
<style>
.goods-container {
width: 800px;
margin: 20px auto;
}
.goods-item {
border: 1px solid #e5e5e5;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-name {
font-size: 16px;
font-weight: bold;
color: #333;
}
.goods-price {
color: #ff4d4f;
font-size: 18px;
}
.goods-stock {
color: #666;
font-size: 14px;
}
.low-stock {
color: #ff4d4f;
}
</style>
</head>
<body>
<div class="goods-container" id="goodsContainer">
<!-- 动态生成的商品元素会插入到这里 -->
</div>
<script>
// 商品数据集合,实际开发中通常从接口获取
const goodsList = [
{ name: '无线蓝牙耳机', price: 299, stock: 50 },
{ name: '机械键盘', price: 459, stock: 23 },
{ name: '便携充电宝', price: 129, stock: 8 },
{ name: '4K高清显示器', price: 1899, stock: 15 },
{ name: '人体工学椅', price: 1299, stock: 5 }
];
// 获取承载商品的容器
const container = document.getElementById('goodsContainer');
/**
* 生成单个商品元素的HTML字符串
* @param {Object} goods 单个商品数据
* @returns {string} 商品元素的HTML字符串
*/
function createGoodsItem(goods) {
// 库存小于10时添加低库存样式
const stockClass = goods.stock < 10 ? 'low-stock' : '';
return `
<div class="goods-item">
<div class="goods-name">${goods.name}</div>
<div class="goods-price">¥${goods.price}</div>
<div class="goods-stock ${stockClass}">库存:${goods.stock}件</div>
</div>
`;
}
// 遍历商品数据,生成所有元素并插入容器
let goodsHtml = '';
goodsList.forEach(goods => {
goodsHtml += createGoodsItem(goods);
});
container.innerHTML = goodsHtml;
</script>
</body>
</html>上面的代码实现了完整的动态渲染逻辑:首先定义了商品数据的数组,然后编写了 createGoodsItem 函数,接收单个商品对象,返回对应结构的HTML字符串。接着遍历数据数组,拼接所有商品的HTML字符串,最后一次性赋值给容器的 innerHTML 属性,完成所有元素的渲染。
这里需要注意,如果数据量非常大,频繁操作 innerHTML 可能会有性能问题,这种情况下可以使用文档片段 DocumentFragment 来优化,先创建片段,把所有生成的元素插入片段,最后再把片段插入容器,减少DOM重绘次数。
进阶:动态添加和删除元素
除了初始渲染,我们还经常需要支持动态添加新元素或者删除已有元素。下面我们在上面的示例基础上,增加添加商品和删除单个商品的功能:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>可操作的动态商品列表</title>
<style>
.goods-container {
width: 800px;
margin: 20px auto;
}
.goods-item {
border: 1px solid #e5e5e5;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-name {
font-size: 16px;
font-weight: bold;
color: #333;
width: 200px;
}
.goods-price {
color: #ff4d4f;
font-size: 18px;
width: 120px;
}
.goods-stock {
color: #666;
font-size: 14px;
width: 120px;
}
.low-stock {
color: #ff4d4f;
}
.delete-btn {
padding: 6px 12px;
background-color: #ff4d4f;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.add-section {
margin-bottom: 20px;
padding: 15px;
border: 1px dashed #e5e5e5;
border-radius: 8px;
}
.add-input {
margin-right: 10px;
padding: 6px 10px;
border: 1px solid #e5e5e5;
border-radius: 4px;
width: 150px;
}
.add-btn {
padding: 6px 16px;
background-color: #1677ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="goods-container">
<!-- 添加商品区域 -->
<div class="add-section">
<input type="text" class="add-input" id="goodsNameInput" placeholder="商品名称">
<input type="number" class="add-input" id="goodsPriceInput" placeholder="商品价格">
<input type="number" class="add-input" id="goodsStockInput" placeholder="商品库存">
<button class="add-btn" id="addGoodsBtn">添加商品</button>
</div>
<!-- 商品列表容器 -->
<div id="goodsListContainer"></div>
</div>
<script>
// 初始商品数据
let goodsList = [
{ id: 1, name: '无线蓝牙耳机', price: 299, stock: 50 },
{ id: 2, name: '机械键盘', price: 459, stock: 23 },
{ id: 3, name: '便携充电宝', price: 129, stock: 8 }
];
// 用于生成商品唯一id的计数器
let goodsIdCounter = 4;
const container = document.getElementById('goodsListContainer');
const addBtn = document.getElementById('addGoodsBtn');
const nameInput = document.getElementById('goodsNameInput');
const priceInput = document.getElementById('goodsPriceInput');
const stockInput = document.getElementById('goodsStockInput');
/**
* 生成单个商品元素的DOM节点
* @param {Object} goods 单个商品数据
* @returns {HTMLElement} 商品元素DOM节点
*/
function createGoodsElement(goods) {
const item = document.createElement('div');
item.className = 'goods-item';
item.setAttribute('data-id', goods.id);
const stockClass = goods.stock < 10 ? 'low-stock' : '';
item.innerHTML = `
<div class="goods-name">${goods.name}</div>
<div class="goods-price">¥${goods.price}</div>
<div class="goods-stock ${stockClass}">库存:${goods.stock}件</div>
<button class="delete-btn">删除</button>
`;
// 给删除按钮绑定点击事件
const deleteBtn = item.querySelector('.delete-btn');
deleteBtn.addEventListener('click', function() {
deleteGoods(goods.id);
});
return item;
}
/**
* 渲染所有商品到容器
*/
function renderGoodsList() {
// 使用文档片段减少DOM操作
const fragment = document.createDocumentFragment();
goodsList.forEach(goods => {
const element = createGoodsElement(goods);
fragment.appendChild(element);
});
// 清空容器后插入新内容
container.innerHTML = '';
container.appendChild(fragment);
}
/**
* 删除指定id的商品
* @param {number} id 要删除的商品id
*/
function deleteGoods(id) {
goodsList = goodsList.filter(goods => goods.id !== id);
renderGoodsList();
}
/**
* 添加新的商品
*/
function addGoods() {
const name = nameInput.value.trim();
const price = Number(priceInput.value);
const stock = Number(stockInput.value);
if (!name) {
alert('请输入商品名称');
return;
}
if (isNaN(price) || price <= 0) {
alert('请输入有效的商品价格');
return;
}
if (isNaN(stock) || stock < 0) {
alert('请输入有效的商品库存');
return;
}
const newGoods = {
id: goodsIdCounter++,
name,
price,
stock
};
goodsList.push(newGoods);
renderGoodsList();
// 清空输入框
nameInput.value = '';
priceInput.value = '';
stockInput.value = '';
}
// 绑定添加按钮点击事件
addBtn.addEventListener('click', addGoods);
// 初始渲染
renderGoodsList();
</script>
</body>
</html>这个进阶示例中,我们做了几个优化:首先给每个商品添加了唯一的id,方便后续删除时定位;生成元素时不再拼接HTML字符串,而是使用 document.createElement 创建DOM节点,这样绑定事件更直接,也不容易出现XSS相关的问题;添加和删除操作之后,只需要重新调用渲染函数,就可以更新页面上的内容,不需要手动操作每个DOM节点。
注意事项
- 如果商品数据中包含用户输入的内容,直接拼接HTML字符串可能会有XSS风险,建议使用
textContent属性赋值文本内容,或者使用DOM创建方法替代字符串拼接。 - 动态生成的元素如果需要在后续通过选择器获取,要注意渲染完成之后再执行选择操作,或者在渲染时直接绑定事件,避免获取不到元素的问题。
- 如果数据量非常大,比如上千条数据,一次性渲染所有元素会导致页面卡顿,这种情况可以考虑分页加载或者虚拟滚动的方案,只渲染当前可视区域的元素。
通过上面的方法,我们可以很灵活地实现各种同结构元素的动态展示,不管是简单的列表渲染,还是带交互的复杂场景,都可以通过调整数据结构和生成逻辑来满足需求,大大提升开发效率。