1. 从零搭建一个在线教育平台首页:为什么我选择死磕这套布局
很多人第一次拿到“学成在线”这类在线教育平台的首页设计稿时,第一反应是打开编辑器直接开写。HTML 结构还没理清楚,CSS 已经敲了三百行,结果做到一半发现导航栏和轮播图打架,课程卡片在不同屏幕宽度下像喝醉了酒一样东倒西歪。我见过太多这样的案例,包括我自己早期也这么干过。后来我才明白,一个看起来“只是展示”的首页,背后涉及的布局逻辑、命名规范、响应式断点设计,远比想象中复杂。
“学成在线”这个项目,本质上是一个典型的在线教育平台前端页面。它要解决的核心问题是:如何把课程分类、推荐课程、讲师介绍、学习路径、用户评价这些模块,用一套清晰、可维护、可扩展的代码结构呈现出来。适合谁来参考?如果你是刚学完 HTML 和 CSS 基础、想找一个完整项目练手的前端新手,或者你正在准备一个类似教育类平台的页面搭建,这套思路可以直接拿去用。我下面要讲的,不是那种“第一步写 div,第二步写样式”的流水账,而是我在实际搭建过程中反复调整后沉淀下来的结构决策和避坑经验。
先说一个反直觉的结论:首页最难的不是视觉还原,而是模块之间的解耦。设计稿上每个区块看起来独立,但一旦你开始写代码,就会发现导航栏的高度会影响轮播图的定位,课程卡片的间距会跟底部版权信息产生莫名其妙的空白。所以我在动手之前,会先做一件事——把整个页面拆成若干个互不干扰的“区块容器”,每个容器有自己的宽度约束和内边距规则,区块之间只通过外边距发生关系。这个习惯让我在后面调整任何一个模块时,都不会牵一发而动全身。
2. 页面骨架的搭建逻辑:从版心容器到模块划分
2.1 为什么先定版心宽度再写任何样式
版心,也就是页面内容区域的最大宽度。很多教程会告诉你“设个 1200px 就行了”,但没解释为什么。我选择 1200px 作为版心宽度,原因有三个:第一,主流笔记本屏幕分辨率在 1366px 到 1920px 之间,1200px 的内容区在两侧留出足够呼吸空间,不会显得拥挤;第二,1200px 能被 2、3、4、5、6 整除,做栅格布局时列宽计算方便;第三,这个宽度下,一行放四张课程卡片,每张卡片约 280px,加上间距刚好填满,视觉上不松不紧。
具体做法是定义一个.container类:
.container { width: 1200px; margin: 0 auto; }margin: 0 auto让容器在父级中水平居中,这是最稳妥的居中方案,比 flex 或 grid 居中更少副作用。注意不要给容器设padding,否则实际内容宽度会变成 1200px 减去左右 padding,导致卡片对不齐。如果确实需要内边距,用box-sizing: border-box并接受内容区变窄,或者把 padding 放到子元素上。
2.2 模块划分的颗粒度怎么把握
我把首页拆成了七个一级模块:顶部导航栏、轮播图区域、课程分类导航、推荐课程列表、名师介绍、学习路径、底部版权。每个模块对应一个独立的<section>或<header>、<footer>标签,并且给每个模块一个语义化的类名,比如.header、.banner、.course-category、.course-list、.teacher、.learning-path、.footer。
这里有个经验:模块的颗粒度不要太细,也不要太粗。太细的话,比如把导航栏里的 logo、菜单、搜索框、登录按钮各拆成一个 section,代码会变得碎片化,维护时找起来费劲;太粗的话,把轮播图和课程分类合并成一个 section,后面想单独调整轮播图高度就会影响课程分类的定位。我的判断标准是:如果一个区块在视觉上有明显的背景色变化或间距分隔,就单独成一个模块。
2.3 导航栏的固定定位与占位处理
导航栏我用了position: fixed固定在顶部,这样用户滚动页面时导航始终可见。但固定定位会带来一个问题:导航栏脱离了文档流,下面的轮播图会往上跑,被导航栏遮住一部分。解决办法是给轮播图或者一个占位元素设置padding-top,值等于导航栏的高度。
导航栏高度我设为 80px,所以:
.banner { padding-top: 80px; }或者更干净的做法,在导航栏后面加一个空的占位 div:
<div class="header-placeholder"></div>.header-placeholder { height: 80px; }我倾向于后者,因为占位 div 的职责更单一,不会跟轮播图自身的 padding 混淆。如果后面导航栏高度改了,只需要改占位 div 的高度,不用去动轮播图的样式。
3. 轮播图区域的实现细节:不只是放几张图那么简单
3.1 轮播图的结构设计
轮播图区域我用了最基础的结构:一个外层容器,里面放一个图片列表和一个指示器列表。
<div class="banner"> <ul class="banner-list"> <li><img src="banner1.jpg" alt="课程推荐"></li> <li><img src="banner2.jpg" alt="名师课堂"></li> <li><img src="banner3.jpg" alt="学习路径"></li> </ul> <ol class="banner-indicator"> <li class="active"></li> <li></li> <li></li> </ol> </div>外层.banner设position: relative,图片列表绝对定位铺满,指示器绝对定位在底部居中。图片列表的li默认隐藏,只显示当前激活的那一张。切换逻辑可以用 CSS 的:target或者简单的 JavaScript 来控制,这里不展开 JS 部分,重点说 CSS 布局。
3.2 图片自适应与不变形处理
轮播图最容易出的问题是图片变形。如果直接给img设width: 100%和固定高度,图片会被拉伸或压缩。我的做法是给图片容器设固定高度,图片用object-fit: cover:
.banner-list li img { width: 100%; height: 100%; object-fit: cover; }object-fit: cover会保持图片宽高比,同时填满容器,超出部分裁剪。这样无论原图尺寸如何,展示效果都不会变形。注意object-fit在 IE 上不支持,如果项目需要兼容 IE,可以用背景图加background-size: cover替代。
3.3 指示器的交互状态设计
指示器的小圆点,默认是半透明白色,激活状态是实心白色。我用.active类来控制:
.banner-indicator li { width: 10px; height: 10px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.5); margin: 0 5px; cursor: pointer; } .banner-indicator li.active { background-color: #fff; }这里有个细节:指示器的z-index要高于图片列表,否则会被图片盖住点不到。通常设z-index: 10就够了,但要注意如果图片列表里也有定位元素,层级关系要理清楚。我的习惯是给轮播图区域内的所有绝对定位元素都显式设置z-index,避免出现“明明写了样式却点不到”的情况。
4. 课程卡片列表的布局方案:Flex 还是 Grid
4.1 两种布局方式的对比与选择
课程卡片列表是首页的核心内容区,通常一行展示四张卡片,卡片之间有固定间距。实现方式有两种:Flex 布局和 Grid 布局。
Flex 方案:
.course-list { display: flex; flex-wrap: wrap; gap: 20px; } .course-list .course-card { width: calc((100% - 60px) / 4); }Grid 方案:
.course-list { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }我最终选了 Grid。原因很简单:Grid 的repeat(4, 1fr)直接声明了“四列等宽”,语义清晰,不需要手动计算calc。而且 Grid 的gap在行列方向都生效,如果后面要改成两行八张卡片,不需要额外调整。Flex 方案虽然也能用,但calc((100% - 60px) / 4)这个表达式里的 60px 是三个间距的总和,一旦间距改了,公式也要跟着改,容易漏。
4.2 卡片内部的信息层级与间距
一张课程卡片包含:课程封面图、课程标题、讲师名称、价格、学习人数。信息层级从上到下依次减弱。封面图占卡片顶部,宽度 100%,高度固定 160px,同样用object-fit: cover。标题用 16px 加粗,最多显示两行,超出用省略号:
.course-card .title { font-size: 16px; font-weight: bold; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }讲师名称和价格放在同一行,用 Flex 的justify-content: space-between分开。价格用橙色突出,讲师名称用灰色弱化。学习人数放在最底部,字号最小,颜色最浅。
这里有个踩过的坑:卡片高度不一致。如果某张卡片的标题只有一行,另一张有两行,卡片高度就会不同,导致下一行的卡片顶部对不齐。解决办法是给标题设固定高度,或者给卡片设min-height。我选择给标题设height: 44px(两行的高度),这样无论标题一行还是两行,卡片总高度都一致。
4.3 响应式断点的设置策略
虽然版心是 1200px,但用户可能用平板或手机访问。我设了两个断点:768px 和 1200px。
- 大于 1200px:版心 1200px 居中,一行四张卡片。
- 768px 到 1200px:版心宽度改为 100%,左右各留 20px 内边距,一行三张卡片。
- 小于 768px:一行两张卡片,导航栏折叠成汉堡菜单。
断点的选择不是拍脑袋定的,而是根据内容的最小可读宽度反推。一张课程卡片最小需要 220px 才能保证标题不换行太频繁,1200px 减去间距后每张约 280px,768px 减去间距后每张约 230px,都还在可接受范围内。低于 768px 后,三张卡片每张不到 200px,标题会频繁折行,体验变差,所以降到两张。
5. 课程分类导航的交互设计:悬浮展开的细节处理
5.1 二级菜单的定位与显示逻辑
课程分类导航通常是一个垂直列表,鼠标悬浮在某一项时,右侧展开对应的二级菜单。结构如下:
<ul class="category-list"> <li class="category-item"> <span>前端开发</span> <div class="sub-menu"> <a href="#">HTML/CSS</a> <a href="#">JavaScript</a> <a href="#">Vue</a> </div> </li> <!-- 更多分类 --> </ul>.category-item设position: relative,.sub-menu设position: absolute; left: 100%; top: 0;,默认display: none,悬浮时display: block。
5.2 悬浮区域的连续性处理
这里有个经典问题:鼠标从一级菜单移到二级菜单时,中间如果有间隙,悬浮状态会丢失,二级菜单瞬间消失。解决办法是让二级菜单的left等于一级菜单的宽度,并且一级菜单和二级菜单之间不要有 margin 或 padding 造成的空隙。如果设计上必须有间隙,可以用一个透明的伪元素填充:
.category-item::after { content: ''; position: absolute; left: 100%; top: 0; width: 10px; height: 100%; }这个伪元素不可见,但能“接住”鼠标,保持悬浮状态。
5.3 二级菜单的宽度与内容溢出
二级菜单的宽度我设为 600px,内部用 Grid 分三列展示链接。如果某个分类下的子项特别多,超过三列,就自动换行。这里要注意二级菜单的高度不要超过视口高度,否则底部内容会被截断。我的做法是给二级菜单设max-height: 400px; overflow-y: auto;,超出部分滚动查看。
6. 名师介绍与学习路径模块的排版技巧
6.1 名师卡片的圆形头像与文字环绕
名师介绍模块通常是四个圆形头像加姓名和头衔,横向排列。圆形头像用border-radius: 50%实现,但要注意图片本身最好是正方形,否则会变成椭圆。如果不是正方形,先用object-fit: cover裁剪成正方形再设圆角。
.teacher-avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; }姓名和头衔居中显示,用text-align: center。四个卡片之间用 Flex 的justify-content: space-around均匀分布。
6.2 学习路径的时间线布局
学习路径模块我用了一个垂直时间线布局,左侧是时间节点,右侧是课程内容。时间线的竖线用伪元素实现:
.learning-path { position: relative; padding-left: 40px; } .learning-path::before { content: ''; position: absolute; left: 15px; top: 0; bottom: 0; width: 2px; background-color: #e0e0e0; }每个节点的小圆点用::after或者单独的span定位在竖线上。这种布局的关键是竖线的top和bottom要跟内容区对齐,不能超出或不足。我通常给.learning-path设padding-top和padding-bottom,然后竖线的top和bottom设为 0,这样竖线会跟随内容区的高度自动伸缩。
7. 实际搭建中容易忽略的细节与排查清单
7.1 清除默认样式带来的连锁反应
浏览器对ul、ol、h1到h6、p都有默认的 margin 和 padding。如果不清除,页面会出现莫名其妙的空白。我通常在 CSS 开头写一段重置:
* { margin: 0; padding: 0; box-sizing: border-box; } ul, ol { list-style: none; } a { text-decoration: none; color: inherit; } img { display: block; max-width: 100%; }注意img设display: block可以消除图片底部的默认间隙,这个间隙是因为img默认是行内元素,基线对齐导致的。max-width: 100%防止图片撑破容器。
7.2 浮动与清除浮动的现代替代方案
早期布局常用float,但浮动元素会脱离文档流,父元素高度塌陷。现在有了 Flex 和 Grid,基本不需要用浮动了。如果确实要用浮动,记得给父元素加overflow: hidden或者用clearfix伪元素清除浮动。我的建议是:新项目一律用 Flex 或 Grid,不要再用 float 做布局,除非你需要文字环绕图片这种特殊效果。
7.3 层级与定位的调试方法
当元素定位不对时,我常用的排查步骤是:第一,给元素加一个临时背景色,看它的实际占位区域;第二,用浏览器开发者工具检查position、top、left、z-index的计算值;第三,检查父元素是否有position: relative,因为绝对定位是相对于最近的定位祖先元素计算的。如果父元素没有定位,绝对定位会相对于body计算,导致位置完全不对。
7.4 字体与行高的统一设置
字体我通常设font-family: "Microsoft YaHei", "PingFang SC", sans-serif;,优先使用系统自带的中文字体,避免加载外部字体带来的闪烁。行高设1.5到1.6之间,正文用1.6,标题用1.4。行高不要用固定像素值,用无单位数值,这样字号变化时行高会自动缩放。
8. 从静态页面到可维护代码的进阶思路
8.1 CSS 变量的使用
如果页面中有重复使用的颜色、间距、字号,建议用 CSS 变量统一管理:
:root { --primary-color: #ff6b00; --text-color: #333; --text-light: #999; --gap: 20px; --container-width: 1200px; }后面修改主题色时,只需要改--primary-color一处,所有引用这个变量的地方都会更新。这个习惯在项目变大后尤其重要。
8.2 类名命名规范
我用的是一种简化的 BEM 风格:模块名用中划线连接,子元素用模块名加双下划线加子元素名。比如.course-card__title、.course-card__price。这样从类名就能看出元素属于哪个模块,不会出现.title这种过于泛化的命名导致样式冲突。
8.3 代码注释的取舍
注释不是越多越好。我通常只在三个地方写注释:一是模块的开始和结束,标明这个区块是什么;二是复杂的定位计算,解释为什么用这个值;三是兼容性处理,说明为什么需要这段 hack。其他一眼能看懂的样式不需要注释,否则代码会变得臃肿。
8.4 后续扩展的方向
这套页面搭好之后,可以往几个方向扩展:一是接入真实数据,把静态的课程卡片改成从接口获取;二是增加课程详情页,复用首页的卡片组件;三是加入搜索功能,在导航栏的搜索框里实现关键词过滤。每个扩展方向都建立在当前模块化结构的基础上,不会推翻重来。
我在实际搭建这个页面的过程中,最大的体会是:前期花时间理清结构,后期省下的调试时间远超预期。尤其是版心宽度、模块划分、命名规范这三件事,一旦定好,后面写样式就是填空,而不是反复推翻重来。如果你也在做类似的教育平台首页,不妨先把这七个模块在纸上画一遍,标好每个模块的宽度和间距,再打开编辑器。这个习惯我坚持了三年,每次都能帮我省下至少半天的返工时间。