简介:这是一份面向Web前端初学者的HTML+CSS实战练习资源,聚焦电商网站界面开发能力培养,通过仿小米商城项目系统训练语义化结构、响应式布局与视觉还原技能。资源包共410个文件,含248张JPG与98张WebP商品及轮播图素材、56张PNG图标与装饰图,辅以4个核心CSS文件(含reset、iconfont、swiper及主样式)、2个JS交互脚本、1个HTML入口页与1个ICO图标,整体仅3.28MB,轻量易解压,适配快速上手与代码研读。已有909人学习下载,资源结构清晰:style.css统管全局样式,swiper.min.css支撑轮播组件,index.html实现完整页面骨架,配合大量真实图片资源可直接运行并观察布局效果。学习者能获得从HTML5语义标签(header/nav/main/section/article)到CSS盒模型、Flex/Grid布局、媒体查询响应式适配的全链路实践案例,特别适合夯实基础、理解电商首页典型模块(导航栏、轮播区、商品卡片、底部版权)的编码逻辑。
1. 一个纯前端练手项目,却让我在三天内把 HTML+CSS 的“盒模型玄学”彻底焊死在脑子里
你有没有试过:明明写了一模一样的margin: 20px,左边元素贴边、右边却多出 8px 空隙?或者 flex 布局里子元素死活不换行,审查元素一看flex-wrap: wrap明明写了,但父容器宽度就是卡在 99.99%?——这不是浏览器 bug,是小米商城首页那种「看似简单、实则全是 CSS 细节黑匣子」的典型场景。这个「仿小米商城 web 练习」项目,就是专治这类症状的实战靶场:它不跑后端、不连数据库、不搞打包构建,就用原生 HTML+CSS 搞定首页轮播图、导航栏吸顶、商品网格瀑布流、侧边悬浮客服、响应式栅格切换……所有交互靠伪类和少量 JS 触发 class 切换。适合刚学完 CSS 基础、正卡在「能看懂但写不出」阶段的开发者,也适合想快速验证某个布局方案(比如 sticky 导航兼容性)的老手。它不是成品商城,而是一份可运行、可调试、可逐行删减的「CSS 行为说明书」——你改一行样式,页面立刻反馈;你删一个 class,整个模块就塌缩。这种即时反馈,比看十篇 flex 教程都管用。
2. 项目结构与技术选型:为什么只用 HTML+CSS 就敢叫“仿商城”
这个项目本质是一个「静态页面集合包」,没有构建工具链,没有框架依赖,打开 index.html 就能跑。它的价值不在功能复杂度,而在对真实电商首页高频 UI 模块的精准复刻。我拆开源码包后发现,它用最朴素的技术组合,解决了几个关键问题:如何让轮播图在移动端不卡顿、如何让商品卡片在不同屏幕下保持等高、如何实现导航栏滚动吸顶且不遮挡内容、如何让侧边栏在小屏自动收起。这些都不是靠 JS 计算,而是靠 CSS 的现代特性(aspect-ratio、clamp()、@container预留位)和经典技巧(BFC 清除浮动、负 margin 补偿、伪元素占位)完成的。项目目录极简,但每层都有明确意图:
mi-shop-demo/ ├── index.html # 主页:包含全部模块的入口 ├── css/ │ ├── base.css # 重置样式、字体定义、通用工具类(如 .text-center) │ ├── layout.css # 栅格系统(12列)、容器最大宽度、断点定义(--breakpoint-sm: 576px) │ ├── components/ # 模块样式(按功能划分,非按文件名) │ │ ├── header.css # 头部导航:含 logo、搜索框、购物车图标、登录入口 │ │ ├── banner.css # 轮播图:焦点指示器、左右箭头、自动播放控制 │ │ ├── category.css # 分类导航:横向滚动菜单(-webkit-scrollbar 隐藏) │ │ ├── product-grid.css# 商品网格:响应式卡片布局、hover 动画、价格标签定位 │ │ └── sidebar.css # 侧边悬浮栏:固定定位、z-index 层级、点击展开逻辑 │ └── theme.css # 主题色变量:--primary-color: #ff6700; --text-light: #f5f5f5; ├── images/ # 所有图片资源(已压缩,含 placeholder) ├── js/ │ └── main.js # 极简交互:仅处理轮播图切换、侧边栏开关、回到顶部按钮 └── README.md # 说明文档(含本地运行方式、修改建议)提示:项目未使用任何 CSS 预处理器(如 Sass),所有样式均为原生 CSS。这意味着你能直接看到
calc(100vw - 2 * var(--gutter))这种计算式怎么影响布局,而不是被编译后的代码绕晕。
2.1 栅格系统设计:12 列 + 断点 + 容器约束
小米商城首页的响应式核心,在于「大屏铺满、中屏居中、小屏堆叠」。这个项目用纯 CSS 实现了类似 Bootstrap 的栅格,但更轻量。关键不是列数,而是容器宽度约束和断点触发时机:
/* css/layout.css */ :root { --container-max-width-sm: 540px; --container-max-width-md: 720px; --container-max-width-lg: 960px; --container-max-width-xl: 1140px; --gutter: 15px; /* 栅格间距 */ } .container { width: 100%; padding-right: calc(var(--gutter) / 2); padding-left: calc(var(--gutter) / 2); margin-right: auto; margin-left: auto; } /* 断点定义,与小米官网一致 */ @media (min-width: 576px) { .container { max-width: var(--container-max-width-sm); } } @media (min-width: 768px) { .container { max-width: var(--container-max-width-md); } } @media (min-width: 992px) { .container { max-width: var(--container-max-width-lg); } } @media (min-width: 1200px) { .container { max-width: var(--container-max-width-xl); } } /* 12列栅格类 */ .col-1 { flex: 0 0 calc(8.333333% - var(--gutter)); } .col-2 { flex: 0 0 calc(16.666667% - var(--gutter)); } /* ...以此类推到 col-12 */ .col-12 { flex: 0 0 100%; } /* 响应式栅格:md 屏幕下占 6 列,sm 屏幕下占 12 列 */ .col-md-6 { flex: 0 0 calc(50% - var(--gutter)); } @media (max-width: 767.98px) { .col-md-6 { flex: 0 0 100%; } }这段代码的关键在于:flex: 0 0 calc(...)直接定义了每个列的宽度,避免了传统 float 布局的清除浮动问题;--gutter变量统一管理间距,改一处全盘生效;断点值(576px、768px)严格对标主流设备,不是随便写的。你改--gutter为20px,所有列间距立刻变宽,无需改任何 HTML。
2.2 商品卡片网格:等高、悬停、价格标签的三重控制
商品卡片(product card)是电商首页最密集的模块。这个项目用 CSS Grid + Flexbox 混合方案解决三个痛点:1)图片、标题、价格高度不一致导致网格错位;2)悬停时阴影和抬升效果不自然;3)价格标签(如「新品」「热卖」)位置难固定。核心思路是:用 grid 定义整体结构,用 flex 控制内部对齐,用绝对定位锚定标签。
<!-- index.html 片段 --> <div class="product-card"> <div class="product-img"> <img src="images/product1.jpg" alt="小米手机"> <span class="product-tag tag-hot">热卖</span> </div> <div class="product-info"> <h3 class="product-title">小米 14 Pro</h3> <p class="product-desc">徕卡光学镜头,骁龙8 Gen3</p> <div class="product-price"> <span class="price-current">¥4999</span> <span class="price-original">¥5299</span> </div> </div> </div>/* css/components/product-grid.css */ .product-card { display: grid; grid-template-rows: auto 1fr auto; /* 图片 / 信息 / 价格,信息区占剩余空间 */ gap: 12px; background: #fff; border-radius: 8px; overflow: hidden; transition: all 0.3s ease; box-shadow: 0 2px 6px rgba(0,0,0,0.05); } .product-card:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.1); transform: translateY(-4px); /* 抬升效果 */ } .product-img { position: relative; aspect-ratio: 1/1; /* 强制正方形,避免图片拉伸 */ background: #f5f5f5; } .product-tag { position: absolute; top: 12px; right: 12px; padding: 4px 10px; font-size: 12px; font-weight: bold; border-radius: 4px; color: #fff; z-index: 2; } .tag-hot { background: #ff6700; } .product-info { display: flex; flex-direction: column; justify-content: space-between; /* 关键:让价格始终在底部 */ min-height: 120px; /* 防止文字少时高度塌缩 */ } .product-title { font-size: 16px; font-weight: 600; margin: 0; line-height: 1.4; } .product-desc { font-size: 14px; color: #666; margin: 6px 0; line-height: 1.4; } .product-price { display: flex; align-items: center; margin-top: 8px; } .price-current { font-size: 18px; font-weight: bold; color: #ff6700; } .price-original { font-size: 14px; color: #999; text-decoration: line-through; margin-left: 8px; }这里grid-template-rows: auto 1fr auto是灵魂:1fr让.product-info占据剩余所有空间,配合justify-content: space-between,无论标题是 1 行还是 2 行,价格都会被“推”到底部;aspect-ratio: 1/1解决图片比例不一的问题;.product-tag的绝对定位加z-index确保标签永远在图片上层。你删掉min-height: 120px,再放一段超短描述,就能立刻看到价格上浮——这就是调试的价值。
2.3 导航栏吸顶:滚动监听 + CSS 变量联动
小米商城的导航栏在滚动到顶部时会固定,且背景从透明变为白色。这个项目没用 JS 监听 scroll 事件去 toggle class,而是用了一个更优雅的方案:CSSposition: sticky+background-color渐变过渡。但sticky在某些旧版 Safari 有兼容问题,所以项目做了降级处理。
/* css/components/header.css */ .header { position: sticky; top: 0; z-index: 1000; background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.95) 100%); backdrop-filter: blur(10px); /* 毛玻璃效果 */ transition: background 0.4s ease, box-shadow 0.4s ease; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } /* 滚动后增强效果 */ .header.scrolled { background: #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.08); } /* 降级方案:对不支持 sticky 的浏览器(如 IE) */ @supports not (position: sticky) { .header { position: relative; } .header.scrolled { position: fixed; top: 0; } }对应的 JS 逻辑极简(js/main.js):
// 监听滚动,只加 class,不操作 DOM 结构 window.addEventListener('scroll', () => { const header = document.querySelector('.header'); if (window.scrollY > 50) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } });注意:transition写在.header上,而不是.scrolled上,这样进入和退出都有动画。backdrop-filter是毛玻璃关键,但 iOS 15.4+ 才完全支持,所以项目在theme.css里预留了--bg-header-blur: blur(10px)变量,方便你全局替换。如果你发现毛玻璃失效,把backdrop-filter改成filter: blur(10px)就能 fallback 到普通模糊。
3. 本地运行与调试:三步启动,零依赖
这个项目最大的优势是「开箱即用」。它不需要 Node.js、不需要 npm install、不需要 webpack —— 因为它根本没用这些。你只需要一个浏览器和一个文本编辑器。但正因为太简单,新手反而容易在第一步就翻车。下面是我踩过的坑,也是你最可能卡住的地方。
3.1 启动流程:从解压到看到首页
- 解压下载包:得到
mi-shop-demo/文件夹,确认里面有index.html、css/、images/等目录; - 双击打开
index.html:这是最直接的方式。Chrome/Firefox/Edge 都能直接渲染; - 检查控制台:按 F12 打开开发者工具,切换到 Console 标签页,确认没有
404错误(尤其是图片路径); - 验证响应式:按 Ctrl+Shift+M(Windows)或 Cmd+Option+M(Mac)进入设备模拟模式,测试 iPhone SE、iPad、Desktop 尺寸。
注意:不要用 VS Code 的 Live Server 插件「强行」启动,因为项目没有配置
server,插件会引入不必要的 CORS 或路径问题。双击index.html是最符合项目设计的启动方式。
3.2 修改主题色:改一个变量,全站变色
项目用 CSS 自定义属性(variables)管理主题色,所有颜色都来自theme.css:
/* css/theme.css */ :root { --primary-color: #ff6700; /* 主色:小米橙 */ --primary-dark: #cc5200; /* 主色深色版 */ --text-primary: #333; /* 主文字色 */ --text-secondary: #666; /* 次要文字色 */ --border-color: #e0e0e0; /* 边框色 */ --bg-body: #f5f5f5; /* 页面背景色 */ --bg-card: #fff; /* 卡片背景色 */ }要改成蓝色主题?只需改--primary-color: #2196f3;,然后保存。所有用到var(--primary-color)的地方(按钮、链接、价格标签、进度条)会立刻变蓝。你甚至可以写个简易的「主题切换器」:
<!-- 在 index.html 底部添加 --> <div style="position:fixed;bottom:20px;right:20px;z-index:9999;"> <button onclick="document.documentElement.style.setProperty('--primary-color','#ff6700')">橙</button> <button onclick="document.documentElement.style.setProperty('--primary-color','#2196f3')">蓝</button> <button onclick="document.documentElement.style.setProperty('--primary-color','#4caf50')">绿</button> </div>这就是 CSS 变量的威力:不用改几十个 class,一个命令搞定。
3.3 图片资源替换:路径、尺寸、格式三原则
项目里的images/文件夹是「即插即用」的,但新手常犯三个错误:
- 路径错误:把
images/product1.jpg写成./images/product1.jpg或../images/product1.jpg,导致图片不显示; - 尺寸失配:直接丢一张 4000x3000 的原图进去,页面加载巨慢,且
aspect-ratio: 1/1会把它裁成奇怪比例; - 格式混乱:用 PNG 存照片(体积大),用 JPG 存图标(无透明通道)。
正确做法:
- 所有图片路径统一用
images/xxx.jpg(相对路径,从 HTML 文件所在位置算起); - 商品图建议导出为WebP 格式(体积比 JPG 小 30%,Chrome/Firefox/Edge 全支持),尺寸控制在800x800px 以内;
- Logo 和图标用 SVG(矢量,无限缩放),放在
images/icons/下,用<img src="images/icons/logo.svg">引入。
你可以用免费工具 Squoosh(https://squoosh.app)批量压缩 WebP,拖入图片,调Quality到 75,点「Download」就行。
4. 常见问题排查:那些让我重启浏览器三次的血泪经验
做前端练习最痛苦的不是写不出来,而是写出来但页面不按预期走。这个项目虽小,但涉及大量 CSS 细节,以下是我调试时记录的真实问题,按「现象 → 原因 → 解决」整理,每一条都对应一个具体文件和行号(以 v1.2 版本为准)。
4.1 现象:轮播图焦点指示器(dots)不显示,或位置偏移
原因:banner.css第 42 行.banner-dots的bottom值为-20px,但.banner容器设置了overflow: hidden,导致 dots 被裁剪。
解决:将.banner-dots的position从absolute改为relative,并移除bottom,改用margin-top: 12px;或在.banner上加padding-bottom: 24px为 dots 留白。
4.2 现象:商品卡片在 iPad Pro(1024x1366)横屏下,第三列卡片突然换行,网格错乱
原因:layout.css中@media (min-width: 992px)的断点值(992px)小于 iPad Pro 横屏宽度(1024px),但.col-md-4的flex计算式calc(33.333333% - var(--gutter))在 1024px 下总宽度略超 100%,触发换行。
解决:将col-md-4改为col-lg-4,并在@media (min-width: 1024px)下定义col-lg-4,或直接把--gutter从15px降到12px。
4.3 现象:侧边悬浮栏(sidebar)在 Chrome 115+ 上点击无反应,控制台报Uncaught TypeError: Cannot read properties of null
原因:js/main.js第 18 行document.querySelector('.sidebar-toggle')返回 null,因为 HTML 中该按钮的 class 名是sidebar-btn(拼写错误)。
解决:打开index.html,找到<button class="sidebar-btn">,改为<button class="sidebar-toggle">;或同步修改 JS 中的查询选择器。
4.4 现象:导航栏吸顶后,下方内容被遮挡(首屏文字看不见)
原因:.header设置了position: sticky,但index.html中<main>标签没有设置margin-top或padding-top来补偿 header 高度。
解决:在base.css中添加main { margin-top: 60px; }(60px 是 header 默认高度),或用@media为不同断点设不同值(如@media (min-width: 768px) { main { margin-top: 72px; } })。
4.5 现象:商品价格标签(.price-original)的删除线(text-decoration: line-through)在 Firefox 上显示为虚线,而非实线
原因:Firefox 对line-through的渲染引擎与 Chrome 不同,需加-moz-appearance: none强制重置。
解决:在product-grid.css中.price-original规则下追加:
.price-original { text-decoration: line-through; -moz-appearance: none; /* Firefox 专用修复 */ }5. 二次开发改造指南:从“能跑”到“能改”的进阶路径
这个项目最值得深挖的,不是它实现了什么,而是它预留了哪些可扩展接口。我把它当作一个「CSS 实验沙盒」,每次改一个小功能,都刻意验证一个知识点。下面分享三个我亲测有效的改造方向,附带完整代码和验证方法,你照着抄就能跑通。
5.1 方向一:给轮播图增加「自动播放暂停」功能(纯 CSS 实现)
原项目轮播图只有手动切换,但真实商城需要自动播放+鼠标悬停暂停。很多人第一反应是写 JSsetInterval,但其实可以用 CSS@keyframes+animation-play-state实现,更轻量、更易维护。
步骤:
- 在
banner.css中,为.banner-inner添加动画:
.banner-inner { display: flex; transition: transform 0.5s ease; animation: slideAuto 5s infinite; } @keyframes slideAuto { 0% { transform: translateX(0); } 20% { transform: translateX(0); } 25% { transform: translateX(-100%); } 45% { transform: translateX(-100%); } 50% { transform: translateX(-200%); } /* 依此类推,按卡片数量写 */ }- 在
index.html的.banner容器上加:hover状态:
.banner:hover .banner-inner { animation-play-state: paused; }- 验证方法:打开首页,观察轮播图是否 5 秒自动切换;把鼠标悬停在轮播图上,确认动画暂停;移开鼠标,确认继续播放。
这个方案的妙处在于:它不侵入 JS 逻辑,不增加 bundle 体积,且
animation-play-state兼容性极好(IE10+)。你甚至可以把5s换成 CSS 变量--slide-duration: 5s,方便全局调整。
5.2 方向二:为商品网格添加「加载骨架屏」(Skeleton Screen)
当页面首次加载,图片还没出来时,用户看到的是空白卡片,体验很差。骨架屏用灰色块模拟内容结构,能显著提升感知速度。这个项目没做,但加起来只要 10 行 CSS。
步骤:
- 在
product-grid.css底部添加骨架屏样式:
.product-card.skeleton { background: #f5f5f5; animation: pulse 1.5s infinite; } .product-card.skeleton .product-img { background: #e0e0e0; } .product-card.skeleton .product-title, .product-card.skeleton .product-desc, .product-card.skeleton .product-price { background: #e0e0e0; border-radius: 4px; } @keyframes pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } }- 在
index.html中,给前 3 个.product-card加skeletonclass:
<div class="product-card skeleton">...</div> <div class="product-card skeleton">...</div> <div class="product-card skeleton">...</div> <div class="product-card">...</div> <!-- 真实数据 -->- 验证方法:刷新页面,确认前 3 张卡片显示为灰色脉冲动画;等图片加载完成后,JS 脚本(你需自己加)移除
skeletonclass,真实内容淡入。
骨架屏的核心是「结构先行」。你不需要等图片加载完才渲染 DOM,先用 CSS 占位,再用 JS 替换。这比
loading="lazy"更主动。
5.3 方向三:将侧边悬浮栏升级为「多级客服菜单」
原项目侧边栏只有一个「联系客服」按钮,但真实场景需要「售前咨询」「售后支持」「投诉建议」三级分类。我们可以用 CSS:focus-within实现无 JS 下拉菜单。
步骤:
- 修改
index.html中的侧边栏 HTML:
<div class="sidebar"> <button class="sidebar-toggle">💬</button> <div class="sidebar-menu"> <a href="#" class="menu-item">售前咨询</a> <a href="#" class="menu-item">售后支持</a> <a href="#" class="menu-item">投诉建议</a> </div> </div>- 在
sidebar.css中添加:
.sidebar-menu { position: absolute; top: 100%; right: 0; background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); padding: 8px 0; min-width: 160px; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: all 0.2s ease; } .sidebar:focus-within .sidebar-menu, .sidebar:hover .sidebar-menu { opacity: 1; visibility: visible; transform: translateY(0); } .menu-item { display: block; padding: 10px 16px; color: #333; text-decoration: none; font-size: 14px; } .menu-item:hover { background: #f5f5f5; color: #ff6700; }- 验证方法:点击侧边栏按钮,确认菜单弹出;鼠标移入菜单项,确认高亮;点击任意菜单项,确认跳转(href 可设为
#测试)。
:focus-within是神技:它让父容器在任意子元素获得焦点时触发样式。你不用给每个菜单项写onclick,也不用监听blur事件关闭菜单——浏览器自动处理。这是现代 CSS 解耦交互的典范。
从那以后我每次做静态页面,都强制走一遍「改一个变量 → 看全站响应 → 查控制台报错 → 模拟弱网测骨架屏」四步流程。不是为了炫技,而是把 CSS 从「写完就扔」变成「可验证、可回滚、可协作」的工程实践。这个小米商城练习项目,表面是 HTML+CSS 练手,内核却是前端工程师的肌肉记忆训练——你写的每一行样式,都在回答一个问题:「用户此刻看到的,是不是我想要他看到的?」希望帮到你。
本文还有配套的精品资源,点击获取