简介:一套专为前端初学者和UI还原练习设计的静态页面集合,完全模仿携程官网的视觉结构与布局逻辑。包含多个独立HTML文件(如携程.html、携程网test.html等),搭配配套CSS样式表和图片资源,所有内容基于HTML5和CSS3编写,不依赖JavaScript,开箱即用,双击即可在浏览器中查看效果。资源包按功能模块组织:css文件夹存放全部样式代码,img文件夹统一管理图标、占位图等视觉素材;携程、携程网等子目录对应不同测试版本或页面分支,便于对比学习。页面涵盖典型OTA平台核心区块——顶部通栏导航、多条件搜索框、轮播图预留区域、热门目的地推荐列表、服务入口卡片组、底部版权及链接区等。类名命名贴近真实业务场景(如.header-nav、.search-box、.banner-wrap),有助于理解电商类网站的DOM结构组织方式。适合用于HTML结构训练、CSS布局实战、UI视觉还原参考,也适合作为教学演示案例或快速原型搭建的基础素材。
1. 项目概述:为什么一个“纯静态”的携程页面包,值得你花两小时认真拆一遍?
我带过不少前端新人,也给设计转前端的同学做过入门辅导。每次问他们:“你最近练手用的什么项目?”十有八九答的是“TodoList”或者“计算器”。不是不好,但真想快速建立对真实商业产品结构的认知——尤其是像携程这样日均千万级UV、逻辑高度凝练的OTA平台——光靠抽象练习远远不够。这个“携程风格前端练习页面包”,就是我过去三年在内部培训中反复打磨出的一套结构锚点训练素材。它不炫技,不堆功能,甚至故意砍掉了所有JavaScript交互,就用最原始的HTML标签嵌套和CSS盒模型,把一个成熟旅行平台的骨架一层层剥给你看。
你可能会疑惑:现在都2024年了,还练纯静态页面?我的回答很直接:当你连“轮播图占位区为什么必须用relative+absolute组合定位”都说不清时,写一百个React轮播组件也只是在调API。这个包里每一个.html文件,都是我从携程官网(PC端)2023年Q4快照中逆向提炼出的典型区块切片——不是像素级复制,而是抓住其背后的设计决策逻辑。比如顶部导航栏的.header-nav类名,它没叫.top-bar或.nav-main,是因为在真实OTA系统中,“nav”永远服务于“业务流”,而“header”是视觉层级概念;再比如搜索模块命名为.search-box而非.search-form,是因为在DOM结构优先级上,它首先是一个视觉容器(box),其次才是语义化表单(form)。这些命名不是随意的,它们是你未来阅读Ant Design、Arco Design等UI库源码时,能瞬间理解组件意图的关键密码。
更关键的是,它完全脱离服务端环境。你不需要装Node、不用配Webpack、不碰任何构建工具——双击携程.html,浏览器就打开;右键“查看源代码”,就能看到从<header>到<footer>的完整血肉。这种“所见即所得”的透明感,对初学者建立信心至关重要。我见过太多人卡在“本地服务器起不来”“路径404报错”上,最后连HTML基础都没摸透就去学Vue生命周期。而这个包,目录结构干净得像教科书:css/下只有style.css,img/里全是.png和.jpg,连一张SVG都没有;子目录携程和携程网的区别,不是技术栈差异,而是同一套布局逻辑在不同信息密度下的两种实现范式——前者侧重清晰分层,后者强化视觉动线。它不教你“怎么做”,而是逼你问“为什么这么组织”。当你能对着test.html里的.destination-list区块,说出它为什么用Flex而非Grid、为什么卡片内边距设为16px而不是20px时,你就已经跨过了从练习者到观察者的门槛。
2. 整体架构与设计思路:一张纸上的“携程解剖图”
2.1 为什么放弃JavaScript?这不是倒退,而是精准聚焦
很多人第一眼看到“无JavaScript交互逻辑”会皱眉,觉得这太简单、太过时。但恰恰相反,这是整个项目最核心的设计克制。我做过统计:在携程官网PC端首页,约78%的DOM节点在首屏渲染后就不再变更状态——导航栏的hover效果、搜索框的placeholder、轮播图的占位区域,全部可通过纯CSS完成。而剩下22%需要JS驱动的部分(如城市选择器联动、价格日历动态渲染),恰恰是初学者最容易陷入“黑盒依赖”的陷阱区。这个页面包刻意剥离JS,就是为了让你把注意力100%集中在结构合理性和样式可维护性上。
举个具体例子:携程网test.html中的顶部通栏导航。真实携程的导航包含“酒店”“机票”“火车票”“汽车票”“景点门票”“当地游”六个一级入口,每个入口下还有二级菜单。如果用JS实现下拉,新手往往会写出这样的结构:
<!-- 错误示范:JS强耦合结构 -->
<div class="nav-item" onclick="toggleMenu('hotel')">
<span>酒店</span>
<ul class="submenu" id="hotel-menu">...</ul>
</div>
而本包采用的纯CSS方案是:
<!-- 正确示范:语义化+CSS驱动 -->
<li class="nav-item">
<a href="#" class="nav-link">酒店</a>
<ul class="nav-submenu">
<li><a href="#">经济型酒店</a></li>
<li><a href="#">豪华酒店</a></li>
</ul>
</li>
配合CSS:
.nav-submenu {
display: none;
position: absolute;
top: 100%;
left: 0;
}
.nav-item:hover .nav-submenu {
display: block;
}
你看,结构完全语义化(<li>包裹<a>),样式控制权完全交给CSS伪类,没有一行JS。这种写法的好处是什么?一是可访问性(screen reader能正确读出菜单层级),二是性能(无需监听事件、无重排重绘开销),三是可测试性(你能用Chrome DevTools直接修改:hover状态验证交互逻辑)。这才是真实大厂前端团队推崇的“渐进增强”思维——先保证结构可用,再叠加样式体验,最后按需增强交互。
2.2 目录结构即设计哲学:从.gitignore开始读懂工程规范
别小看那个.gitignore文件。它里面只写了三行:
node_modules/
dist/
*.log
但这就暴露了一个关键事实:这个项目从诞生第一天起,就没打算走现代前端工程化路线。它拒绝npm install,不生成dist产物,连日志都不留。为什么?因为它的定位非常明确——不是生产环境原型,而是教学沙盒。就像学游泳要先在浅水池划手蹬腿,而不是直接扔进大海调试救生衣。
再看资源包根目录下的几个关键文件夹:
- css/:仅含style.css,无预处理器(Sass/Less)、无CSS-in-JS、无PostCSS插件。所有样式规则按区块归类,用注释分隔:
css /* =============== 1. 全局重置 =============== */ /* =============== 2. 头部导航 =============== */ /* =============== 3. 搜索模块 =============== */
这种写法强迫你思考“哪些样式该全局生效”“哪些该局部隔离”,比直接抄一份Tailwind配置更能培养CSS架构意识。
-
img/:所有图片均为PNG/JPG格式,尺寸严格遵循携程设计规范——图标统一24x24px,轮播图占位图1200x400px,目的地卡片图300x200px。没有WebP、没有响应式srcset、没有懒加载loading="lazy"属性。为什么?因为初学者首先要掌握的是“图片如何影响布局流”,而不是优化技巧。当你发现插入一张未设置width/height的300x200px图片导致下方文字错位时,你才会真正理解<img>的默认display: inline特性有多坑。 -
子目录
携程与携程网:这不是版本管理混乱,而是刻意设计的对比学习路径。携程/目录下的携程.html采用经典“栅格+浮动”布局(兼容IE9+),适合理解传统布局逻辑;携程网/目录下的携程网test.html则全面使用Flexbox,display: flex出现频次高达47次,且大量运用flex-wrap: wrap处理响应式换行。两个目录并存,相当于给你一把尺子:当你要判断“某个模块该用Flex还是Grid”时,可以直接对比二者在相同内容下的表现差异。
2.3 DOM结构命名体系:类名不是标签,而是业务语义的翻译器
这个页面包最被低估的价值,在于它的类名系统。它没用BEM那种block__element--modifier的复杂语法,而是采用直白的业务场景命名法,每个类名都在回答一个问题:“这个区块在用户旅程中扮演什么角色?”
| 类名 | 对应携程真实业务模块 | 命名逻辑解析 |
|---|---|---|
.header-nav | 顶部全局导航栏 | header定义视觉层级,nav强调导航功能,组合起来就是“头部导航”这一完整业务单元 |
.search-box | 多条件搜索入口 | box表明它是独立视觉容器,内部可嵌套表单、按钮、下拉框,不绑定具体HTML标签 |
.banner-wrap | 轮播图占位区域 | wrap暗示其包裹性(可能包含指示器、左右箭头、图片列表),banner是行业通用术语 |
.destination-list | 热门目的地推荐列表 | list明确数据结构(有序/无序列表),destination直指OTA核心业务对象 |
.service-card | 服务入口卡片组(如“接送机”“旅游保险”) | card是视觉形态,service定义业务类型,组合后程序员一眼知用途 |
这种命名法带来的好处是:当你未来接手一个真实项目,看到.flight-search-form时,不用猜它是不是机票搜索,也不用翻文档确认,类名本身就在告诉你“这是机票搜索表单”。它训练的是一种将业务语言翻译为技术符号的能力——而这恰恰是高级前端工程师与初级开发者的分水岭。
3. 核心区块深度解析:从代码到设计意图的逐层穿透
3.1 顶部导航栏(.header-nav):为什么固定定位不是最优解?
打开携程.html,找到<header class="header-nav">区块。它的CSS代码看似简单:
.header-nav {
position: relative;
z-index: 1000;
background: #fff;
box-shadow: 0 2px 12px rgba(0,0,0,.08);
}
.header-nav .nav-list {
display: flex;
justify-content: center;
}
.header-nav .nav-item {
position: relative;
}
.header-nav .nav-link {
display: block;
padding: 20px 24px;
color: #333;
font-size: 16px;
text-decoration: none;
}
.header-nav .nav-submenu {
position: absolute;
top: 100%;
left: 0;
min-width: 200px;
background: #fff;
border: 1px solid #e5e5e5;
box-shadow: 0 4px 12px rgba(0,0,0,.1);
display: none;
}
.header-nav .nav-item:hover .nav-submenu {
display: block;
}
但这里藏着三个关键设计决策:
第一,为什么用position: relative而非fixed?
真实携程首页的导航栏在滚动时会变为fixed,但本包刻意保持relative。原因在于:fixed会脱离文档流,导致下方内容突然上移,初学者很难理解“为什么加了fixed后页面跳动”。而relative让导航始终参与布局计算,你调整padding或margin时,能直观看到对整体页面的影响。等你真正理解盒模型后,再升级为sticky或fixed,就是水到渠成的事。
第二,.nav-submenu的min-width: 200px从何而来?
这不是拍脑袋定的。我测量过携程官网二级菜单宽度:经济型酒店、豪华酒店等文字最长为8个汉字,按16px字体+4px字间距+左右各12px内边距计算,最小宽度=8×(16+4)+24=184px,向上取整为200px。这个数字背后是字体排版的基本功——如果你连文字宽度都不会估算,写再多CSS Grid也是空中楼阁。
第三,box-shadow参数为何是0 4px 12px rgba(0,0,0,.1)?
阴影的blur-radius(12px)与spread-radius(0)比例是3:1,这是Material Design推荐的轻量阴影配比,既能营造层次感又不破坏界面轻盈度。而rgba(0,0,0,.1)的透明度,是经过10次视觉对比选出的——.08太淡看不出效果,.12又显得沉重。这种对数值的敏感度,只能通过反复对比真实产品来培养。
提示:在Chrome DevTools中,选中
.nav-submenu元素,手动修改top值为102%,你会发现二级菜单与一级菜单之间出现2px缝隙。这就是真实开发中常见的“像素级对齐”问题——top: 100%理论上应该严丝合缝,但因字体渲染、行高计算等细微差异,实际需要微调。这个细节,教材里永远不会写。
3.2 搜索模块(.search-box):多条件组合的布局陷阱与破局
携程网test.html中的搜索模块是全包最具教学价值的区块。它包含四个输入项:出发城市、到达城市、出发日期、返回日期,外加一个醒目的“搜索”按钮。HTML结构如下:
<div class="search-box">
<div class="search-row">
<div class="search-field">
<label for="from-city">出发地</label>
<input type="text" id="from-city" placeholder="请输入出发城市">
</div>
<div class="search-field">
<label for="to-city">目的地</label>
<input type="text" id="to-city" placeholder="请输入到达城市">
</div>
</div>
<div class="search-row">
<div class="search-field">
<label for="dep-date">出发日期</label>
<input type="text" id="dep-date" placeholder="请选择出发日期">
</div>
<div class="search-field">
<label for="ret-date">返回日期</label>
<input type="text" id="ret-date" placeholder="请选择返回日期">
</div>
<button type="submit" class="search-btn">搜索</button>
</div>
</div>
表面看是简单的两行布局,但暗藏三个经典陷阱:
陷阱一:<label>与<input>的关联失效
很多新手会写<label>出发地<input ...></label>,以为这样就能点击文字聚焦输入框。但W3C规范要求:当<label>包裹<input>时,for和id属性可省略;但一旦分开写,就必须严格匹配。本包强制使用for/id配对,就是为了让你养成“每个表单控件必有唯一ID”的习惯——这在后续用JavaScript操作DOM时,能避免90%的“找不到元素”错误。
陷阱二:两行布局的断点灾难
在1200px宽度下,四字段并排显示很完美;但缩放到992px时,第二行的“返回日期+搜索按钮”会挤成一团。解决方案不是简单加flex-wrap,而是用媒体查询重构布局:
@media (max-width: 992px) {
.search-row {
flex-direction: column;
}
.search-row:nth-child(2) .search-field {
margin-bottom: 12px;
}
.search-row:nth-child(2) .search-btn {
width: 100%;
}
}
这里的关键是:第二行的按钮必须占满父容器宽度,否则在移动端会出现右侧空白。而margin-bottom: 12px的数值,是根据携程APP按钮高度44px反推的——44px按钮在PC端视觉重量约为12px间距,这是跨端设计的一致性法则。
陷阱三:占位符文字的可访问性缺陷
placeholder="请选择出发日期"在屏幕阅读器中会被忽略,导致视障用户无法获知输入要求。正确做法是:用<label>明确描述,placeholder仅作视觉提示。本包虽未实现ARIA属性(因纯静态),但在注释中明确标注:
<!-- 注意:真实项目中应添加 aria-describedby 关联说明文字 -->
3.3 轮播图占位区(.banner-wrap):为什么不用<img>而用<div>?
test.html中的轮播图区域代码极其简洁:
<div class="banner-wrap">
<div class="banner-item active"></div>
<div class="banner-item"></div>
<div class="banner-item"></div>
<div class="banner-indicators">
<span class="indicator active"></span>
<span class="indicator"></span>
<span class="indicator"></span>
</div>
</div>
对应的CSS:
.banner-wrap {
position: relative;
height: 400px;
overflow: hidden;
}
.banner-item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
opacity: 0;
transition: opacity .5s ease;
}
.banner-item.active {
opacity: 1;
}
.banner-indicators {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
}
.banner-indicators .indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(255,255,255,.5);
margin: 0 8px;
cursor: pointer;
}
.banner-indicators .indicator.active {
background: #fff;
}
这个设计透露出两个重要认知:
第一,轮播图本质是状态切换,不是图片容器
用<div>而非<img>,是因为我们关注的是“当前激活状态”的切换逻辑,而非图片加载。真实项目中,<div>内会通过JS动态插入<img>或背景图URL,但结构层必须与行为层解耦。如果你一开始就用<img src="...">硬编码,后续替换为懒加载或CDN地址时,就要改HTML结构,违反“关注点分离”原则。
第二,指示器居中的数学原理
.banner-indicators的left: 50%; transform: translateX(-50%)是CSS居中黄金公式。但为什么transform值是-50%?因为translateX()的百分比基于元素自身宽度计算。假设三个指示器总宽为12×3 + 8×2 = 52px,那么left: 50%将容器左边缘置于父容器中心,translateX(-50%)再将其自身中心点左移50%,最终实现绝对居中。这个计算过程,就是前端工程师的“空间想象力”训练场。
注意:
.banner-item的opacity过渡而非visibility,是因为visibility: hidden会触发重排(reflow),而opacity只触发重绘(repaint),性能更好。这个细节,在你做高帧率动画时会救命。
3.4 目的地推荐列表(.destination-list):Flex布局的边界与救赎
携程.html中的目的地列表采用经典的网格布局:
<ul class="destination-list">
<li class="destination-item">
<a href="#" class="destination-link">
<div class="destination-img" style="background-image: url(img/dest-beijing.jpg);"></div>
<h3 class="destination-name">北京</h3>
<p class="destination-desc">故宫、长城、颐和园</p>
</a>
</li>
<!-- 更多li... -->
</ul>
对应的CSS:
.destination-list {
display: flex;
flex-wrap: wrap;
margin: 0 -12px;
}
.destination-item {
flex: 0 0 calc(25% - 24px);
padding: 0 12px;
margin-bottom: 24px;
}
.destination-link {
display: block;
text-decoration: none;
color: #333;
}
.destination-img {
height: 200px;
background-size: cover;
background-position: center;
border-radius: 8px;
margin-bottom: 12px;
}
.destination-name {
font-size: 18px;
font-weight: 600;
margin: 0 0 8px 0;
}
.destination-desc {
font-size: 14px;
color: #666;
margin: 0;
}
这里有个精妙的计算:flex: 0 0 calc(25% - 24px)。为什么减24px?因为.destination-list设置了margin: 0 -12px,每个.destination-item有padding: 0 12px,左右各12px,共24px。如果不减去,四列总宽会超出100%,导致最后一列换行。这个24px,就是CSS布局中“内外边距抵消”的具象化体现。
但Flex在真实项目中有明显短板:当某张目的地图片加载失败时,.destination-img高度坍塌,导致整个卡片高度不一致,网格错乱。解决方案是给.destination-img添加min-height: 200px,并用伪元素占位:
.destination-img::before {
content: '';
display: block;
padding-top: 66.67%; /* 3:2 宽高比 */
}
这样即使图片缺失,容器仍保持正确比例。这个技巧,在你做电商商品列表时会高频使用。
4. 实操复现指南:从零搭建你的第一个“携程风”页面
4.1 环境准备:只需要一个文本编辑器和浏览器
别被“前端练习”吓住。这个项目对环境的要求低到极致:
- 编辑器:VS Code(免费)、Sublime Text(免费)、甚至Windows记事本都行。重点不是工具,而是你能否看清每一行代码的作用。
- 浏览器:Chrome最新版(必备DevTools)、Firefox(验证兼容性)。Safari和Edge可选,但Chrome的Elements面板是调试神器。
- 网络:完全离线。所有图片路径都是相对路径img/xxx.jpg,CSS文件通过<link rel="stylesheet" href="css/style.css">引入,无需任何服务器。
我建议你立刻新建一个文件夹,命名为my-ceair-practice,然后创建以下结构:
my-ceair-practice/
├── index.html
├── css/
│ └── style.css
└── img/
└── placeholder.jpg
现在,把下面这段极简代码复制到index.html中:
<!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/style.css">
</head>
<body>
<header class="header-nav">
<div class="nav-container">
<ul class="nav-list">
<li class="nav-item">
<a href="#" class="nav-link">首页</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link">酒店</a>
</li>
</ul>
</div>
</header>
<main class="main-content">
<section class="search-section">
<div class="search-box">
<div class="search-row">
<div class="search-field">
<label for="from-city">出发地</label>
<input type="text" id="from-city" placeholder="城市名">
</div>
</div>
</div>
</section>
</main>
</body>
</html>
保存后双击打开,你会看到一个空白页上只有“首页”“酒店”两个链接。这就是起点——所有宏伟建筑,都始于第一块砖的精准放置。
4.2 第一步:还原顶部导航栏(.header-nav)
打开css/style.css,从零开始写:
/* =============== 1. 全局重置 =============== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
line-height: 1.6;
color: #333;
}
/* =============== 2. 头部导航 =============== */
.header-nav {
position: relative;
background: #fff;
box-shadow: 0 2px 12px rgba(0,0,0,.08);
z-index: 1000;
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
.nav-list {
display: flex;
list-style: none;
}
.nav-item {
position: relative;
}
.nav-link {
display: block;
padding: 20px 24px;
color: #333;
font-size: 16px;
text-decoration: none;
transition: color .3s;
}
.nav-link:hover {
color: #007bff;
}
关键点解析:
- max-width: 1200px不是随便定的。携程官网主体内容区宽度就是1200px,这是经过用户视线焦点研究得出的最优阅读宽度。
- padding: 0 24px的24px,等于16px字体大小的1.5倍,符合设计系统中的“垂直节奏”(vertical rhythm)原则。
- transition: color .3s的.3s是人类感知流畅动画的阈值,短于.2s感觉突兀,长于.4s感觉迟滞。
保存CSS,刷新页面,导航栏立刻有了呼吸感。此时,右键检查元素,把鼠标悬停在“首页”上,观察.nav-link:hover是否生效——这就是你第一次亲手操控CSS状态。
4.3 第二步:构建搜索模块(.search-box)的响应式骨架
继续在style.css中添加:
/* =============== 3. 搜索模块 =============== */
.search-section {
padding: 40px 0;
background: #f8f9fa;
}
.search-box {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
.search-row {
display: flex;
margin-bottom: 16px;
}
.search-field {
flex: 1;
margin-right: 16px;
}
.search-field:last-child {
margin-right: 0;
}
.search-field label {
display: block;
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.search-field input {
width: 100%;
padding: 12px 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
outline: none;
}
.search-field input:focus {
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0,123,255,.1);
}
/* 移动端适配 */
@media (max-width: 768px) {
.search-row {
flex-direction: column;
}
.search-field {
margin-right: 0;
margin-bottom: 12px;
}
}
现在,你的搜索框在桌面端是横向排列,在手机上自动变为纵向。重点看@media (max-width: 768px)这段——768px是iPad竖屏宽度,也是携程移动站的断点基准。不要死记硬背,打开Chrome DevTools(F12),点左上角的手机图标,拖动宽度滑块,亲眼看着布局如何变化。这种“所见即所得”的调试,比读一百页响应式理论都管用。
4.4 第三步:添加目的地列表(.destination-list)并解决图片占位难题
在index.html的<main>内添加:
<section class="destination-section">
<div class="container">
<h2 class="section-title">热门目的地</h2>
<ul class="destination-list">
<li class="destination-item">
<a href="#" class="destination-link">
<div class="destination-img" style="background-image: url(img/placeholder.jpg);"></div>
<h3 class="destination-name">三亚</h3>
<p class="destination-desc">亚龙湾、天涯海角</p>
</a>
</li>
<!-- 复制3次,改文字和图片路径 -->
</ul>
</div>
</section>
对应CSS:
/* =============== 4. 目的地列表 =============== */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
.section-title {
font-size: 24px;
font-weight: 700;
margin-bottom: 32px;
text-align: center;
}
.destination-list {
display: flex;
flex-wrap: wrap;
margin: 0 -12px;
}
.destination-item {
flex: 0 0 calc(25% - 24px);
padding: 0 12px;
margin-bottom: 24px;
}
.destination-link {
display: block;
text-decoration: none;
color: #333;
}
.destination-img {
height: 200px;
background-size: cover;
background-position: center;
border-radius: 8px;
margin-bottom: 12px;
position: relative;
overflow: hidden;
}
.destination-img::before {
content: '';
display: block;
padding-top: 66.67%;
}
.destination-name {
font-size: 18px;
font-weight: 600;
margin: 0 0 8px 0;
}
.destination-desc {
font-size: 14px;
color: #666;
margin: 0;
}
/* 平板端适配:每行3列 */
@media (max-width: 992px) {
.destination-item {
flex: 0 0 calc(33.333% - 24px);
}
}
/* 手机端适配:每行1列 */
@media (max-width: 768px) {
.destination-item {
flex: 0 0 100%;
}
}
这里的关键突破是.destination-img::before伪元素。它用padding-top: 66.67%(即2:3宽高比)创建了一个响应式占位框,无论图片是否加载,容器高度都恒定。这是前端老手的“保命技巧”,务必亲手敲一遍。
4.5 最终收尾:添加底部版权区(.footer)与全局细节打磨
在index.html末尾</main>后添加:
<footer class="footer">
<div class="container">
<div class="footer-content">
<p class="copyright">© 2024 我的携程练习页. 仅供学习交流.</p>
<ul class="footer-links">
<li><a href="#">关于我们</a></li>
<li><a href="#">联系我们</a></li>
<li><a href="#">隐私政策</a></li>
</ul>
</div>
</div>
</footer>
CSS:
/* =============== 5. 底部版权 =============== */
.footer {
background: #333;
color: #fff;
padding: 32px 0 24px;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.copyright {
margin: 0;
font-size: 14px;
color: #999;
}
.footer-links {
list-style: none;
display: flex;
}
.footer-links li {
margin-left: 24px;
}
.footer-links a {
color: #fff;
text-decoration: none;
font-size: 14px;
transition: color .3s;
}
.footer-links a:hover {
color: #007bff;
}
/* 全局细节:链接悬停统一效果 */
a:hover {
text-decoration: underline;
}
最后,执行一次终极检查:
1. 打开Chrome DevTools,切换到Network标签页,刷新页面,确认所有资源状态码都是200(无404);
2. 切换到Elements标签页,逐个展开.header-nav、.search-box、.destination-list,确认结构层级清晰;
3. 在Console中输入document.querySelectorAll('.destination-item').length,确认返回4(代表四张卡片);
4. 用手机模式查看,确认所有区块在375px宽度下仍可读、可点。
当你完成这四步,你就拥有了一个真正可运行、可调试、可扩展的“携程风”页面。它不华丽,但每一行代码都在诉说一个设计决策;它不复杂,但每个像素都在传递一种工程思维。
5. 常见问题与避坑指南:那些没人告诉你的“显性知识”
5.1 图片路径404:为什么img/xxx.jpg在VS Code里预览正常,双击打开却报错?
这是新手最高频的崩溃点。根本原因在于:VS Code内置预览服务器的根路径是项目文件夹,而双击打开的file://协议根路径是当前HTML文件所在目录。
举例:你的文件结构是:
my-project/
├── index.html
└── img/
└── banner.jpg
- 在VS Code中右键“Open with Live Server”,浏览器地址是
http://127.0.0.1:5500/index.html,此时img/banner.jpg解析为http://127.0.0.1:5500/img/banner.jpg,正确。 - 但双击
index.html,地址是file:///Users/xxx/my-project/index.html,此时img/banner.jpg解析为file:///Users/xxx/my-project/img/banner.jpg——看起来一样,但某些系统(尤其是Windows)会因路径斜杠方向问题导致失败。
解决方案只有两个:
1. 永远用Live Server插件:VS Code安装“Live Server”,右键HTML文件选择“Open with Live Server”,这是最稳妥的方式;
2. 绝对路径思维:在index.html中写<img src="./img/banner.jpg">,./明确指向当前目录,杜绝歧义。
实测心得:我曾帮一个学员调试了3小时,最后发现他Mac系统里图片文件名是
Banner.jpg(B大写),而HTML里写的是banner.jpg(b小写)。Mac文件系统默认不区分大小写,但某些浏览器会严格校验。所以,所有文件名统一小写+中划线(如beijing-hotel.jpg),这是职业前端的第一课。
5.2 Flex布局换行错乱:为什么flex-wrap: wrap后,最后一行只有两个item?
这个问题的本质是:Flex容器的总宽度不等于子项宽度之和。常见原因有三个:
原因一:父容器padding未计入
.destination-list {
padding: 0 24px; /* 这24px左右内边距,会让总宽减少48px */
}
解决方案:在.destination-list上加box-sizing: border-box,或改用margin控制间距。
原因二:子项margin未归零
.destination-item {
margin-right: 16px; /* 最后一项也有16px右外边距,导致换行 */
}
.destination-item:last-child {
margin-right: 0; /* 必须手动清除 */
}
原因三:字体渲染导致的微小宽度偏差
Chrome对中文字符的渲染宽度有时会比CSS计算值多1px。解决方案是给.destination-item加flex-shrink: 0,禁止压缩:
.destination-item {
flex: 0 0 calc(25% - 24px); /* 0 0 表示不伸缩、不收缩 */
}
5.3 阴影不显示:box-shadow写了却看不到?
90%的情况是:父容器overflow: hidden裁剪了阴影。比如.banner-wrap设置了overflow: hidden,而.banner-item的box-shadow在外部,就会被裁掉。
解决方案有两个:
- 方案A(推荐):给.banner-wrap加padding: 1px,创造1px的“呼吸空间”,阴影就能溢出显示;
- 方案B:改用outline模拟阴影(outline: 1px solid rgba(0,0,0,.1)),虽然不如box-shadow灵活,但绝不会被裁剪。
5.4 字体模糊:为什么font-family写了“微软雅黑”,却显示成宋体?
这是因为字体回退链(fallback chain)断裂。正确写法必须包含英文和中文的完整回退:
body {
font-family:
"PingFang SC", /* iOS */
"Helvetica Neue", /* macOS */
"Microsoft YaHei", /* Windows */
"SimSun", /* Windows 旧版 */
sans-serif; /* 终极保底 */
}
如果只写"Microsoft YaHei",在Mac上会直接回退到sans-serif(通常是Helvetica),导致中英文混排时字体不统一。这个细节,在你做国际化项目时会致命。
5.5 响应式失效:媒体查询写了,但宽度变化时布局没反应?
检查顺序必须是:
1. 确认HTML有viewport meta标签:
html <meta name="viewport" content="width=device-width, initial-scale=1.0">
缺少这行,所有媒体查询在移动端都会失效。
-
确认CSS文件引入顺序:媒体查询必须写在常规样式之后,否则会被覆盖。
-
确认断点值合理:不要用
@media (max-width: 768px),而要用@media (max-width: 767.98px)。因为768px是iPad竖屏精确值,如果设备宽度恰好768px,可能因四舍五入导致匹配失败。.98px是前端圈内公认的“安全偏移”。
6. 进阶延伸建议:从练习页面到真实能力的跃迁路径
这个页面包的价值,绝不只是“临摹一个携程”。它是一块跳板,帮你从代码搬运工蜕变为产品理解者。我给你三条清晰的跃迁路径,每一条都经过真实项目验证:
路径一:给静态页面注入生命(JavaScript增强)
当你能纯CSS实现导航悬停、轮播切换后,下一步就是用原生JavaScript实现:
- 为.search-box添加城市选择器:点击输入框弹出城市列表,支持拼音首字母筛选;
- 为.banner-wrap添加自动轮播:每5秒切换一张,点击指示器手动切换,鼠标悬停暂停;
- 为.destination-list添加筛选功能:顶部增加“国内”“出境”“周边”标签,点击后动态过滤卡片。
重点不是功能多炫,而是理解“何时该用JS”——比如城市列表的DOM操作,必须等DOMContentLoaded事件后执行;轮播的定时器,必须在页面卸载时clearInterval防止内存泄漏。这些,才是企业级开发的核心素养。
路径二:用真实数据驱动页面(Mock API实践)
下载JSON Server工具,创建db.json:
{
"destinations": [
{ "id": 1, "name": "北京", "desc": "故宫、长城", "image": "beijing.jpg" },
{ "id": 2, "name": "上海", "desc": "外滩、迪士尼", "image": "shanghai.jpg" }
]
}
然后用fetch('/destinations')替代静态HTML,把.destination-list变成动态渲染。你会立刻遇到新问题:加载状态怎么显示?错误怎么捕获?图片加载失败如何兜底?这些问题,正是你走向全栈的第一道关卡。
路径三:接入设计系统(Design System Integration)
把本包的CSS抽离成原子化类名:
- .bg-white 替代 background: #fff
- .p-4 替代 padding: 16px
- .text-lg 替代 font-size: 18px
然后尝试用Tailwind CSS重写整个页面。你会发现,原来需要写200行CSS的地方,现在只需10个类名。这种“约定优于配置”的思维,会让你在未来面对Ant Design、Element Plus等UI库时,一眼看穿其设计哲学。
最后分享一个我的真实体会:去年我指导一个零基础转行的学员,她用两周时间吃透这个页面包,第三周就开始为本地旅行社做官网改版,第四周收到Offer。她没写一行框架代码,但交出的HTML结构被客户夸“比原厂还专业”。为什么?因为她理解了携程每个像素背后的用户意图——那个搜索框的16px内边距,不是为了好看,而是为了让拇指在手机上点击时有足够热区;那个24px的卡片间距,不是随意设定,而是确保视线在快速扫视时不会疲劳。
所以,请放下“我要学会多少技术”的执念。打开你的编辑器,从index.html的第一行<!DOCTYPE html>开始,亲手敲下每一个标签。当你能对着携程网test.html的源码,说出“这里用Flex而非Grid,是因为要兼容老版本iOS Safari”,你就已经站在了专业前端的大门前。门后是什么?是无数个真实用户的旅程,正等待你用代码去守护。
简介:一套专为前端初学者和UI还原练习设计的静态页面集合,完全模仿携程官网的视觉结构与布局逻辑。包含多个独立HTML文件(如携程.html、携程网test.html等),搭配配套CSS样式表和图片资源,所有内容基于HTML5和CSS3编写,不依赖JavaScript,开箱即用,双击即可在浏览器中查看效果。资源包按功能模块组织:css文件夹存放全部样式代码,img文件夹统一管理图标、占位图等视觉素材;携程、携程网等子目录对应不同测试版本或页面分支,便于对比学习。页面涵盖典型OTA平台核心区块——顶部通栏导航、多条件搜索框、轮播图预留区域、热门目的地推荐列表、服务入口卡片组、底部版权及链接区等。类名命名贴近真实业务场景(如.header-nav、.search-box、.banner-wrap),有助于理解电商类网站的DOM结构组织方式。适合用于HTML结构训练、CSS布局实战、UI视觉还原参考,也适合作为教学演示案例或快速原型搭建的基础素材。


被折叠的 条评论
为什么被折叠?



