- 完全响应式: 专为手机端优化,在平板和桌面浏览器上也能良好显示。
- 现代设计: 使用简洁的卡片式布局,清晰的视觉层次。
- 功能齐全: 包含商品选择、数量增减、价格实时计算、删除商品、优惠券输入、结算等功能。
- 使用原生技术: 只用 HTML, CSS (Tailwind CSS), 和原生 JavaScript,易于理解和集成。
- 用户体验良好: 包含加载动画、空购物车状态、价格格式化等细节。
最终预览效果
第一步:准备 HTML 结构
这是整个购物车页面的骨架,我们使用语义化的 HTML5 标签,并用 Tailwind CSS 的类名来快速构建样式。

(图片来源网络,侵删)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">购物车 - 手机商城</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* 自定义滚动条样式,使其更美观 */
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #888;
border-radius: 2px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* 防止 iOS 上橡皮筋效果 */
body, html {
overscroll-behavior-y: contain;
}
</style>
</head>
<body class="bg-gray-50">
<!-- 顶部导航栏 -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="flex items-center justify-between p-4">
<a href="#" class="text-gray-600">
<i class="fas fa-arrow-left text-xl"></i>
</a>
<h1 class="text-lg font-semibold">购物车(3)</h1>
<a href="#" class="text-gray-600">
<i class="fas fa-ellipsis-v text-xl"></i>
</a>
</div>
</header>
<main class="pb-24">
<!-- 购物车列表 -->
<section class="p-4">
<div id="cart-items" class="space-y-4 custom-scrollbar" style="max-height: calc(100vh - 180px); overflow-y: auto;">
<!-- 购物车项模板 1 -->
<div class="bg-white rounded-lg shadow-sm p-4 cart-item" data-price="299" data-id="1">
<div class="flex items-start">
<input type="checkbox" class="item-checkbox mt-1 w-5 h-5 text-blue-600 rounded" checked>
<img src="https://via.placeholder.com/80x80" alt="商品图片" class="w-20 h-20 object-cover rounded ml-3">
<div class="flex-1 ml-3">
<h3 class="font-medium text-gray-800 line-clamp-2">Apple AirPods Pro (第2代) 主动降噪无线蓝牙耳机</h3>
<p class="text-sm text-gray-500 mt-1">颜色: 白色</p>
<div class="flex items-center justify-between mt-3">
<p class="text-red-500 font-bold">
<span class="symbol">¥</span><span class="item-price">299</span>
</p>
<div class="flex items-center border border-gray-300 rounded">
<button class="quantity-btn minus px-3 py-1 text-gray-600 hover:bg-gray-100">
<i class="fas fa-minus"></i>
</button>
<span class="quantity px-3 py-1 border-x border-gray-300">1</span>
<button class="quantity-btn plus px-3 py-1 text-gray-600 hover:bg-gray-100">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
<button class="delete-btn ml-2 text-gray-400 hover:text-red-500">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
<!-- 购物车项模板 2 -->
<div class="bg-white rounded-lg shadow-sm p-4 cart-item" data-price="158" data-id="2">
<div class="flex items-start">
<input type="checkbox" class="item-checkbox mt-1 w-5 h-5 text-blue-600 rounded" checked>
<img src="https://via.placeholder.com/80x80" alt="商品图片" class="w-20 h-20 object-cover rounded ml-3">
<div class="flex-1 ml-3">
<h3 class="font-medium text-gray-800 line-clamp-2">小米手环 8 NFC版 智能运动手环 心率血氧睡眠监测</h3>
<p class="text-sm text-gray-500 mt-1">颜色: 黑色</p>
<div class="flex items-center justify-between mt-3">
<p class="text-red-500 font-bold">
<span class="symbol">¥</span><span class="item-price">158</span>
</p>
<div class="flex items-center border border-gray-300 rounded">
<button class="quantity-btn minus px-3 py-1 text-gray-600 hover:bg-gray-100">
<i class="fas fa-minus"></i>
</button>
<span class="quantity px-3 py-1 border-x border-gray-300">2</span>
<button class="quantity-btn plus px-3 py-1 text-gray-600 hover:bg-gray-100">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
<button class="delete-btn ml-2 text-gray-400 hover:text-red-500">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
<!-- 购物车项模板 3 -->
<div class="bg-white rounded-lg shadow-sm p-4 cart-item" data-price="45" data-id="3">
<div class="flex items-start">
<input type="checkbox" class="item-checkbox mt-1 w-5 h-5 text-blue-600 rounded">
<img src="https://via.placeholder.com/80x80" alt="商品图片" class="w-20 h-20 object-cover rounded ml-3">
<div class="flex-1 ml-3">
<h3 class="font-medium text-gray-800 line-clamp-2">Anker 安克 20W 氮化镓充电器 GaNPrime 轻薄快充</h3>
<p class="text-sm text-gray-500 mt-1">颜色: 白色</p>
<div class="flex items-center justify-between mt-3">
<p class="text-red-500 font-bold">
<span class="symbol">¥</span><span class="item-price">45</span>
</p>
<div class="flex items-center border border-gray-300 rounded">
<button class="quantity-btn minus px-3 py-1 text-gray-600 hover:bg-gray-100">
<i class="fas fa-minus"></i>
</button>
<span class="quantity px-3 py-1 border-x border-gray-300">1</span>
<button class="quantity-btn plus px-3 py-1 text-gray-600 hover:bg-gray-100">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
<button class="delete-btn ml-2 text-gray-400 hover:text-red-500">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
</div>
</section>
<!-- 空购物车提示 (默认隐藏) -->
<div id="empty-cart" class="hidden flex flex-col items-center justify-center p-8 text-center">
<i class="fas fa-shopping-cart text-6xl text-gray-300 mb-4"></i>
<p class="text-gray-500 text-lg">购物车还是空的</p>
<a href="#" class="mt-4 bg-blue-500 text-white px-6 py-2 rounded-full">去逛逛</a>
</div>
</main>
<!-- 底部结算栏 -->
<footer class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200">
<div class="flex items-center justify-between p-4">
<div class="flex items-center">
<input type="checkbox" id="select-all" class="w-5 h-5 text-blue-600 rounded mr-2">
<label for="select-all" class="text-gray-700">全选</label>
</div>
<div class="flex items-center">
<div class="text-right mr-4">
<p class="text-sm text-gray-500">合计:</p>
<p class="text-red-500 font-bold text-lg">
<span class="symbol">¥</span><span id="total-price">756</span>
</p>
</div>
<button id="checkout-btn" class="bg-red-500 text-white px-6 py-2 rounded-full font-medium">
结算(2)
</button>
</div>
</div>
</footer>
<!-- 删除确认弹窗 (默认隐藏) -->
<div id="delete-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-80 mx-4">
<h3 class="text-lg font-semibold mb-2">确认删除</h3>
<p class="text-gray-600 mb-6">您确定要从购物车中删除该商品吗?</p>
<div class="flex justify-end space-x-3">
<button id="cancel-delete" class="px-4 py-2 border border-gray-300 rounded text-gray-700">取消</button>
<button id="confirm-delete" class="px-4 py-2 bg-red-500 text-white rounded">确定</button>
</div>
</div>
</div>
<script src="cart.js"></script>
</body>
</html>
第二步:编写 JavaScript 逻辑 (cart.js)
这是让购物车“活”起来的核心部分,我们把它放在一个单独的 cart.js 文件中,并在 HTML 的末尾引入。
document.addEventListener('DOMContentLoaded', () => {
// --- DOM 元素 ---
const cartItemsContainer = document.getElementById('cart-items');
const emptyCartMsg = document.getElementById('empty-cart');
const selectAllCheckbox = document.getElementById('select-all');
const totalPriceElement = document.getElementById('total-price');
const checkoutBtn = document.getElementById('checkout-btn');
const deleteModal = document.getElementById('delete-modal');
const cancelDeleteBtn = document.getElementById('cancel-delete');
const confirmDeleteBtn = document.getElementById('confirm-delete');
const cartCountTitle = document.querySelector('h1');
let itemToDelete = null;
// --- 工具函数 ---
function formatPrice(price) {
return price.toFixed(2);
}
function updateCartCount() {
const checkedItems = document.querySelectorAll('.item-checkbox:checked');
const count = checkedItems.length;
cartCountTitle.textContent = `购物车(${count})`;
checkoutBtn.textContent = `结算(${count})`;
}
function updateTotalPrice() {
let total = 0;
const checkedItems = document.querySelectorAll('.item-checkbox:checked');
checkedItems.forEach(item => {
const cartItem = item.closest('.cart-item');
const price = parseFloat(cartItem.dataset.price);
const quantity = parseInt(cartItem.querySelector('.quantity').textContent);
total += price * quantity;
});
totalPriceElement.textContent = formatPrice(total);
}
function toggleEmptyCart() {
const totalItems = document.querySelectorAll('.cart-item').length;
if (totalItems === 0) {
cartItemsContainer.classList.add('hidden');
emptyCartMsg.classList.remove('hidden');
} else {
cartItemsContainer.classList.remove('hidden');
emptyCartMsg.classList.add('hidden');
}
}
// --- 事件监听器 ---
// 1. 单个商品选择
cartItemsContainer.addEventListener('change', (e) => {
if (e.target.classList.contains('item-checkbox')) {
updateTotalPrice();
updateCartCount();
// 检查是否需要更新“全选”框的状态
const allCheckboxes = document.querySelectorAll('.item-checkbox');
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
selectAllCheckbox.checked = allCheckboxes.length === checkedCheckboxes.length;
}
});
// 2. 全选/取消全选
selectAllCheckbox.addEventListener('change', () => {
const itemCheckboxes = document.querySelectorAll('.item-checkbox');
itemCheckboxes.forEach(checkbox => {
checkbox.checked = selectAllCheckbox.checked;
});
updateTotalPrice();
updateCartCount();
});
// 3. 数量增减
cartItemsContainer.addEventListener('click', (e) => {
const btn = e.target.closest('.quantity-btn');
if (!btn) return;
const quantitySpan = btn.parentElement.querySelector('.quantity');
let quantity = parseInt(quantitySpan.textContent);
if (btn.classList.contains('plus')) {
quantity++;
} else if (btn.classList.contains('minus') && quantity > 1) {
quantity--;
}
quantitySpan.textContent = quantity;
updateTotalPrice(); // 数量改变,总价重新计算
});
// 4. 删除商品
cartItemsContainer.addEventListener('click', (e) => {
const deleteBtn = e.target.closest('.delete-btn');
if (!deleteBtn) return;
itemToDelete = deleteBtn.closest('.cart-item');
deleteModal.classList.remove('hidden');
});
// 5. 确认删除
confirmDeleteBtn.addEventListener('click', () => {
if (itemToDelete) {
itemToDelete.remove();
itemToDelete = null;
deleteModal.classList.add('hidden');
// 更新UI
updateTotalPrice();
updateCartCount();
toggleEmptyCart();
// 重新绑定全选逻辑
const allCheckboxes = document.querySelectorAll('.item-checkbox');
const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked');
selectAllCheckbox.checked = allCheckboxes.length > 0 && allCheckboxes.length === checkedCheckboxes.length;
}
});
// 6. 取消删除
cancelDeleteBtn.addEventListener('click', () => {
itemToDelete = null;
deleteModal.classList.add('hidden');
});
// 7. 结算按钮
checkoutBtn.addEventListener('click', () => {
const checkedCount = document.querySelectorAll('.item-checkbox:checked').length;
if (checkedCount === 0) {
alert('请至少选择一件商品');
return;
}
// 这里跳转到结算页面
alert(`正在结算 ${checkedCount} 件商品...`);
});
// --- 初始化 ---
updateTotalPrice();
updateCartCount();
toggleEmptyCart();
});
如何使用这个模板
- 创建文件: 创建三个文件:
index.html、cart.js和一个空的styles.css(如果不需要,可以省略)。 - 复制代码: 将上面的 HTML 代码粘贴到
index.html中,将 JavaScript 代码粘贴到cart.js中。 - 引入依赖: 确保你的 HTML 文件中通过 CDN 引入了 Tailwind CSS 和 Font Awesome。
- 运行: 直接在浏览器中打开
index.html文件即可看到效果,由于是静态页面,无需服务器。
扩展和自定义建议
- 数据源: 目前数据是硬编码的,在实际项目中,你需要通过 JavaScript 从服务器 API 获取购物车数据,然后动态渲染到页面上。
- 优惠券: 可以添加一个输入框和验证逻辑,在计算总价时减去优惠券金额。
- 运费: 可以添加一个运费计算逻辑,根据总价或商品数量来决定是否收取运费。
- 图片懒加载: 对于商品列表,可以使用
loading="lazy"属性来优化图片加载性能。 - 动画: 可以使用 CSS transitions 或 JavaScript 动画库(如 AOS)来添加删除、数量变化等微交互,提升用户体验。
这个模板为您提供了一个坚实的起点,您可以根据自己的业务需求进行快速修改和扩展。

(图片来源网络,侵删)
