做网页设计,最难的一环往往不是写代码本身,而是怎么把零散的需求拼成一个完整、能跑、还好看的页面。最近我把一个体育运动主题的网站首页从零写成了一套可直接运行的html+css+js网页设计案例,包含完整代码,从结构搭建到交互处理都做了完整的实现。这篇文章就把它拆开讲清楚:每一个模块为什么这么放、样式为什么这么定、脚本为什么这么写,以及我在调试过程中踩过的坑。想学网页设计但缺完整案例的新手,或者需要交作业、做作品集的朋友,完全可以照着这个案例自己敲一遍,改一改文案和配色就能变成自己的项目。
这个案例不依赖任何前端框架和构建工具,纯 HTML5 + CSS3 + 原生 JavaScript,拿来就能跑,这也就意味着你能直观地看到网页设计里最核心的三件事:结构、样式、行为是怎么协同工作的。我会从设计思路讲起,到页面骨架、CSS 布局、JS 交互,最后给出一份可以直接复制运行的三件套源码,再聊聊实战中遇到过的问题。文章里所有代码块都可以直接对照着敲,动手练一遍比看十遍都管用。
1. 案例定位与整体设计思路
1.1 为什么选体育运动主题
选体育运动主题不是随手拍的,这类网页有几个特别适合练手的特点。第一,运动类页面视觉冲击力强,配色可以大胆,深色底加高饱和亮色的方案很容易出效果,这比做企业官网那套素雅风格更能锻炼你对色彩的控制力。第二,运动健身页面通常包含的信息模块比较多——首屏轮播、课程卡片、数据统计、团队介绍、预约表单,一套组合拳下来,基本上覆盖了一个企业站该有的全部模块形态,练完这个案例,常规的信息展示类页面你都有能力下手。
第三点很实际,体育运动类页面目标人群明确,设计表达可以很直接。整个页面我用了深色背景加活力的橙色渐变作为主视觉,营造一种力量和速度感。这种风格在网上搜索热度也很高,像“体育运动网页设计”“html5网页设计”这些关键词,搜出来的首页基本都是类似的气质。跟着这个方向练,你做出的东西天然就更贴近市场需求。
1.2 页面模块与技术选型
这个案例规划了 7 个页面模块,我直接贴一张模块规划表出来:
| 模块 | 作用 | 核心HTML标签 |
|---|---|---|
| 顶部导航栏 | 全站导航 + 吸顶效果 | header、nav |
| Hero首屏轮播 | 展示品牌核心信息和图片 | section、div |
| 课程卡片区 | 展示热门课程 | section、article |
| 数据统计区 | 展示学员数、教练数等数据 | section、div |
| 教练介绍区 | 展示团队信息 | section、article |
| 预约表单区 | 收集用户信息 | form、input、select |
| 页脚 | 版权信息和辅助导航 | footer |
技术选型上就用最基础的三件套,原因有两个。一是学习价值,当你只用原生 HTML 和 JS 时,每一个布局都要自己动手算,每一种交互都要自己一行行写,这套基本功扎实了,以后学 Vue、React 都轻松得多,因为你理解底层发生了什么。二是部署成本,三个文件拷到服务器就能上线,不依赖 node_modules 那堆东西,尤其适合学生作业、个人作品集这种场景。
1.3 设计风格的确定
设计风格上我定了一个关键词:燃。色彩体系用深灰黑(#14151a)打底,主色用活力橙(#ff5a2a),强调运动中力量与速度的爆发感。字体上中文用系统字体栈,不需要额外引入字体文件,避免增加新手跑通的成本。圆角统一采用 12px 到 16px,卡片悬浮时做上浮动和阴影变化,这些都是偏现代互联网产品的设计语言,做出来不会显得像十年前的培训班作业。
一个好用的技巧是:先在纸上把页面框架画出来,标清楚每一个区块的职责和层级关系,再动手写代码。我每次做页面都是这个流程,直接打开编辑器就动手,很容易写着写着把页面做成了“内容堆砌”,模块之间没有主次。下面这个小图是我开工前的草图逻辑:
导航栏固定在顶部,下面依次是 Hero 轮播、四列课程卡片、四列数据统计、三列教练卡片、两列表单区。整体纵向节奏是“视觉冲击 → 课程推荐 → 数据背书 → 团队信任 → 转化收集”,这是从商业文案迁移过来的漏斗逻辑,好页面不应该只是好看,还应该有目的感。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 页面骨架搭建:HTML结构与语义化
2.1 全局结构规划
代码的第一步不是写样式,而是把 HTML 骨架搭对。骨架就像房子的承重墙,后面所有装修都依赖它。我在这个页面里全部使用了 HTML5 语义化标签,header、nav、section、article、footer,每一个块都有它明确的含义。这么做对搜索引擎更友好,对维护代码的人也更友好——看代码就像看文章大纲一样清晰。
为了保证页面在不同屏幕上打开时默认样式一致,我在 CSS 最开头做了一套样式重置:把 margin、padding 清零,盒模型统一成 border-box。这个步骤虽基础,但踩过坑的人都懂——浏览器默认样式不一致带来的折磨远比想象中烦人,尤其上下 margin 折叠、按钮自带边框这类问题,早处理早安心。
全局还有个固定宽度的容器类 container,所有内容区块都套上它,保证在大屏幕上内容不会无限拉伸。我设置的是 1200px 最大宽度,上下加左右 padding,再让 margin: 0 auto 水平居中。你以后做任何页面都可以沿用这个习惯,你会发现内容一下子变得整齐有序。
2.2 导航栏与Hero首屏的HTML实现
导航栏是用户打开页面后最先看到的元素,它需要包含 logo、导航菜单、CTA 按钮和移动端的汉堡按钮四个部分。需要注意,移动端菜单按钮和 PC 端导航菜单是并存的,只是通过 CSS 在不同屏幕宽度下控制显示和隐藏,这样 HTML 只需写一遍,维护起来不重复。
Hero 首屏我用了轮播图组件,三张幻灯片叠放在同一个容器里,通过 JavaScript 切换显示状态。每一张 slide 里包含标题、副标题和行动按钮,背景用内联 style 写成渐变效果,这样你下载代码打开就能看到完整的视觉效果,不用额外准备图片文件。真实项目中,把 background 的渐变值替换成图片路径就行。
导航和 Hero 的核心 HTML 长这样:
html复制<header class="navbar" id="navbar">
<div class="container nav-container">
<a href="#" class="logo">燃动<span>健身</span></a>
<nav class="nav-menu" id="navMenu">
<a href="#hero">首页</a>
<a href="#courses">课程</a>
<a href="#stats">数据</a>
<a href="#coaches">教练</a>
<a href="#booking">预约</a>
</nav>
<a href="#booking" class="btn btn-primary nav-cta">免费体验</a>
<button class="hamburger" id="hamburger" aria-label="打开菜单">
<span></span>
<span></span>
<span></span>
</button>
</div>
</header>
<section id="hero" class="hero">
<div class="hero-slider" id="heroSlider">
<div class="slide active" style="background: linear-gradient(135deg, #14151a 0%, #2b2430 100%);">
<div class="slide-content">
<h1>让运动成为习惯</h1>
<p>专业教练指导,科学训练计划,唤醒你的身体能量</p>
<a href="#booking" class="btn btn-light">立即预约体验</a>
</div>
</div>
<div class="slide" style="background: linear-gradient(135deg, #1a1030 0%, #3a1d3f 100%);">
<div class="slide-content">
<h2>新一代健身空间</h2>
<p>专业器械配置,全屋新风系统,智能体测设备</p>
<a href="#courses" class="btn btn-light">了解课程</a>
</div>
</div>
<div class="slide" style="background: linear-gradient(135deg, #0f1f1c 0%, #1d3a34 100%);">
<div class="slide-content">
<h2>30天见证改变</h2>
<p>定制化训练计划,带你突破瓶颈,重建身体状态</p>
<a href="#coaches" class="btn btn-light">认识教练</a>
</div>
</div>
</div>
<button class="slider-arrow prev" id="prevBtn">❮</button>
<button class="slider-arrow next" id="nextBtn">❯</button>
<div class="slider-dots" id="sliderDots">
<span class="dot active" data-index="0"></span>
<span class="dot" data-index="1"></span>
<span class="dot" data-index="2"></span>
</div>
</section>
写语义化标签时我心里一直有个原则:选标签是在描述内容是什么,而不是去定义内容长什么样。比如导航链接是一组链接列表,所以用 nav 包起来;轮播图里的每张卡片是独立内容,理论上可以用 article;但这里的 slide 更接近展示容器,不是独立文章,用 div 就够了。很多初学者习惯性全用 div,页面也能显示,但代码的可读性会差很多,等你要调试或接手别人的项目时,这种差距会非常折磨人。
2.3 内容区块与表单的HTML实现
课程卡片区我用了 article 标签,因为每张课程卡片都可以理解为一篇独立的小文章。卡片里包含课程名称、说明文字、训练目标列表,还有一个“了解详情”的链接。这个区域没有用图片,图标我用了一个彩色圆底文字来代替,既避免外部图片依赖,又保持视觉效果。
数据统计区是个很有意思的模块,用四个数字来建立用户信任感。这里有一个设计细节:数字的展示用了大号字体加橙色描边,而不是单纯加粗,这样更有设计感。教练介绍区是三张卡片并排,每个卡片用色块占位代替头像,真实项目里替换成教练照片即可。
预约表单区是整页转化的关键,包含姓名、手机号、课程下拉选择、备注文本域和一个提交按钮。表单的每一个输入项后面我都预留了一个错误提示的容器,这是给 JS 验证使用的。HTML 结构做得越干净,后面写 JS 就越省力。三个内容区块的 HTML 实现如下:
html复制<section id="courses" class="section courses">
<div class="container">
<div class="section-header">
<span class="section-tag">COURSES</span>
<h2>热门课程</h2>
<p>针对不同训练目标设计的课程体系,总有一款适合你</p>
</div>
<div class="course-grid">
<article class="course-card">
<div class="course-icon icon-strength">力</div>
<h3>力量训练</h3>
<p>自由重量与固定器械结合,打造核心力量和肌肉线条。</p>
<a href="#booking">了解详情</a>
</article>
<article class="course-card">
<div class="course-icon icon-cycling">骑</div>
<h3>动感单车</h3>
<p>高强度间歇骑行,快速燃烧卡路里,提升心肺耐力。</p>
<a href="#booking">了解详情</a>
</article>
<article class="course-card">
<div class="course-icon icon-yoga">柔</div>
<h3>高温瑜伽</h3>
<p>在恒温环境中完成体式练习,改善柔韧性与身体平衡。</p>
<a href="#booking">了解详情</a>
</article>
<article class="course-card">
<div class="course-icon icon-boxing">搏</div>
<h3>搏击操</h3>
<p>结合拳击与武术动作,释放压力同时提升身体协调性。</p>
<a href="#booking">了解详情</a>
</article>
</div>
</div>
</section>
<section id="stats" class="section stats">
<div class="container stats-grid">
<div class="stat-item">
<span class="stat-number" data-target="8000">0</span>
<span class="stat-label">累计学员</span>
</div>
<div class="stat-item">
<span class="stat-number" data-target="36">0</span>
<span class="stat-label">专业教练</span>
</div>
<div class="stat-item">
<span class="stat-number" data-target="24">0</span>
<span class="stat-label">训练课程</span>
</div>
<div class="stat-item">
<span class="stat-number" data-target="12000">0</span>
<span class="stat-label">训练面积</span>
</div>
</div>
</section>
<section id="coaches" class="section coaches">
<div class="container">
<div class="section-header">
<span class="section-tag">COACHES</span>
<h2>金牌教练团队</h2>
<p>平均从业8年以上的专业教练,为你的训练保驾护航</p>
</div>
<div class="coach-grid">
<article class="coach-card">
<div class="coach-avatar" style="background: linear-gradient(135deg, #ff5a2a, #ffb35a);">李</div>
<h3>李铭</h3>
<p>力量训练导师</p>
<p>国家职业健身教练认证,擅长增肌与体能突破。</p>
</article>
<article class="coach-card">
<div class="coach-avatar" style="background: linear-gradient(135deg, #1a6bff, #5ab8ff);">王</div>
<h3>王倩</h3>
<p>瑜伽与普拉提导师</p>
<p>全美瑜伽联盟认证导师,专注体态修复与柔韧训练。</p>
</article>
<article class="coach-card">
<div class="coach-avatar" style="background: linear-gradient(135deg, #17b978, #6bffb8);">陈</div>
<h3>陈杰</h3>
<p>搏击与体能导师</p>
<p>职业搏击运动员出身,擅长高强度间歇训练。</p>
</article>
</div>
</div>
</section>
<section id="booking" class="section booking">
<div class="container booking-container">
<div class="booking-info">
<span class="section-tag">BOOKING</span>
<h2>预约免费体验课</h2>
<p>填写表单即可获得一次免费体验机会,名额有限,优先安排。</p>
</div>
<form id="bookingForm" class="booking-form" novalidate>
<div class="form-group">
<label for="name">姓名</label>
<input type="text" id="name" name="name" placeholder="请输入姓名">
<span class="error-msg" id="nameError"></span>
</div>
<div class="form-group">
<label for="phone">手机号</label>
<input type="tel" id="phone" name="phone" placeholder="请输入11位手机号">
<span class="error-msg" id="phoneError"></span>
</div>
<div class="form-group">
<label for="course">体验课程</label>
<select id="course" name="course">
<option value="">请选择课程</option>
<option value="strength">力量训练</option>
<option value="cycling">动感单车</option>
<option value="yoga">高温瑜伽</option>
<option value="boxing">搏击操</option>
</select>
<span class="error-msg" id="courseError"></span>
</div>
<div class="form-group">
<label for="message">备注</label>
<textarea id="message" name="message" rows="3" placeholder="如有特殊需求请留言"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">提交预约</button>
<p class="form-success" id="formSuccess">预约信息已提交,我们会在24小时内与你联系。</p>
</form>
</div>
</section>
这里有一个容易忽略的细节:form 加了 novalidate 属性。这个属性是用来关闭浏览器自带的表单校验弹窗的,因为我们要用 JS 自己控制校验逻辑和错误提示的样式。如果不加这个属性,当表单校验失败时浏览器会先弹一个默认的提示气泡,样式很难看,而且和页面风格不搭。这个小细节,做过真实表单校验的人都有共鸣。
3. 视觉系统构建:CSS布局与动画细节
3.1 设计变量与基础样式
CSS 部分我首先定义了全局变量。移动互联网时代,改一个主题色就要满文件找色号的时代早就过去了,把颜色、阴影、圆角、间距这些设计常量抽成变量,是每个前端从业者的基本素养。这个案例里我只定义了一组最基础的设计变量,之后所有颜色和尺寸都引用它们。
基础重置部分我处理了 margin 清零、盒模型统一、图片最大宽度百分百防止溢出等操作。字体设置为系统字体栈,在移动端会自动调用系统中的 PingFang SC 或 Roboto,显示效果足够自然。所有按钮去掉了默认边框,统一定义了过渡动画,让交互反馈不是瞬变而是有轻微缓动。
3.2 核心模块布局与响应式处理
布局是这个页面 CSS 的重头戏。导航栏使用 Flex 布局,logo 在左,菜单在中间,CTA 按钮和汉堡按钮在右。Hero 区域是高度 100vh 的全屏布局,slide 里的内容用 Flex 居中。课程卡片和教练卡片用 Grid 布局,实现等宽的三列或四列效果。数据统计区我同样用 Grid 四列分布,数字用大字号加粗加橙色渐变效果。
响应式处理上我设置了两档断点:768px 是 Pad 竖屏和手机的分界线,480px 是窄屏手机的防线。768px 以下导航菜单会变成一个从右侧滑出的抽屉,课程卡片和教练卡片从多列变成单列。Grid 的响应式还有一种更省事的写法:直接用 repeat(auto-fit, minmax(280px, 1fr)),让浏览器自动决定列数。但为了让你能清晰理解断点控制的原理,这里还是用媒体查询手动控制列数,这在实际项目里可控性更高。
3.3 关键CSS代码与动画效果
我把这份案例里最关键的部分拆出来说明:吸顶导航阴影、Hero 轮播的叠放切换、卡片悬浮动效、表单错误提示。轮播切换我这里没有做左右平移的动画,而是用了透明度淡入淡出,实现成本和稳定性都更友好。
css复制:root {
--primary: #ff5a2a;
--primary-dark: #e0441a;
--dark: #14151a;
--dark-light: #1f2027;
--gray: #6b7280;
--light: #f7f8fc;
--white: #ffffff;
--shadow: 0 12px 30px rgba(0, 0, 0, 0.08);
--radius: 14px;
--transition: 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
color: var(--dark);
background: var(--light);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* 导航栏 */
.navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 72px;
background: transparent;
z-index: 1000;
transition: background var(--transition), box-shadow var(--transition);
}
.navbar.scrolled {
background: rgba(20, 21, 26, 0.92);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.nav-container {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
}
.logo {
font-size: 24px;
font-weight: 800;
color: var(--white);
text-decoration: none;
}
.logo span {
color: var(--primary);
}
.nav-menu {
display: flex;
gap: 32px;
}
.nav-menu a {
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
font-size: 15px;
transition: color var(--transition);
}
.nav-menu a:hover {
color: var(--primary);
}
.hamburger {
display: none;
flex-direction: column;
gap: 5px;
background: transparent;
border: none;
cursor: pointer;
padding: 4px;
}
.hamburger span {
display: block;
width: 24px;
height: 2px;
background: var(--white);
border-radius: 2px;
transition: transform var(--transition), opacity var(--transition);
}
/* Hero 轮播 */
.hero {
position: relative;
height: 100vh;
min-height: 480px;
overflow: hidden;
}
.slide {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.8s ease;
pointer-events: none;
}
.slide.active {
opacity: 1;
pointer-events: auto;
}
.slide-content {
text-align: center;
color: var(--white);
padding: 0 20px;
}
.slide-content h1,
.slide-content h2 {
font-size: 52px;
margin-bottom: 16px;
letter-spacing: 2px;
}
.slide-content p {
font-size: 18px;
opacity: 0.85;
margin-bottom: 32px;
}
.slider-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 46px;
height: 46px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, 0.2);
color: var(--white);
font-size: 20px;
cursor: pointer;
z-index: 10;
transition: background var(--transition);
}
.slider-arrow:hover {
background: rgba(255, 255, 255, 0.4);
}
.slider-arrow.prev {
left: 20px;
}
.slider-arrow.next {
right: 20px;
}
.slider-dots {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
z-index: 10;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
cursor: pointer;
transition: background var(--transition), transform var(--transition);
}
.dot.active {
background: var(--primary);
transform: scale(1.2);
}
导航栏这里我特意用了 position: fixed 而不是 sticky,因为 fixed 在移动端的兼容性表现更稳定。但它有一个副作用:导航会盖住页面顶部的 Hero 内容。所以我在 hero 区域设置 height: 100vh 后,它本身有足够的高度,内容不会被导航盖住。但后续其他区块如果有锚点跳转,会被 fixed 导航挡住顶部,这个我在后面踩坑部分会讲怎么处理。
轮播动画用 opacity 过渡而不是 left 位移,最大的好处是避免移动端对 transform 的兼容问题,而且实现原理更简单。幻灯片之间通过 pointer-events 控制交互,这样可以避免隐藏的幻灯片挡住底部按钮的点击。
3.4 卡片、表单与页脚的样式细节
课程卡片和教练卡片是页面内容的主体,它们的样式决定了页面整体质感。我用白色背景、浅色边框、14px 圆角、卡片阴影组合出一个干净的视觉面。悬浮时卡片上移 6px,阴影加深,这种微交互可以直接提升页面品质感,代码量却极少。
表单样式上,输入框统一浅灰背景、圆角边框,获得焦点时边框变成橙色,错误时边框和提示文字变成红色。页脚用深色背景收尾,和页面头部呼应,版权文字居中显示。这些样式都不复杂,但组合起来视觉完整性很好。
4. 交互逻辑实现:JavaScript核心功能
4.1 轮播图组件
轮播图是最能体现 JS 基本功的组件。我这里的实现逻辑很直白:维护一个当前的索引变量 currentIndex,每次切换时把上一张 slide 移除 active 类,给下一张加上 active 类。圆点指示器也根据 currentIndex 同步高亮。
自动播放功能用 setInterval 实现,每 3 秒切换到下一张。在手动点击左右箭头时,我会先清除当前定时器,再重新启动一个新的定时器,避免用户手动切换后定时器仍在跑而导致快速跳两下。这个“重置定时器”的小习惯非常实用,做任何自动播放的组件都会用到。
圆点点击的处理我用了事件委托,在父容器上监听点击事件,通过 e.target 的 data-index 属性判断点击的是哪个圆点。这是事件委托的典型应用,为动态生成的元素绑定事件时尤其方便。
4.2 移动端菜单与吸顶效果
移动端汉堡菜单的交互是最常见的“开关”逻辑。汉堡按钮被点击时,给导航菜单加一个 open 类,菜单就通过 CSS 的 transform 属性从右侧滑入。点击菜单里的任何一个链接,菜单自动收回。同时,汉堡按钮三条横线在菜单打开时会变成 X 形态,这里用了 CSS transform 旋转来实现,不额外创建元素。
吸顶效果监听 window 的 scroll 事件,当页面滚动超过 50px 时给导航栏加 scrolled 类,导航栏背景从透明变成半透明深色,同时加阴影。这里有个性能优化点:我直接绑定了 scroll 事件,没用 throttle。在这个页面规模下没有问题,但如果页面里监听滚动事件的函数很重,建议加一份节流函数,每 100ms 执行一次,避免滚动时频繁触发布局计算。
4.3 表单验证与提交反馈
表单验证是这个页面里 JS 交互的重头戏。我定义了三个校验规则:姓名不能为空,手机号必须是 11 位且以 1 开头,课程必须选择。验证触发有两种时机:失焦时单独验证当前字段,提交时全部验证一遍。
错误提示通过 JS 动态设置文本,而不是预先把错误文案写在 HTML 里。这样代码干净,错误信息集中管理,想改文案不用去 HTML 里翻。提交成功后,用 preventDefault 阻止浏览器的默认刷新行为,然后给成功提示容器加显示类,同时把表单重置。
javascript复制document.addEventListener('DOMContentLoaded', function () {
var currentIndex = 0;
var slides = document.querySelectorAll('.slide');
var dots = document.querySelectorAll('.dot');
var totalSlides = slides.length;
var sliderTimer = null;
function goToSlide(index) {
if (index < 0) {
index = totalSlides - 1;
} else if (index >= totalSlides) {
index = 0;
}
currentIndex = index;
slides.forEach(function (slide, i) {
slide.classList.toggle('active', i === currentIndex);
});
dots.forEach(function (dot, i) {
dot.classList.toggle('active', i === currentIndex);
});
}
function nextSlide() {
goToSlide(currentIndex + 1);
}
function startAutoPlay() {
stopAutoPlay();
sliderTimer = setInterval(nextSlide, 3000);
}
function stopAutoPlay() {
if (sliderTimer) {
clearInterval(sliderTimer);
sliderTimer = null;
}
}
document.getElementById('nextBtn').addEventListener('click', function () {
goToSlide(currentIndex + 1);
startAutoPlay();
});
document.getElementById('prevBtn').addEventListener('click', function () {
goToSlide(currentIndex - 1);
startAutoPlay();
});
document.getElementById('sliderDots').addEventListener('click', function (e) {
var target = e.target;
if (target.classList.contains('dot')) {
goToSlide(parseInt(target.getAttribute('data-index'), 10));
startAutoPlay();
}
});
startAutoPlay();
// 移动端菜单
var hamburger = document.getElementById('hamburger');
var navMenu = document.getElementById('navMenu');
hamburger.addEventListener('click', function () {
navMenu.classList.toggle('open');
hamburger.classList.toggle('active');
});
navMenu.addEventListener('click', function (e) {
if (e.target.tagName === 'A') {
navMenu.classList.remove('open');
hamburger.classList.remove('active');
}
});
// 导航栏滚动吸顶
var navbar = document.getElementById('navbar');
window.addEventListener('scroll', function () {
if (window.scrollY > 50) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
});
// 表单验证
var form = document.getElementById('bookingForm');
var nameInput = document.getElementById('name');
var phoneInput = document.getElementById('phone');
var courseSelect = document.getElementById('course');
var formSuccess = document.getElementById('formSuccess');
function validateField(input, errorId, rule) {
var errorEl = document.getElementById(errorId);
var message = rule(input.value.trim());
if (message) {
errorEl.textContent = message;
input.classList.add('input-error');
return false;
}
errorEl.textContent = '';
input.classList.remove('input-error');
return true;
}
function validators() {
return {
name: function (value) {
if (!value) return '请输入姓名';
if (value.length < 2) return '姓名至少2个字符';
return '';
},
phone: function (value) {
if (!value) return '请输入手机号';
var phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(value)) return '手机号格式不正确';
return '';
},
course: function (value) {
if (!value) return '请选择体验课程';
return '';
}
};
}
nameInput.addEventListener('blur', function () {
validateField(nameInput, 'nameError', validators().name);
});
phoneInput.addEventListener('blur', function () {
validateField(phoneInput, 'phoneError', validators().phone);
});
courseSelect.addEventListener('change', function () {
validateField(courseSelect, 'courseError', validators().course);
});
form.addEventListener('submit', function (e) {
e.preventDefault();
var rules = validators();
var nameOk = validateField(nameInput, 'nameError', rules.name);
var phoneOk = validateField(phoneInput, 'phoneError', rules.phone);
var courseOk = validateField(courseSelect, 'courseError', rules.course);
if (nameOk && phoneOk && courseOk) {
formSuccess.classList.add('show');
form.reset();
setTimeout(function () {
formSuccess.classList.remove('show');
}, 4000);
}
});
});
表单校验的正则表达式 /^1[3-9]\d{9}$/ 是一个很常用的手机号校验规则,但不代表它能覆盖所有情况,只是覆盖目前市面上大多数合法号段。如果你用它做真实业务,建议后端再做一次更完整的校验,前端校验只负责提升用户体验,不能当作数据安全边界。
5. 完整源码文件(可直接运行)
5.1 index.html 完整代码
这一节我把三个文件完整地贴出来。你只需要新建一个文件夹,把下面三份代码分别保存为 index.html、style.css、script.js,然后用浏览器打开 index.html,就能看到完整效果。我已在关键代码上加了中文注释,方便你对照理解。
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="style.css">
</head>
<body>
<header class="navbar" id="navbar">
<div class="container nav-container">
<a href="#" class="logo">燃动<span>健身</span></a>
<nav class="nav-menu" id="navMenu">
<a href="#hero">首页</a>
<a href="#courses">课程</a>
<a href="#stats">数据</a>
<a href="#coaches">教练</a>
<a href="#booking">预约</a>
</nav>
<a href="#booking" class="btn btn-primary nav-cta">免费体验</a>
<button class="hamburger" id="hamburger" aria-label="打开菜单">
<span></span>
<span></span>
<span></span>
</button>
</div>
</header>
<section id="hero" class="hero">
<div class="hero-slider" id="heroSlider">
<div class="slide active" style="background: linear-gradient(135deg, #14151a 0%, #3a1d3f 100%);">
<div class="slide-content">
<h1>让运动成为习惯</h1>
<p>专业教练指导,科学训练计划,唤醒你的身体能量</p>
<a href="#booking" class="btn btn-light">立即预约体验</a>
</div>
</div>
<div class="slide" style="background: linear-gradient(135deg, #1a1030 0%, #2b2430 100%);">
<div class="slide-content">
<h2>新一代健身空间</h2>
<p>专业器械配置,全屋新风系统,智能体测设备</p>
<a href="#courses" class="btn btn-light">了解课程</a>
</div>
</div>
<div class="slide" style="background: linear-gradient(135deg, #0f1f1c 0%, #1d3a34 100%);">
<div class="slide-content">
<h2>30天见证改变</h2>
<p>定制化训练计划,带你突破瓶颈,重建身体状态</p>
<a href="#coaches" class="btn btn-light">认识教练</a>
</div>
</div>
</div>
<button class="slider-arrow prev" id="prevBtn">❮</button>
<button class="slider-arrow next" id="nextBtn">❯</button>
<div class="slider-dots" id="sliderDots">
<span class="dot active" data-index="0"></span>
<span class="dot" data-index="1"></span>
<span class="dot" data-index="2"></span>
</div>
</section>
<section id="courses" class="section courses">
<div class="container">
<div class="section-header">
<span class="section-tag">COURSES</span>
<h2>热门课程</h2>
<p>针对不同训练目标设计的课程体系,总有一款适合你</p>
</div>
<div class="course-grid">
<article class="course-card">
<div class="course-icon icon-strength">力</div>
<h3>力量训练</h3>
<p>自由重量与固定器械结合,打造核心力量和肌肉线条。</p>
<a href="#booking">了解详情</a>
</article>
<article class="course-card">
<div class="course-icon icon-cycling">骑</div>
<h3>动感单车</h3>
<p>高强度间歇骑行,快速燃烧卡路里,提升心肺耐力。</p>
<a href="#booking">了解详情</a>
</article>
<article class="course-card">
<div class="course-icon icon-yoga">柔</div>
<h3>高温瑜伽</h3>
<p>在恒温环境中完成体式练习,改善柔韧性与身体平衡。</p>
<a href="#booking">了解详情</a>
</article>
<article class="course-card">
<div class="course-icon icon-boxing">搏</div>
<h3>搏击操</h3>
<p>结合拳击与武术动作,释放压力同时提升身体协调性。</p>
<a href="#booking">了解详情</a>
</article>
</div>
</div>
</section>
<section id="stats" class="section stats">
<div class="container stats-grid">
<div class="stat-item">
<span class="stat-number">8000+</span>
<span class="stat-label">累计学员</span>
</div>
<div class="stat-item">
<span class="stat-number">36+</span>
<span class="stat-label">专业教练</span>
</div>
<div class="stat-item">
<span class="stat-number">24+</span>
<span class="stat-label">训练课程</span>
</div>
<div class="stat-item">
<span class="stat-number">12000㎡</span>
<span class="stat-label">训练面积</span>
</div>
</div>
</section>
<section id="coaches" class="section coaches">
<div class="container">
<div class="section-header">
<span class="section-tag">COACHES</span>
<h2>金牌教练团队</h2>
<p>平均从业8年以上的专业教练,为你的训练保驾护航</p>
</div>
<div class="coach-grid">
<article class="coach-card">
<div class="coach-avatar" style="background: linear-gradient(135deg, #ff5a2a, #ffb35a);">李</div>
<h3>李铭</h3>
<p>力量训练导师</p>
<p>国家职业健身教练认证,擅长增肌与体能突破。</p>
</article>
<article class="coach-card">
<div class="coach-avatar" style="background: linear-gradient(135deg, #1a6bff, #5ab8ff);">王</div>
<h3>王倩</h3>
<p>瑜伽与普拉提导师</p>
<p>全美瑜伽联盟认证导师,专注体态修复与柔韧训练。</p>
</article>
<article class="coach-card">
<div class="coach-avatar" style="background: linear-gradient(135deg, #17b978, #6bffb8);">陈</div>
<h3>陈杰</h3>
<p>搏击与体能导师</p>
<p>职业搏击运动员出身,擅长高强度间歇训练。</p>
</article>
</div>
</div>
</section>
<section id="booking" class="section booking">
<div class="container booking-container">
<div class="booking-info">
<span class="section-tag">BOOKING</span>
<h2>预约免费体验课</h2>
<p>填写表单即可获得一次免费体验机会,名额有限,优先安排。</p>
</div>
<form id="bookingForm" class="booking-form" novalidate>
<div class="form-group">
<label for="name">姓名</label>
<input type="text" id="name" name="name" placeholder="请输入姓名">
<span class="error-msg" id="nameError"></span>
</div>
<div class="form-group">
<label for="phone">手机号</label>
<input type="tel" id="phone" name="phone" placeholder="请输入11位手机号">
<span class="error-msg" id="phoneError"></span>
</div>
<div class="form-group">
<label for="course">体验课程</label>
<select id="course" name="course">
<option value="">请选择课程</option>
<option value="strength">力量训练</option>
<option value="cycling">动感单车</option>
<option value="yoga">高温瑜伽</option>
<option value="boxing">搏击操</option>
</select>
<span class="error-msg" id="courseError"></span>
</div>
<div class="form-group">
<label for="message">备注</label>
<textarea id="message" name="message" rows="3" placeholder="如有特殊需求请留言"></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">提交预约</button>
<p class="form-success" id="formSuccess">预约信息已提交,我们会在24小时内与你联系。</p>
</form>
</div>
</section>
<footer class="footer">
<div class="container">
<p>© 2024 燃动健身 FitLife. 让运动成为习惯。</p>
</div>
</footer>
<script src="script.js"></script>
</body>
</html>
5.2 style.css 完整代码
CSS 文件是页面的视觉核心。我把文件分成几个大的段落:变量与重置、通用组件、导航、Hero、课程、数据、教练、表单、页脚、响应式。这种分块注释的习惯成本很低,但等你几个月后再回来看自己写的代码,你会庆幸当初写了这些注释。
css复制:root {
--primary: #ff5a2a;
--primary-dark: #e0441a;
--dark: #14151a;
--dark-light: #1f2027;
--gray: #6b7280;
--light: #f7f8fc;
--white: #ffffff;
--shadow: 0 12px 30px rgba(0, 0, 0, 0.08);
--radius: 14px;
--transition: 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
color: var(--dark);
background: var(--light);
line-height: 1.6;
}
a {
text-decoration: none;
color: inherit;
}
ul {
list-style: none;
}
img {
max-width: 100%;
display: block;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.btn {
display: inline-block;
padding: 12px 28px;
border: none;
border-radius: 50px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: background var(--transition), transform var(--transition), box-shadow var(--transition);
}
.btn-primary {
background: var(--primary);
color: var(--white);
}
.btn-primary:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(255, 90, 42, 0.35);
}
.btn-light {
background: var(--white);
color: var(--dark);
}
.btn-light:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(255, 255, 255, 0.25);
}
.btn-block {
display: block;
width: 100%;
}
/* 导航栏 */
.navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 72px;
background: transparent;
z-index: 1000;
transition: background var(--transition), box-shadow var(--transition);
}
.navbar.scrolled {
background: rgba(20, 21, 26, 0.92);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.nav-container {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
}
.logo {
font-size: 24px;
font-weight: 800;
color: var(--white);
}
.logo span {
color: var(--primary);
}
.nav-menu {
display: flex;
gap: 32px;
}
.nav-menu a {
color: rgba(255, 255, 255, 0.85);
font-size: 15px;
transition: color var(--transition);
}
.nav-menu a:hover {
color: var(--primary);
}
.hamburger {
display: none;
flex-direction: column;
gap: 5px;
background: transparent;
border: none;
cursor: pointer;
padding: 4px;
}
.hamburger span {
display: block;
width: 24px;
height: 2px;
background: var(--white);
border-radius: 2px;
transition: transform var(--transition), opacity var(--transition);
}
/* Hero */
.hero {
position: relative;
height: 100vh;
min-height: 480px;
overflow: hidden;
}
.slide {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.8s ease;
pointer-events: none;
}
.slide.active {
opacity: 1;
pointer-events: auto;
}
.slide-content {
text-align: center;
color: var(--white);
padding: 0 20px;
}
.slide-content h1,
.slide-content h2 {
font-size: 52px;
margin-bottom: 16px;
letter-spacing: 2px;
}
.slide-content p {
font-size: 18px;
opacity: 0.85;
margin-bottom: 32px;
}
.slider-arrow {
position: absolute;
top: 50%;
transform
