在电商应用中,购物车是一个核心功能模块。本文将带你逐步实现一个具备基本功能的购物车,
包括商品展示、数量增减、全选 / 反选、计算总价以及删除商品等功能。
我们将用HTML、CSS、原生JavaScript实现本次效果
本次聚焦显示效果,如下图所示:
本次实现的目标购物车具备以下功能:
(1)商品展示:展示商品图片、名称、价格以及数量。
(2)数量调整:支持通过按钮和输入框增加或减少商品数量,并限制数量最小值为 1。
(3)选中与全选:每个商品可单独选中,也有全选按钮,当所有商品都被选中时,全选按钮自动勾选。
(4)总价计算:实时计算并显示已选中商品的总价,以及选中商品的总数量。
(5)商品删除:可以删除购物车中的单个商品。
实现思路:
【1】通过遍历方法实现数据的初始渲染
【2】累加器通过绑定事件,传值id查询实现加减操作
注意:本次添加input的失焦监听事件,赋值并重新渲染
【3】通过input的原始属性checked来进行选中的操作
此时要考虑复选框的更改更新,选中的时机触发
【4】价格的计算,获取并遍历数组计算即可
【5】删除时通过id查询,即删除,更新视图即可
前期准备:
3.1 HTML 结构搭建
首先,我们构建购物车的基本 HTML 结构,包括商品列表的容器和底部的操作区域。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>购物车基本思路实现</title>
<style>
* {
margin: 0;
padding: 0;
font-size: 12px;
box-sizing: border-box;
}
.box {
width: 400px;
padding-top: 5%;
margin: 0 auto;
}
li {
display: flex;
justify-content: space-around;
gap: 10px;
}
img {
width: 100px;
}
.footer {
justify-content: space-around;
display: flex;
gap: 20px;
margin-top: 50px;
}
.input {
width: 20px;
text-align: center;
}
.del {
margin-left: 5%;
}
.mar-b {
margin-top: 10px;
}
</style>
</head>
<body>
<div class="box">
<ul></ul>
<div class="footer">
<div><input type="checkbox" name="" id="checkedAll" />全选</div>
<div>总价:¥<span class="money">0</span></div>
<div>结算(<span class="checkNum">0</span>)</div>
</div>
</div>
</body>
</html>
在这段代码中,我们创建了一个 `.box` 类的容器,用于包裹整个购物车。内部有一个无序列表 `
- ` 用于展示商品列表,底部的 `.footer` 区域包含全选按钮、总价显示和结算数量显示。
3.2 定义商品数据
在 JavaScript 中,我们定义一个包含商品信息的数组,作为购物车的数据源
let goods = [
{
id: 1,
num: 2,
goodsName: "小小",
price: 14.34,
type: true,
img: "https://img13.360buyimg.com/n5/s720x720_jfs/t1/350658/6/20365/122167/69032f21F233e211c/facf8296ce748477.jpg.avif"
},
{
id: 2,
num: 2,
goodsName: "小小",
price: 4.5,
type: true,
img: "https://img13.360buyimg.com/n5/s720x720_jfs/t1/350658/6/20365/122167/69032f21F233e211c/facf8296ce748477.jpg.avif"
},
{
id: 3,
num: 2,
goodsName: "小小",
price: 4.99,
type: false,
img: "https://img13.360buyimg.com/n5/s720x720_jfs/t1/350658/6/20365/122167/69032f21F233e211c/facf8296ce748477.jpg.avif"
}
];
每个商品对象包含 id(唯一标识)、num(数量)、goodsName(商品名称)、price(价格)、type(是否选中状态)以及 img(商品图片链接)。
4.1页面渲染功能
将商品数据渲染到 HTML 页面上。
// 1.渲染,重绘
function setDom() {
let lis = "";
goods.forEach((item) => {
lis += `<li>
<input onclick="setCheck(${item.id},event)" type="checkbox" ${item.type? "checked" : ""} />
<img
src="${item.img}"
alt=""
/>
<div>
<p>${item.goodsName}</p>
<p>价格:${item.price}</p>
<p class="mar-b">
<button onclick="setNum(-1,${item.id})">-</button
><input class="input" onblur="updateNum(${item.id},event)" value="${item.num}" type="text" /><button onclick="setNum(1,${item.id})">+</button>
</p>
</div>
<button class="del" onclick="removegoods(${item.id})">删除</button>
</li>`;
});
document.querySelector("ul").innerHTML = lis;
}
// 初始化DOM渲染
setDom();
//getMoney();此时还未编写该函数,该函数是进行金额计算及更新操作
此时我们实现了第一步渲染的操作,效果如最上图
4.2 商品数量改变功能
我们需要实现通过点击按钮或在输入框输入来改变商品数量的功能,并确保数量不小于 1。
// 商品数量改变事件
function setNum(e, id) {
let index = goods.findIndex((item) => item.id == id);
goods[index].num += e;
//个性化设置
if (goods[index].num <= 1) {
goods[index].num = 1;
}
setDom();
// getMoney();此时还未编写该函数,该函数是进行金额计算及更新操作
}
setNum 函数接受两个参数:e 表示数量的增减值(1 或 -1),id 用于确定是哪个商品的数量发生改变。通过 findIndex 找到对应的商品,更新其数量,并调用 setDom 和 getMoney 函数来更新页面显示和总价计算。
那么我们实现了第二步,累加器的初步实现
但是此时有问题:当用户在需求量大时在输入框输入时,也需要进行更新操作,接下来来编写这部分代码。
实现通过输入框改变商品数量后实时更新购物车的功能。
// 输入框输入更新
function updateNum(id, e) {
let index = goods.findIndex((item) => item.id == id);
goods[index].num = Number(e.target.value);
setDom();
// getMoney();此时还未编写该函数,该函数是进行金额计算及更新操作
}
updateNum 函数在输入框失去焦点时,获取输入框的值并更新对应的商品数量,然后更新页面和总价。
在此我们实现了第二步的完整功能
4.3 全选与反选功能的实现
实现全选和反选的功能,以及根据商品选中状态更新全选按钮的状态。
let butAll = document.querySelector("#checkedAll");
// 全选按钮,点全选按钮事件
butAll.onclick = function () {
goods.forEach((item) => (item.type = this.checked));
setDom();
// getMoney();此时还未编写该函数,该函数是进行金额计算及更新操作
};
// 监听商品选中状态,若全部选中则全选框变为true
function typeEvery() {
butAll.checked = goods.every((item) => item.type);
if (goods.length == 0) {
butAll.checked = false;
}
// getMoney();此时还未编写该函数,该函数是进行金额计算及更新操作
}
// 复选框改变时为相关type修改更新
function setCheck(id, e) {
let index = goods.findIndex((item) => item.id == id);
goods[index].type = e.target.checked;
typeEvery();
// getMoney();此时还未编写该函数,该函数是进行金额计算及更新操作
}
butAll.onclick事件处理函数在点击全选按钮时,遍历所有商品,将它们的type属性设置为与全选按钮相同的状态,然后更新页面和总价。typeEvery函数检查所有商品是否都被选中,如果是则勾选全选按钮,同时处理购物车为空时全选按钮应取消勾选的情况,并更新总价。setCheck函数在单个商品的复选框状态改变时,更新对应商品的type属性,并调用typeEvery和getMoney函数。
4.4 总价计算功能
实时计算并显示已选中商品的总价和选中商品的总数量。
let sum;//总数金额容器
let checkNum;//总数商品个数容器
// 总额计算
function getMoney() {
let money = document.querySelector(".money");
let checkNumbox = document.querySelector(".checkNum");
checkNum = 0;
sum = 0;
goods.forEach((item) => {
if (item.type) {
sum += item.num * item.price;
checkNum += item.num;
}
});
checkNumbox.innerHTML = checkNum;
sum = Number(sum).toFixed(2);
money.innerHTML = sum;
}
getMoney 函数遍历所有商品,累加已选中商品的总价和数量,然后更新页面上的总价和结算数量显示。
4.5 删除商品功能
实现删除购物车中单个商品的功能。
// 删除该行元素
function removegoods(id) {
let index = goods.findIndex((item) => item.id == id);
goods.splice(index, 1);
setDom();
getMoney();
typeEvery();
}
removegoods 函数通过商品的 id 找到要删除的商品,使用 splice 方法从 goods 数组中移除该商品,然后更新页面和总价,并检查全选状态。
通过以上步骤,我们就完成了一个具备基本功能的购物车的初步实现。
但代码冗余率比高,可通过开启代理Proxy的方式去进一步优化代码。
例如:
进行优化
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>购物车基本思路实现</title>
<style>
* {
margin: 0;
padding: 0;
font-size: 12px;
box-sizing: border-box;
}
.box {
width: 400px;
padding-top: 5%;
margin: 0 auto;
}
li {
display: flex;
justify-content: space-around;
gap: 10px;
}
img {
width: 100px;
}
.footer {
justify-content: space-around;
display: flex;
gap: 20px;
margin-top: 50px;
}
.input {
width: 20px;
text-align: center;
}
.del {
margin-left: 5%;
}
.mar-b {
margin-top: 10px;
}
</style>
</head>
<body>
<div class="box">
<ul></ul>
<div class="footer">
<div><input type="checkbox" name="" id="checkedAll" />全选</div>
<div>总价:¥<span class="money">0</span></div>
<div>结算(<span class="checkNum">0</span>)</div>
</div>
</div>
<script>
let goods = [
{
id: 1,
num: 2,
goodsName: "小小",
price: 14.34,
type: true,
img: "https://img13.360buyimg.com/n5/s720x720_jfs/t1/350658/6/20365/122167/69032f21F233e211c/facf8296ce748477.jpg.avif",
},
{
id: 2,
num: 2,
goodsName: "小小",
price: 4.5,
type: true,
img: "https://img13.360buyimg.com/n5/s720x720_jfs/t1/350658/6/20365/122167/69032f21F233e211c/facf8296ce748477.jpg.avif",
},
{
id: 3,
num: 2,
goodsName: "小小",
price: 4.99,
type: false,
img: "https://img13.360buyimg.com/n5/s720x720_jfs/t1/350658/6/20365/122167/69032f21F233e211c/facf8296ce748477.jpg.avif",
},
];
// 商品数量改变事件
function setNum(e, id) {
let index = goods.findIndex((item) => item.id == id);
goods[index].num += e;
if (goods[index].num <= 1) {
goods[index].num = 1;
}
setDom();
getMoney();
}
// 删除该行元素
function removegoods(id) {
let index = goods.findIndex((item) => item.id == id);
goods.splice(index, 1);
setDom();
getMoney();
typeEvery();
}
let butAll = document.querySelector("#checkedAll");
// 全选按钮,点全选事件
butAll.onclick = function () {
goods.forEach((item) => (item.type = this.checked));
setDom();
getMoney();
};
// 全选按钮 第二种行内添加onclick事件,可正常使用
// function typeEvery() {
// let typeEveryStatue = goods.every((item) => item.type);
// // false时全部修改为true
// if (!typeEveryStatue) {
// // butAll.setAttribute("checked", "checked");
// goods.forEach((item) => {
// item.type = true;
// });
// } else {
// goods.forEach((item) => {
// item.type = false;
// });
// }
// setDom();
// }
// 监听商品选中状态,若全部选中则全选框变为true
function typeEvery() {
butAll.checked = goods.every((item) => item.type);
if(goods.length==0){
butAll.checked = false;
}
getMoney();
}
// 复选框改变时为相关type修改更新
function setCheck(id, e) {
let index = goods.findIndex((item) => item.id == id);
goods[index].type = e.target.checked;
typeEvery();
getMoney();
}
let sum;
let checkNum;
// 总额计算
function getMoney() {
let money = document.querySelector(".money");
let checkNumbox = document.querySelector(".checkNum");
checkNum = 0;
sum = 0;
goods.forEach((item) => {
if (item.type) {
sum += item.num * item.price;
checkNum += item.num;
}
});
checkNumbox.innerHTML = checkNum;
sum = Number(sum).toFixed(2);
money.innerHTML = sum;
}
// 1.重新渲染,重绘
function setDom() {
let lis = "";
goods.forEach((item) => {
lis += `<li>
<input onclick="setCheck(${item.id},event)" type="checkbox" ${
item.type ? "checked" : ""
}/>
<img
src="${item.img}"
alt=""
/>
<div>
<p>${item.goodsName}</p>
<p>价格:${item.price}</p>
<p class="mar-b">
<button onclick="setNum(-1,${item.id})">-</button
><input class="input" onblur="updateNum(${
item.id
},event)" value="${
item.num
}" type="text" /><button onclick="setNum(1,${item.id})">+</button>
</p>
</div>
<button class="del" onclick="removegoods(${item.id})">删除</button>
</li>`;
});
document.querySelector("ul").innerHTML = lis;
}
// 初始化渲染
setDom();
getMoney();
// 输入框输入更新
function updateNum(id, e) {
let index = goods.findIndex((item) => item.id == id);
goods[index].num = Number(e.target.value);
setDom();
getMoney();
}
</script>
</body>
</html>
希望这篇教程能帮助你理解和掌握购物车功能的开发思路与实现方法。