HTML5+CSS3+JS实战:6页面静态电影网站开发全解析
2026/9/23 9:08:22 网站建设 项目流程

简介:面向大学生HTML5期末作业与Web前端入门实践的猫眼电影网页设计成品,共6个页面,综合运用HTML5结构、CSS3样式与JavaScript交互,可帮助从零搭建风格统一、具备动态效果的电影信息展示站点。压缩包共36个文件、约693KB,其中包含6个html页面、8个css样式表、2个js脚本,以及png、jpg图片和ico图标素材,目录按页面与公共样式划分,便于定位和二次修改。目前已有76人学习下载。资源覆盖首页、电影详情、搜索结果、登录注册、影评与排行榜等模块,轮播图、搜索展示、用户信息交互等均由原生JavaScript实现,并针对不同屏幕做了响应式布局;页面间导航清晰,代码结构规范,适合作为期末作业提交、课程设计参考或前端综合练兵项目。

1. 从 6 个静态页面看 html5+css3+js 期末作业的真实边界

我最初拿到这套猫眼电影 6 页面资源时,以为最大的看点是轮播图和渐变背景。真正把所有 HTML 文件在本地跑起来之后才发现,这个项目最值钱的地方在于拆得很清楚:index.html 负责聚合入口,cinema.html 和 list.html 承担列表与搜索,move.html 是详情落地,Beihai.html 补一个内容专题;样式按通用、页面、动画拆成 8 个 CSS 文件,交互集中在 JS.js 和 index.js 两个脚本里。对于做大学生 HTML5 期末作业的读者来说,它示范了一套不依赖框架的 web 前端项目该如何组织;对已经有两年工作经验的工程师,也能从命名、兼容性和交互拆分里找到可以复盘的问题。整包没有后端,任何静态服务器都能打开,适合拿来练手和改造成自己的作品。

2. HTML5 语义化与 6 页面信息架构

有了整体印象之后,第一件事不是写代码,而是把 6 个 HTML 页面之间的关系先画出来。这个项目只有 6 个页面,但如果页面职责不清晰,后期改样式时就会陷入「改一个页面要同步改三个文件」的境地。

2.1 六个页面先对号入座

从资源包的文件清单可以直接反推出每个页面的职责。index.html 是首页,hotspot.html 做热门影片排行,cinema.html 是影院信息和排片,list.html 承接列表与搜索,move.html 是电影详情页(拼写应是 movie.html),Beihai.html 是一个以「北海」为主题的影评或专题落地页。下面这张表是这些页面在项目里的实际分工:

页面文件页面职责配套样式/脚本
index.html首页:轮播、热映推荐、排行榜入口common.css、index.css、lunbo.css、JS.js、index.js
hotspot.html热门影片排行,按热度/评分排序common.css、hotspot.css、JS.js
cinema.html影院列表与排片时间表common.css、cinema.css、JS.js
list.html电影列表与搜索结果页common.css、list.css、JS.js
move.html电影详情:海报、简介、评分、影评common.css、move.css、JS.js
Beihai.html影评专题落地页common.css、Normal flow.css、JS.js

从这个表能看出两个问题。第一,页面数量和样式文件不是一一对应,有些页面复用 common.css 和 Normal flow.css,只有专属模块才单独建 CSS,这个思路是对的。第二,Beihai.html 和 move.html 的命名都不够语义化。Beihai 是专题内容名,不是通用页面类型,下次再做一个专题得再建一个 Jiaozhou.html;move.html 更是 movie.html 的误拼。期末作业我一般建议直接用 review.html、movie.html,让文件名反映页面类型而不是业务位置,这样导航和 CSS 命名都会顺很多。

2.2 公共 header 用语义化标签搭出来

六个页面都需要的公共部分是顶部导航、搜索框、登录入口和底部信息。用 HTML5 语义化标签去写,比满屏 div 更容易被老师挑出亮点。常见的做法是 header 包裹 brand 和 nav,搜索用 form 而不是 div,登录入口放在 header 右侧。下面这段结构可以原样套到所有页面里:

<header class="film-header"> <a class="brand" href="index.html"> <img src="img/logo.png" alt="猫眼电影" width="120" height="40"> </a> <nav class="film-nav" aria-label="主导航"> <a href="index.html" aria-current="page">首页</a> <a href="hotspot.html">热映</a> <a href="cinema.html">影院</a> <a href="list.html">榜单</a> <a href="move.html">详情</a> <a href="Beihai.html">影评</a> </nav> <form class="search-form" action="list.html" method="get"> <input type="search" name="keyword" placeholder="找电影 / 找影院"> <button type="submit">搜索</button> </form> <a class="user-entry" href="list.html#login"> <img src="img/yonghu.png" alt="登录"> </a> </header>

这里有几个细节值得注意。aria-current="page"用来标记当前页,CSS 可以用[aria-current="page"]选中它做高亮,比手动加 class 更符合语义。搜索框为什么用 form 包起来?因为用户在输入框按回车时,form 默认会提交,页面自动跳到 list.html 并通过 URL 携带 keyword 参数,这样 list.html 不需要额外 JS 就能拿到搜索词。img标签上的 width 和 height 是宽高占位,避免图片加载过程中页面布局跳动,这在期末作业里很少见,但确实是加分项。登录入口直接指向项目里没有单独登录页,而是锚点到 list.html 的登录模块,这个做法节省页面数量,也说明页面跳转不一定非要新建 HTML。

2.3 main 内部用 section/article 组织首页内容

头部之后是首页主体。轮播区、正在热映、即将上映、排行榜这些模块,建议用 section 划分,每张电影卡用 article。section 表示一个独立主题区块,article 表示可以被单独引用的内容单元,这样的结构读起来比一层层 div 清晰得多。

<main class="home-main"> <section class="carousel" aria-label="推荐影片"> <div class="carousel-track"> <div class="carousel-slide"><img src="img/1.jpg" alt="热映影片 A"></div> <div class="carousel-slide"><img src="img/2.jpg" alt="新片推荐 B"></div> <div class="carousel-slide"><img src="img/3.jpg" alt="会员专享 C"></div> </div> </section> <section class="hot-movies"> <h2>正在热映</h2> <div class="movie-grid"> <article class="movie-card"> <img src="img/4.png" alt="示例影片海报"> <h3><a href="move.html?id=1">示例影片</a></h3> <p class="score">9.2 分</p> </article> </div> </section> </main>

轮播区域用 section 带上 aria-label,因为首页可能只有一个轮播,但为了读屏用户能识别它的用途,最好给出可访问名称。hot-movies 区域里我加了一个 h2,这是很多作业容易漏掉的地方:section 通常需要一个标题,否则读屏用户无法感知这个区块的主题。电影卡片用 article 包起来,里面再放链接到 move.html?id=1,这里使用 query string 传递电影 id,详情页的 JS 可以通过 location.search 解析出要展示哪部电影,这种传参方式不依赖服务端,静态页面也能实现。

页脚部分和头部同理,六个页面共用一个 footer 结构,不要每个页面复制不同版本。常见做法是把版权信息、二维码图片(资源包里的二维码.jpg 和 shouji.png 就是用来做移动端入口的)和备案号放在一起,底部背景用深色。由于是期末作业,footer 不需要写得太重,但每个页面的 footer 文案必须一致,否则在页面间跳转时会被一眼看出是拼凑的。

3. CSS3 布局体系:从 Normal flow.css 到 lunbo.css 的动画参数

HTML 结构明确了,接下来是样式。这套项目的 CSS 文件有 8 个,初看会觉得琐碎,拆开看其实分三类:全局基础(common.css、Normal flow.css)、页面专属(index.css、hotspot.css、cinema.css、list.css、move.css)、动画(lunbo.css)。这种拆法最大的好处是页面之间不互相污染,坏处是文件多了以后命名必须更克制。

3.1 样式文件先分类,再决定怎么写

下表把每个 CSS 文件的职责列出来。这个清单是判断一个 CSS 文件是否被塞进多余样式的检查表:如果一个页面专属 CSS 里出现了 reset 或全局字体设置,说明职责分界已经模糊了。

样式文件覆盖范围典型内容
common.css全局公共盒模型重置、body 字体、按钮、输入框
Normal flow.css文档流基础排版标题、段落、列表的默认间距
index.css首页轮播容器、热映榜单布局
lunbo.css轮播动画@keyframes、animation 属性
hotspot.css热映排行页排行数字、hover 状态
cinema.css影院页影院卡片、排片表格
list.css列表与搜索列表项、分页器、空状态
move.css详情页海报栏、演职员列表

这里要特别点出两个命名问题。第一个是 Normal flow.css,文件名里带空格和首字母大写,浏览器和构建工具可以处理,但部署到 Nginx 或 Linux 服务器时,大小写敏感的文件系统会导致 404,空格在 URL 里也会被编码成 %20。第二个是 move.css 对应 move.html,业务上没问题,但容易和 move 动画的含义混淆,建议统一改成 movie.html/movie.css。命名问题不影响这次作业演示,但本科阶段就建立「机器可读」的命名习惯,工作后会少踩很多坑。

3.2 Flex 搭头部,Grid 搭卡片网格

电影类站点首页的视觉重点在头部导航和卡片网格。CSS3 的 Flexbox 处理水平分布比 float 省事,Grid 处理卡片自动换行更直观。先看头部:

/* common.css 片段 */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font: 14px/1.6 "PingFang SC", "Microsoft YaHei", sans-serif; background: #f5f5f5; } .film-header { position: sticky; top: 0; z-index: 100; display: flex; justify-content: space-between; align-items: center; height: 56px; padding: 0 24px; background: #e54847; } .film-nav { display: flex; gap: 8px; } .film-nav a { padding: 6px 14px; border-radius: 4px; color: #fff; text-decoration: none; } .film-nav a[aria-current="page"] { background: rgba(255, 255, 255, 0.25); }

参数解释:justify-content: space-between使 logo、导航、搜索、用户入口在水平方向两端分布,即使中间导航链接数量变了,也不会因为手写 margin 而错位。position: stickytop: 0让 header 在滚动时吸顶,注意 sticky 是相对最近滚动容器生效,如果它的父级设置了 overflow 非 visible,吸顶会失效。gap: 8px替代逐个设置 margin,是 Flexbox 布局里更省心的间距方案。

接下来是首页卡片网格和卡片 hover 效果:

/* index.css 片段 */ .movie-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 16px; padding: 16px; } .movie-card { overflow: hidden; border-radius: 6px; background: #fff; transition: transform 0.3s ease, box-shadow 0.3s ease; } .movie-card:hover { transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); }

这里最值得写进注释的是repeat(auto-fill, minmax(150px, 1fr)):每一列至少 150px,视口宽度足够就自动增加一列,比写死三列更抗缩放。.movie-card:hovertranslateY(-4px)做卡片浮起效果,transition只写了发生变化的属性而不是 all,避免鼠标经过时无关属性也做无意义过渡,这是性能习惯,不是放大镜级别的细节。

3.3 lunbo.css 中 CSS3 动画关键参数

轮播是首页交互核心。纯 CSS 方案适合期末演示,不依赖 JS 也能运行。实现方式是 track 宽度 300%,三张 slide 各占 1/3,通过 @keyframes 改变 transform 位移:

/* lunbo.css */ .carousel { overflow: hidden; } .carousel-track { display: flex; width: 300%; animation: carousel-slide 12s ease-in-out infinite; } .carousel-slide { width: 33.3333%; flex-shrink: 0; } .carousel-slide img { display: block; width: 100%; height: 200px; object-fit: cover; } @keyframes carousel-slide { 0%, 30% { transform: translateX(0); } 34%, 63% { transform: translateX(-33.3333%); } 66%, 95% { transform: translateX(-66.6666%); } 100% { transform: translateX(0); } }

参数含义先说清楚:12s是整个动画周期,ease-in-out控制变速曲线,infinite让动画无限重复播放。动画执行次数由animation-iteration-count控制,值为 2 就正向播两次,值为 infinite 才适合轮播这种首尾相接场景。关键帧里 0%-30% 让第一屏停留,34%-63% 切到第二屏,66%-95% 切到第三屏,最后 100% 瞬移回起点,三屏各停留约 3 秒,循环没有画面断档感。33.3333%对应三张 slide 各占 1/3;如果改成 5 屏,这个百分比要同步调整。object-fit: cover让图片按容器比例裁剪而不是拉伸变形。

这里需要补两个容易被忽略的参数。第一,想让动画延迟 1 秒再启动,写animation-delay: 1s,但要配合animation-fill-mode: backwards,否则延迟期间元素处于未定义状态,会出现闪动。动画完成后要保持最后一帧,用animation-fill-mode: forwards;兼顾延迟阶段和结束阶段则直接写both。第二,animation-direction: alternate会让动画正向播完再反向播放,适合跑马灯、影评渐变这类往复场景,配合 infinite 时方向会来回切换,轮播这种首尾相接的场景最好别用,因为正反方向在首尾处的跳变会露出破绽。把这些参数的取值背景写在注释里,页面一看就是自己调过的。

3.4 媒体查询兜底

最后给一段移动端媒体查询:

@media (max-width: 768px) { .film-header { height: auto; flex-wrap: wrap; padding: 8px 12px; } .film-nav { order: 3; width: 100%; overflow-x: auto; } .movie-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; } }

说明:flex-wrap: wrap让头部在手机端换行,order: 3把导航挪到最后一行,overflow-x: auto让导航在窄屏上横向滑动。网格从 auto-fill 改成固定两列,手机端卡片不会太小。这三个兜底基本能保证 375px 宽度下不出现明显布局错乱。

4. JavaScript 交互:轮播、搜索过滤与 fetch 数据替换

HTML 负责结构、CSS 负责视觉,JS 在这个项目里负责三件事:轮播自动播放、搜索过滤、表单交互。资源包里有 JS.js 和 index.js 两个脚本,从分工上看,JS.js 更像工具函数和数据集合,index.js 更像首页初始化脚本。把数据、工具函数和页面逻辑分开组织,比把所有代码堆在一个文件里更容易扩展。

4.1 轮播的 JS 控制:setInterval 与悬停暂停

第 3 章已经给了纯 CSS 轮播。如果作业要求「鼠标悬停暂停」,纯 CSS 做不到,需要改用 JS 控制。二者只能选一种,不要在一个元素上同时挂 animation 和 JS 改 transform,否则动画帧会覆盖 JS 写入的位移值,出现抽搐。JS 版常见做法是去掉 track 的 animation,改用 setInterval 定时切换,配合 transition 产生滑动动画。

// index.js —— JS 控制的轮播 (function () { 'use strict'; const track = document.querySelector('.carousel-track'); const slides = track.children; const total = slides.length; const interval = 4000; let current = 0; let timer = null; function moveTo(index) { const width = track.clientWidth; track.style.transition = 'transform .45s ease'; track.style.transform = 'translateX(-' + index * width + 'px)'; } function next() { current = (current + 1) % total; moveTo(current); } timer = setInterval(next, interval); track.addEventListener('mouseenter', function () { clearInterval(timer); }); track.addEventListener('mouseleave', function () { clearInterval(timer); timer = setInterval(next, interval); }); })();

逻辑拆解:track.clientWidth每次调用时读取容器实时宽度,窗口缩放后位移依然正确,不需要写死 1200px。(current + 1) % total取余实现「到最后一张回到第一张」的循环,比手动 if 判断越界更简洁。mouseenter 和 mouseleave 配对使用,进入时清掉计时器,离开时重新创建,悬停暂停与离开恢复是真实可用的。transition: transform .45s ease在每次 moveTo 都会重新赋值,和 transform 同时作用,实现滑动。两个参数最值得调:interval 控制停留时长,4000ms 是常规值;transition 时长控制在 0.4s 到 0.6s 之间,太长拖沓,太短像硬切。

4.2 list 页搜索:filter 追加 includes,再用 map 渲染

搜索是信息类网站的常见交互。list.html 作为搜索目标页,需要从 URL 读取 keyword,同时也要响应用户在页面里再次输入的过滤。先把本地电影数据组织成数组,再在 input 事件里做过滤和渲染。

// JS.js —— 数据定义与通用渲染 const movieList = [ { id: 1, name: '示例影片', genre: '剧情', score: 9.2 }, { id: 2, name: '动画示例', genre: '动画', score: 8.8 }, { id: 3, name: '科幻示例', genre: '科幻', score: 7.9 } ]; function renderMovies(list, container) { if (!container) return; if (list.length === 0) { container.innerHTML = '<p class="empty">没有找到相关电影</p>'; return; } container.innerHTML = list .map(function (item) { return ( '<article class="movie-card">' + '<h3><a href="move.html?id=' + item.id + '">' + item.name + '</a></h3>' + '<span class="genre">' + item.genre + '</span>' + '<span class="score">' + item.score.toFixed(1) + '</span>' + '</article>' ); }) .join(''); }

这段代码的关键在 filter 和 map 的配合。filter 不会修改原数组,每次输入都基于 movieList 重新筛选,避免连续输入时把上一次的结果再过滤一遍。map 把对象数组转换成 HTML 字符串数组,最后用 join 拼接成一整段 innerHTML,比在循环里反复 innerHTML += 要高效,也避免每次都触发浏览器重排。toFixed(1) 把 9.2 显示成一位小数,更整齐。

接着是输入监听:

// list.html 中初始化搜索 searchInput.addEventListener('input', function () { const keyword = searchInput.value.trim().toLowerCase(); const matched = movieList.filter(function (item) { return ( item.name.toLowerCase().includes(keyword) || item.genre.toLowerCase().includes(keyword) ); }); renderMovies(matched, movieListContainer); });

includes 是 ES6 字符串方法,用来判断字符串是否包含关键字,但它大小写敏感,所以输入和源数据都先 toLowerCase 再比较。trim 去掉首尾空格,避免用户多敲一个空格就搜不到结果。如果页面是从搜索框 GET 跳转过来的,还要把 URL 里的 keyword 回填到输入框:

const params = new URLSearchParams(location.search); const keyword = params.get('keyword') || ''; if (keyword) { searchInput.value = keyword; searchInput.dispatchEvent(new Event('input')); }

URLSearchParams 是专门的查询参数解析接口,比手动 split 安全,也不需要手动 decodeURIComponent。最后一行 dispatchEvent 是为了让页面在打开时自动执行一次过滤,初始渲染时不用再把过滤逻辑写一遍。这个手法在期末作业里不容易被注意到,但面试时可以讲一整段。

4.3 从写死数组切换到 fetch 加载 JSON

当电影数量超过 30 条,数据写在 JS 里很难维护。常见做法是把数据抽成 data/movies.json,用 fetch 请求。静态页面用 fetch 时必须注意:直接双击打开 HTML 时 fetch 会被 CORS 拦截,必须通过本地静态服务器访问。

fetch('data/movies.json') .then(function (res) { if (!res.ok) throw new Error('请求失败,状态码 ' + res.status); return res.json(); }) .then(function (data) { renderMovies(data, movieListContainer); }) .catch(function (err) { console.error(err); renderMovies(movieList, movieListContainer); // 本地数据兜底 });

这里为什么先检查 res.ok:fetch 只有网络层失败才会 reject,HTTP 404 或 500 时 Promise 状态仍然是 resolved,不检查的话后续解析会拿到错误数据,页面崩溃只在控制台里有痕迹。catch 里回落到 JS.js 中写死的 movieList,这个「先用本地数据兜底,再尝试请求远端」的渐进增强思路,比严格的前后端分离更适合作业场景,还能让评委断网时也能打开页面。

4.4 表单校验与交互点汇总

六个页面里的登录、注册、影评提交这类表单,不需要复杂框架。用 HTML5 原生属性加少量 JS 即可:

<input type="text" name="username" required pattern="[A-Za-z0-9_]{4,16}" title="4-16位字母、数字或下划线">

form 提交时调用 checkValidity,返回 false 时用 reportValidity 显示浏览器默认提示。验证码倒计时用 setInterval 每 1 秒把剩余秒数减 1,到 0 时 clearInterval 并恢复按钮文案,倒计时的第一行就button.disabled = true,避免用户重复点击。

交互点数据来源实现方式涉及文件
轮播自动播放页面图片setInterval + transformindex.js
电影列表渲染本地数组map + joinJS.js
搜索过滤URL 参数 / input 输入filter + includesJS.js、list.html
表单校验HTML5 pattern + checkValidity原生校验JS.js
验证码倒计时本地倒数setIntervalJS.js

5. 本地静态服务器、兼容性修复与期末验收清单

5.1 先跑起来再谈优化

用本地静态服务器启动,在项目根目录执行:

python3 -m http.server 8080

然后在浏览器打开http://localhost:8080/index.html。8080 是端口号,可以换成 8000 或 8081,只要不和其他进程冲突。没有 Python 就改用npx serve,只需临时安装一个依赖。注意不要直接双击 HTML 文件,file:// 协议下 fetch、ES6 module、部分字体文件都会被拦截,还会因为相对路径解析问题出现图片 404。资源包里Normal flow.css的文件名含空格,在 Windows 上双击可能没问题,但放在 Linux 服务器上就要留心大小写和空格,最好提前改成 normal-flow.css。

5.2 兼容性三处要修

第一处是 transform 和 transition 的前缀,讲师演示机如果浏览器内核偏老,动画可能不生效,可以在轮播和 hover 效果的属性行前补-webkit-transform-webkit-transition。第二处是 CSS Grid 的 auto-fill 在旧 Edge 下行为异常,保险做法是给 .movie-grid 加一层display: flex; flex-wrap: wrap兜底,再用@supports (display: grid)覆盖成 Grid。第三处是 JS 的 includes 和 URLSearchParams 在 IE11 不存在,必须兼容 IE 就改成 indexOf 和手写解析;期末作业一般只保证 Chrome 和 Edge 即可。

5.3 验收清单

检查项通过标准
导航连通6 个 HTML 页面之间互相跳转无 404,当前导航有高亮
轮播自动播放,悬停暂停,离开恢复,无跳帧抽搐
搜索list.html 能读取 keyword 并过滤,无结果有空状态
表单空值、格式错误有提示,不产生无意义提交
图片所有 img 均显示,Network 面板无 404
JS 报错切换所有页面后 Console 无报错
移动端375px 宽度下不出现横向滚动条

5.4 最后动手调一遍

资源跑通以后,真正提升完成度的是资源质量而不是再写一轮功能。第一,把 img 目录下超过 200KB 的 jpg/png 压缩到 80KB 左右,首屏加载速度会明显变快;第二,给非首屏图片加上loading="lazy",让浏览器只在接近视口时才发起请求;第三,把 JS.js、index.js 里的 console.log 全部清掉,避免老师打开控制台时看到调试残留。验证时不要只点页面看效果,打开 DevTools 的 Network 面板确认每个资源返回 200,再切到 Lighthouse 跑一遍 Performance,分数低于 90 就从图片压缩和懒加载两条路开始改。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询