简介:一份用于前端基础练习的小米官网首页静态页面资源,面向初学HTML与CSS的开发者,通过复刻真实电商首页来巩固网页结构、选择器、盒模型、Flexbox/Grid布局、媒体查询、伪类与字体图标等核心技能。资源共38个文件,压缩包约2.64MB,以HTML页面、CSS样式表和图片素材为主,另有字体文件与图标,方便直接打开预览和修改。已有667人学习,适合作为第一份完整的前端练手项目,也适用于自学或教学案例。内容包含完整的首页HTML结构与按重置、基础、页面等模块拆分的CSS样式,并配有所需banner图、商品图、图标与字体资源,能帮助学习者对照真实页面理解样式分层与命名规范,逐步模仿小米官网的导航、轮播区、商品陈列和底部布局。通过动手修改样式和布局,可以快速掌握响应式设计与浏览器兼容性处理思路。额外附有演示页面与站点图标,目录结构简洁清晰,方便快速定位文件、对照源码调整,提升实际编码能力。
1. 小米官网首页的静态页面:为什么前端新人都在复刻它
你可能觉得“静态页面”不就是写完 HTML、CSS 放浏览器里能打开吗,有什么好讲的?但如果你真把小米官网首页截图摆在旁边,一行一行去像素级还原,你会发现这活儿一点都不轻松:顶部的二级菜单、滚轮上的轮播图、商品卡片在鼠标悬停时的阴影变化、底部一堆链接对小屏的折叠逻辑,随便哪个环节没处理好,页面就会肉眼可见地“散架”。这个标题的实质,是用纯前端技术复刻一个真实商业网站的首页,不碰后端接口,不碰数据库,所有内容和交互都在浏览器里完成。
它适合两类人:一类是把“小米官网首页(静态页面)”当练习作品的前端初学者,做完能拿出来给面试官看;另一类是已经在写页面,但总觉得自己对布局、响应式、浏览器兼容性心里没底,想通过一个完整项目把这块补齐。你需要掌握的东西很明确:HTML 结构设计、CSS 布局与动画、原生 JavaScript 的轮播与交互,以及一套能应对不同屏幕的响应式策略。这文章我就按自己做这个项目的顺序来讲,从拆解页面到写成代码,最后把最容易踩的坑一个一个列给你。
2. 动手前的技术准备:把小米首页拆成可复现的模块
2.1 静态页面到底指什么:区别于动态渲染的边界
做静态页面之前,先得把“静态”两个字界定清楚。静态页面不是指页面不能动,而是指没有服务端参与:数据不来自接口,页面文件直接存在于本地或静态服务器上,访问时拿到的是原封不动的 HTML、CSS、JavaScript。
小米官网首页实际上是一个动态渲染的页面,有大量商品数据来自接口,但你要做的这个“静态页面”版本,是用写死的数据和图片去模拟它的外观和交互。这带来的好处是逻辑简单,不需要考虑鉴权、接口超时、数据序列化;坏处是如果图片资源过大、路径写错,整个页面会显得很假。所以复刻时我会把图片资源放在本地 images 目录里,用相对路径引用。
一个常见误区是认为静态页面不需要工程化。实际上做这种还原练习时,我建议还是用一个最简单的目录结构,方便后期维护。下面是我常用的文件布局:
| 文件/目录 | 作用 |
|---|---|
| index.html | 页面唯一入口,包含完整结构和引用的 css/js |
| css/main.css | 所有样式,按模块注释分块 |
| js/main.js | 轮播、菜单、返回顶部等交互逻辑 |
| images/ | 本地图片资源,按模块命名如 banner-1.jpg |
没有构建工具,没有框架,纯手写。这样你才能把每个细节吃透。如果一开始就上 Vue 或 React 组件化,反而看不清布局和样式是怎么互相作用的。
2.2 我用的工具与文件组织方式
在动手写代码之前,我会先做三件事:用浏览器打开小米官网首页,按 F12 打开开发者工具;把页面从头到尾截一张长图;把顶部栏、轮播图、商品分区、底部栏这几块单独截图存成参考图。
代码方面我只用两个工具:VS Code 做编辑器,Chrome 做调试。没有装插件,因为静态页面不需要热更新,改完代码手动刷新浏览器就够了。不过如果你想让调试方便一点,VS Code 里装一个 Live Server 插件也可以,它的作用仅是起一个本地静态服务器,让页面通过 http 协议访问,从而避免 file 协议下某些浏览器限制。
文件组织上,我坚持一个原则:每个模块一块样式注释块。比如在 main.css 里,我会写/* ===== header ===== */这样的注释,然后再写这个模块的样式。这样当你写完 1000 行 CSS 后,找某个区块依然很快。
创建一个基础页面骨架时,我会这样写:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>小米官网首页静态复刻</title> <link rel="stylesheet" href="css/main.css"> </head> <body> <!-- 页面主体 --> <script src="js/main.js"></script> </body> </html>逻辑说明:meta charset="UTF-8"保证中文不乱码;meta name="viewport"是响应式的基础,因为后面要用媒体查询适配小屏,如果不写这个,手机浏览器会按 980px 宽度渲染,你的媒体查询就失效了。样式放在 head 里优先加载,脚本放在 body 末尾,避免阻塞渲染。
参数说明:如果你想让页面在电脑端和手机端都正常,width=device-width, initial-scale=1.0是标配,不要省略。后面拆轮播图时,JavaScript 也是在这个 main.js 里面写。
2.3 复制像素级页面:从测量到写码的工作流
小米官网这种大厂页面,设计风格偏简洁,间距和字体大小非常有规律。复刻的关键不是“看个大概”,而是测量出具体数值。我一般这样操作:在 Chrome 的开发者工具里,选中某个元素,看它的padding、margin、font-size、border-radius,直接记下来。如果用截图对比,也可以用截图软件的取色器获取背景色。
我的工作流是:先把页面从上到下切成几个大区块,每个区块放进一个div,再给每个div一个 class 名。然后逐块写 CSS,先从顶部导航开始,接着轮播图,再做商品区,最后是底部。
比如小米的顶部导航背景是白色,高度约 60px,内容区宽度是 1226px,左右居中。我会这样实现:
/* 顶部导航栏 */ .topbar { width: 100%; height: 60px; background: #fff; border-bottom: 1px solid #e0e0e0; } .topbar-inner { width: 1226px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; }逻辑说明:外层topbar占满全宽,让背景色横向铺开;内层topbar-inner固定 1226px 宽度并水平居中,让内容在宽屏下不散得太开。display: flex配合justify-content: space-between可以把左侧 logo 和右侧菜单分别推到两端。这个“内外两层”写法是小米官网这类居中布局页面的通用套路,你会反复用到。
参数说明:1226px 是小米官网内容区的标准宽度,参考自原站。如果你复刻的页面不同,就用你自己测量到的数值替换即可。margin: 0 auto是让固定宽度块居中最常用的方式,没有之一。
3. 按模块还原小米首页:布局、样式与交互的落地步骤
3.1 顶部导航栏:先用浮动还是 Flexbox
顶部导航是访客第一眼看到的内容,也是你写页面时建立的第一个布局基础。很多教程还在用float: left/right做导航,但这在垂直方向上极易遇到高度塌陷。我建议直接用 Flexbox,理由有两点:一是代码更短,二是不需要额外清除浮动。
做个具体例子:左边是“小米商城”Logo,右边是“首页、手机、电视、智能家居”等链接。我会这样写:
<nav class="topbar-inner"> <a class="logo" href="#">小米商城</a> <ul class="nav-list"> <li><a href="#">首页</a></li> <li><a href="#">手机</a></li> <li><a href="#">电视</a></li> </ul> </nav>.logo { font-size: 20px; font-weight: bold; color: #ff6700; text-decoration: none; } .nav-list { display: flex; list-style: none; gap: 20px; } .nav-list a { color: #333; text-decoration: none; font-size: 14px; } .nav-list a:hover { color: #ff6700; }逻辑说明:display: flex作用于ul上,让li水平排列;gap: 20px设置每个链接之间的间距,不需要再给每个li写margin-right。list-style: none去掉默认圆点。链接的悬停颜色用:hover伪类实现,与小米官网的橙色品牌色一致。
参数说明:这里的#ff6700是小米橙,用这个颜色能让还原度更高。text-decoration: none去掉下划线是导航链接必须做的,否则会显得很业余。如果你未来要适配移动端,这个gap在窄屏时可以改写为 10px。
3.2 轮播图区域:静态定位与自动播放的 JS 实现
小米首页顶部下方是一个大图轮播,通常每隔几秒切换一张。静态页面的轮播有两种做法:纯 CSS 轮播动画和 JavaScript 控制位移。为了可维护,我选择用 JavaScript 控制一个滑动容器。
HTML 结构大概是:
<div class="banner"> <div class="banner-track"> <div class="slide" style="background-image: url('images/banner-1.jpg');"></div> <div class="slide" style="background-image: url('images/banner-2.jpg');"></div> <div class="slide" style="background-image: url('images/banner-3.jpg');"></div> </div> <button class="prev">‹</button> <button class="next">›</button> </div>CSS 核心是让banner-track横向排列,并通过transform: translateX()来移动它:
.banner { position: relative; width: 1226px; height: 460px; margin: 0 auto; overflow: hidden; } .banner-track { display: flex; height: 100%; transition: transform 0.4s ease; transform: translateX(0); } .slide { min-width: 100%; height: 100%; background-size: cover; background-position: center; }逻辑说明:.banner-track的子元素.slide都设min-width: 100%,因此每个子元素宽度等于容器宽度,它们会排成一行,容器通过overflow: hidden只露出第一张。切换时只需要计算一个currentIndex变量,然后修改translateX(-currentIndex * 100%)即可。配合transition: transform 0.4s ease,图片滑动就有了顺滑过渡。
JavaScript 自动播放部分:
const track = document.querySelector('.banner-track'); const slides = track.querySelectorAll('.slide'); const prevBtn = document.querySelector('.prev'); const nextBtn = document.querySelector('.next'); let currentIndex = 0; let timer; function goTo(index) { if (index < 0) index = slides.length - 1; if (index >= slides.length) index = 0; currentIndex = index; track.style.transform = 'translateX(-' + (currentIndex * 100) + '%)'; } function nextSlide() { goTo(currentIndex + 1); } function startAutoPlay() { timer = setInterval(nextSlide, 3000); } function stopAutoPlay() { clearInterval(timer); } prevBtn.addEventListener('click', () => { goTo(currentIndex - 1); }); nextBtn.addEventListener('click', nextSlide); track.addEventListener('mouseenter', stopAutoPlay); track.addEventListener('mouseleave', startAutoPlay); startAutoPlay();逻辑说明:goTo函数是核心,它根据索引计算偏移量。负数索引会从末尾接上,超出索引会回到第一张,形成一个首尾循环。setInterval每 3 秒调用nextSlide实现自动播放。鼠标悬停时停止,移开后重启,这是电商首页常见的交互细节。
参数说明:3000是自动播放间隔,单位毫秒。你想调整轮播速度就改它。0.4s的 transition 是时长感觉比较舒服的值,太短会跳,太长会拖沓。如果你发现按钮点击后轮播跳了两次,多半是两个事件都触发了,注意看监听器是否重复绑定。
3.3 商品卡片网格:用 Grid 做出响应式排列
小米首页的商品区是一排排卡片,每张卡片包含图片、标题、价格。这类整齐网格很适合用 CSS Grid 来做,因为你能直接控制列数和间距。
我习惯把商品区写成:
<div class="product-grid"> <a href="#" class="product-card"> <img src="images/xiaomi-14.jpg" alt="小米手机"> <h4>小米手机</h4> <p>¥3999</p> </a> <!-- 更多卡片 --> </div>对应样式:
.product-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; width: 1226px; margin: 30px auto; } .product-card { background: #fff; padding: 20px; text-align: center; border-radius: 8px; transition: box-shadow 0.2s; } .product-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); } .product-card img { max-width: 100%; height: auto; }逻辑说明:grid-template-columns: repeat(4, 1fr)表示每行 4 列,每列等分可用宽度。gap: 15px同时控制列距和行距。这样你不需要操心计算百分比宽度,Grid 会自动完成分配。鼠标悬停阴影是本页还原的重点细节,配合transition让阴影渐变出现,而不是突然跳出来。
参数说明:1fr是 Grid 单位,代表剩余空间中的一份。改成repeat(3, 1fr)就变成 3 列,适合放到平板宽度。商品卡片的圆角我用 8px,与原站接近,你也可以用 6px 或 10px 做微调。这里的图片没有固定高度,所以不会发生裁切,但如果你的卡片图片尺寸不统一,布局就会参差,后面避坑会提到。
3.4 底部与侧边栏:绝对定位和 z-index 的经典用法
小米首页底部有一个区域,左边是商品分类的侧边菜单,右边是内容主体。侧边菜单在鼠标悬停时会弹出二级面板,这里需要用到绝对定位和z-index。
侧边菜单的结构:
<div class="category-area"> <ul class="category-menu"> <li> <a href="#">手机</a> <div class="submenu"> <a href="#">小米13</a> <a href="#">小米14</a> </div> </li> </ul> <div class="category-content">主体内容</div> </div>CSS 部分:
.category-area { position: relative; } .submenu { position: absolute; left: 100%; top: 0; width: 300px; background: #fff; border: 1px solid #e0e0e0; display: none; z-index: 100; } .category-menu li:hover .submenu { display: block; }逻辑说明:position: absolute让二级菜单脱离文档流,以最近的有position: relative的祖先为参照物。这里left: 100%把它放到菜单右侧。display: none默认隐藏,鼠标悬停时改为block。z-index: 100保证它在内容之上,不会被后面的元素盖住。
参数说明:width: 300px是二级菜单宽度,你可以按内容多少调整。如果你发现二级菜单被父元素裁掉,检查父元素的overflow是否设成了hidden。z-index不是越大越好,只要比同级其他元素大就行,一般用 100 足够了。
底部导航栏的样式与顶部类似,但更简单:一排链接用小字号和灰色。这里略过,后面避坑重点讲一下为什么底部链接在小屏上容易挤成一团。
4. 让静态页面在不同屏幕下不翻车:响应式与兼容性
4.1 断点设计:参考小米常见的屏幕宽度
静态页面做完后最怕的一件事,是把浏览器窗口拖小一看,整个布局挤得没法看。响应式的核心是媒体查询。根据小米官网常见布局,我会设置三个断点:1200px 以上为桌面,768px 到 1200px 为平板,768px 以下为手机。注意内容区的宽度也要跟着调整。
/* 默认样式:桌面端 */ .product-grid { grid-template-columns: repeat(4, 1fr); } /* 平板 */ @media (max-width: 1200px) { .product-grid { grid-template-columns: repeat(3, 1fr); } .topbar-inner, .banner, .product-grid { width: 100%; padding: 0 15px; } } /* 手机 */ @media (max-width: 768px) { .product-grid { grid-template-columns: repeat(2, 1fr); } .topbar-nav-list { display: none; /* 隐藏菜单,用汉堡按钮代替 */ } }逻辑说明:由于默认样式写的是桌面端,手机端的媒体查询只需覆盖需要变化的属性。这里把.topbar-inner这些固定 1226px 的容器改成width: 100%并加padding,防止内容贴屏幕边。商品卡片的列数从 4 降到 3 再降到 2,是大多数电商页面在小屏上的处理方式。
参数说明:max-width: 1200px表示当视口宽度小于等于 1200px 时应用其中样式。建议不要写死太多类似 999px 这样的断点,否则每次微调都会很痛苦。如果你做的是电商首页,记住:手机端 2 列卡片通常比 1 列更符合用户密度预期,前提是图片足够清晰。
4.2 图片与字体:rem/vw 和媒体查询怎么配合
静态页面里图片是最容易让页面变重的元凶。小米官网的轮播图win size在 1226x460 像素左右,如果直接用这张大图,手机端加载会很慢。常见做法是给img设置max-width: 100%,让图片在容器变窄时按比例缩放。
我一般还会准备两套图片,用srcset让浏览器自己选:
<img src="images/goods-300.jpg" srcset="images/goods-600.jpg 600w, images/goods-300.jpg 300w" sizes="(max-width: 768px) 300px, 600px" alt="商品图片">逻辑说明:srcset提供不同宽度的图片资源,sizes告诉浏览器在不同屏幕下图片的渲染宽度。浏览器会根据视口宽度和sizes计算并选择加载哪张图。这样可以避免手机端下载桌面大图,减少流量和加载时间。
字体方面,我建议根元素用px固定,配合媒体查询调整根字号,或者直接写rem。一个常用做法是:
html { font-size: 16px; } @media (max-width: 768px) { html { font-size: 14px; } } .title { font-size: 1.5rem; /* 等于24px */ }逻辑说明:rem是相对根元素的单位。当手机端根font-size变为 14px 时,所有用rem定义的字号都会等比缩小,比逐条改px省事。1.5rem在桌面是 24px,在手机是 21px,实现了整体缩放。
参数说明:srcset里的600w是图片真实宽度,不是物理像素。如果你不确定图片尺寸,可以不写这个属性,直接依靠max-width: 100%,但加载性能会差一些。
4.3 浏览器兼容性:必须测的三个场景
复刻静态页面时,我会至少在三种环境测试:最新 Chrome、Safari 手机端、微信内置浏览器。最常见的坑是 Flexbox 的gap在旧版 Safari 上不生效,以及 CSS Grid 的1fr在旧浏览器上的兼容问题。你的静态页面如果只是练习,不必追求所有远古浏览器都完美,但至少要保证不出现布局错乱。
一个保险做法是:关键模块不用 Grid,而是用 Flex 布局加justify-content: space-between,或者用百分比宽度。比如商品卡片可以写成:
.product-card { width: calc(25% - 15px); margin-bottom: 15px; }逻辑说明:calc(25% - 15px)减去gap的实际像素,模拟出四列效果。这种做法兼容性更广,缺点是计算麻烦。如果你使用 Grid,我建议在写完代码后到 caniuse.com 查一下grid-template-columns在你的目标浏览器版本的支持情况。一般说,2020 年后发布的浏览器都已支持 Grid,所以不必过度担心。
5. 静态页面避坑指南:我复刻小米首页时踩过的 7 个坑
5.1 图片路径失效:相对路径与斜杠的坑
现象:浏览器打开 index.html,图片区域是一片空白,F12 控制台提示 404。
原因:我最初把图片路径写成了/images/banner-1.jpg,这个以斜杠开头的路径是“服务器根目录”下的绝对路径。本地用 file 协议打开时,它会被解析成file:///images/banner-1.jpg,这当然找不到文件。哪怕用 Live Server,根目录也可能不是项目的images目录。
解决:统一使用相对路径。在index.html里引用图片时,写images/banner-1.jpg(相对于 index.html 所在目录)。在 CSS 里引用背景图片时,要相对于 CSS 文件所在目录,比如../images/banner-1.jpg。这个细节新手特别容易忽略。
5.2 轮播图定时器叠加:鼠标移入移出导致切换加速
现象:轮播图一开始正常,鼠标多移入移出几次后,图片切换速度明显变快,甚至几秒内连续跳多张。
原因:startAutoPlay每次被调用时都会创建一个新的setInterval,但旧的timer没有被清理。鼠标每次移出都会叠加一个定时器,所有定时器同时运行,轮播自然就加速了。
解决:在startAutoPlay开头先clearInterval已有的定时器,再创建新的:
function startAutoPlay() { clearInterval(timer); // 防止重复叠加 timer = setInterval(nextSlide, 3000); }这样每次重新启动都先取消旧的,保证同一时刻只有一个定时器在跑。代码逻辑看起来是小事,但实属轮播组件最容易踩的坑。
5.3 高度塌陷与 margin 折叠:浮动布局的连锁反应
现象:我最初把商品卡片用float: left实现,结果卡片容器高度变 0,底部背景色不见了,后面元素直接顶上来。
原因:浮动元素脱离文档流,父容器不再计算它们的高度,这就是“高度塌陷”。同时,如果相邻元素有上下 margin,它们会合并成较大值,也就是“margin 折叠”,这个也容易导致间距不对。
解决:不要用浮动布局做这种现代页面。使用 Flexbox 或 Grid 就没有塌陷问题。如果你必须用浮动(比如兼容老代码),可以在父容器上加overflow: hidden或者display: flow-root来清除浮动。
5.4 商品卡片图片尺寸不一致导致网格错位
现象:四张卡片,三张图片高度 300px,一张高度 350px,整行高度被撑高,后面卡片排列参差不齐。
原因:浏览器默认按图片原始高度渲染,Grid 行高由该行最高的元素决定。不同产品图片比例不一致,就会出现这个问题。
解决:给商品卡片的图片设置一个固定的高度或宽高比:
.product-card img { width: 100%; height: 200px; object-fit: cover; }object-fit: cover会让图片按比例裁剪,铺满固定区域,不会变形。200px可以换成你需要的任何值,但注意不同商品图片内容中心可能不同,裁剪后可能会切掉主体,需要微调。
5.5 底部链接在小屏上挤成一团
现象:手机端打开页面,底部一排链接全部堆在一起,文字重叠。
原因:底部链接通常是一个长条容器,里面用多个span或a排成一行,但没有做换行或响应式处理。窄屏时容器宽度不够,元素依然强制在一行内排列,就重叠了。
解决:在小屏媒体查询里给底部链接设置为可换行的 Flex 布局:
@media (max-width: 768px) { .footer-links { flex-wrap: wrap; justify-content: space-between; } .footer-links a { width: 50%; margin-bottom: 8px; } }flex-wrap: wrap允许子元素换行,再配合固定宽度,让链接变成两列排列。这个坑说明一个道理:响应式不只是改主容器,任何长条元素都可能在小屏上出问题。
6. 进阶:把静态首页变成可维护的模板,用 CSS 变量统一主题
当你把小米官网首页的静态版本复刻完,下一步不是急着做其他页面,而是回头重构一遍样式,让代码更容易维护。我最推荐做的一件事,是把颜色、圆角、间距这些重复出现的值收拢成 CSS 变量。比如:
:root { --primary-color: #ff6700; --text-main: #333; --text-light: #999; --gap-sm: 8px; --gap-md: 15px; --gap-lg: 30px; --radius-md: 8px; --shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.1); }然后在你写过的所有样式里替换成var(--primary-color)。这样做的好处是,如果将来想换成其他品牌色,只需要改:root这一个地方。所有按钮、链接、菜单悬停色都会同步变化。我复盘自己写第一版时的代码,由于把#ff6700散落写了十几个地方,后来调整起来只能全局替换,这就是“后悔药”没吃对。
另一个值得做的改进,是把 HTML 中重复的块抽成组件模板。静态页面虽然不能像 Vue 那样做单文件组件,但你可以把轮播图、商品卡片、底部导航分别写成一个独立的.html片段文件,然后用简单的脚本拼接。如果你打算把这个项目继续发展为模板,可以尝试把商品数据放到一个data.js文件里,通过 JavaScript 循环生成卡片:
const products = [ { name: '小米手机', price: 3999 }, { name: '小米电视', price: 1999 } ]; const grid = document.querySelector('.product-grid'); grid.innerHTML = products.map(item => ` <a href="#" class="product-card"> <img src="images/${item.img}" alt="${item.name}"> <h4>${item.name}</h4> <p>¥${item.price}</p> </a> `).join('');逻辑说明:用数组加模板字符串,让静态页面也具备一点“数据驱动”的能力。将来增加商品条目只需要改products,不用再复制 HTML。这样做的边界是:这仍然不算前后端分离,只是前端渲染。
最后一个建议是,把整个项目放到本地静态服务器上跑一遍,然后开着 Chrome 的 Network 面板看加载时间。如果某张图片超过 200KB,就换用压缩版本;如果 CSS 文件超过 100KB,看看是不是有大量重复代码。静态页面虽然简单,但对加载性能的约束和动态页面是一样的,用户不会因为它是静态页面就多等你一秒。
我自己的习惯是,每次写完一个静态页面,都强制自己做一遍删除代码重构:把用不到的 class 删掉,把重复的 CSS 合并。这样一来,这个复刻项目才不只是“能打开”,而是变成一份可以继续扩展的模板。如果你能做到这一步,那这个“小米官网首页(静态页面)”就算真正消化了。希望帮到你。
本文还有配套的精品资源,点击获取