简介:这是一份面向前端初学者与进阶练习者的仿QQ音乐静态页面实战源码,基于HTML与CSS实现,适合想通过真实项目巩固布局与样式能力的开发者。压缩包共20个文件,约1.45MB,包含1个index.html主入口、2个CSS样式文件、9张jpg与8张png图片资源,图片主要用于音乐封面、图标与背景装饰,结构清晰便于直接运行与二次修改。页面综合运用HTML5语义标签、表单元素、多媒体标签,以及CSS选择器、盒模型、Flexbox与Grid布局、媒体查询响应式适配、过渡动画等知识点,并强调组件复用性,帮助理解如何构建高效且易维护的页面结构。目前已有4170人学习下载,可作为前端布局练习、课程设计或作品集参考,通过拆解目录与样式组织方式,掌握从结构搭建到视觉还原的完整思路。
1. 仿QQ音乐HTML静态网页:一个纯前端项目能还原到什么程度
打开一个仿QQ音乐的 HTML 静态网页,第一眼看到的往往是左侧歌单、中间轮播、底部播放条这三块。很多人以为这只是套个模板,但真正动手才发现,难点不在布局,而在「像不像」。QQ音乐的视觉密度很高:渐变背景、圆角卡片、悬浮阴影、播放条进度拖拽、歌词滚动,这些用纯 HTML + CSS + JavaScript 都能做,但要做到「一眼看上去是那个东西」,需要把设计拆成可复用的组件。
这个方向适合两类人:一是前端新手想找一个有真实感的练手项目,二是想验证「不依赖框架能不能撑起一个中后台级界面」的开发者。它解决的核心问题是:在没有后端、没有接口、没有构建工具的前提下,如何用静态文件还原一个成熟产品的交互骨架。下面按「先搭结构、再做交互、最后调细节」的顺序,把可复现的路径拆开。
2. 仿QQ音乐静态页面的骨架:从<!doctype html>到三栏布局
2.1 为什么先写<!doctype html>和lang="zh-cn"
很多新手直接复制一段<div>就开始写,结果在手机浏览器上字体忽大忽小,或者中文标点显示异常。标准开头应该是:
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>仿QQ音乐</title> </head> <body> <!-- 页面内容 --> </body> </html><!doctype html>告诉浏览器用标准模式渲染,避免怪异模式下的盒模型差异。lang="zh-cn"影响的是字体回退和屏幕阅读器发音,中文页面不写这个,某些安卓浏览器会默认用日文字体渲染汉字。viewport那行是移动端适配的底线,不写的话,手机打开会缩成一个指甲盖大小。
提示:静态页面不需要
meta里写keywords和description,那是给搜索引擎看的,本地练手项目写了也没用,反而增加噪音。
2.2 三栏布局用flex还是grid
QQ音乐桌面端是典型的「左窄右宽、底部通栏」结构。左侧歌单栏固定 200px 左右,右侧内容区自适应,底部播放条固定 72px 高度。我一般用flex做外层,因为底部播放条需要独立于内容区滚动:
body { margin: 0; display: flex; flex-direction: column; height: 100vh; overflow: hidden; } .main { flex: 1; display: flex; overflow: hidden; } .sidebar { width: 200px; background: #f5f5f5; overflow-y: auto; } .content { flex: 1; overflow-y: auto; padding: 20px; } .player-bar { height: 72px; background: #fff; border-top: 1px solid #e0e0e0; display: flex; align-items: center; padding: 0 20px; }这里的关键是body设height: 100vh加overflow: hidden,让整个页面不出现外层滚动条,滚动只发生在.sidebar和.content内部。如果直接用min-height,底部播放条会被内容顶下去,失去「固定底栏」的效果。flex: 1让.main占满剩余高度,.content再flex: 1占满剩余宽度,这是最省事的自适应方案。
2.3 轮播图不用插件,手写transform位移
QQ音乐首页顶部有一个横向轮播,通常 3 到 5 张图。用swiper当然快,但静态页面引入外部库会增加不确定性。手写一个最小轮播只需要一个overflow: hidden的容器和一条display: flex的轨道:
<div class="carousel"> <div class="track" id="track"> <img src="banner1.jpg" alt="banner1"> <img src="banner2.jpg" alt="banner2"> <img src="banner3.jpg" alt="banner3"> </div> </div>.carousel { width: 100%; overflow: hidden; border-radius: 12px; } .track { display: flex; transition: transform 0.4s ease; } .track img { width: 100%; flex-shrink: 0; }let index = 0; const track = document.getElementById('track'); const total = track.children.length; function goTo(i) { index = (i + total) % total; track.style.transform = `translateX(-${index * 100}%)`; } setInterval(() => goTo(index + 1), 3000);flex-shrink: 0是必须的,否则图片会被压缩到容器宽度以内,轮播就失效了。translateX的百分比是相对于轨道自身宽度,所以每张图占 100% 时,位移index * 100%正好对齐。setInterval做自动播放,实际项目中记得在鼠标悬停时clearInterval,否则用户想点第二张图时它自己滑走了。
3. 播放条与歌单交互:静态页面里的「假动态」怎么做
3.1 播放进度条拖拽:input[type=range]还是自定义div
QQ音乐的播放条有一个可拖拽的进度圆点。最省力的做法是用原生<input type="range">,但原生控件在不同浏览器里样式差异大,圆点大小和颜色很难统一。我一般用自定义div加mousedown/mousemove/mouseup三件套:
<div class="progress" id="progress"> <div class="progress-fill" id="progressFill"></div> <div class="progress-thumb" id="progressThumb"></div> </div>.progress { flex: 1; height: 4px; background: #e0e0e0; border-radius: 2px; position: relative; cursor: pointer; } .progress-fill { height: 100%; width: 0%; background: #31c27c; border-radius: 2px; } .progress-thumb { width: 12px; height: 12px; background: #31c27c; border-radius: 50%; position: absolute; top: 50%; left: 0%; transform: translate(-50%, -50%); }const progress = document.getElementById('progress'); const fill = document.getElementById('progressFill'); const thumb = document.getElementById('progressThumb'); let dragging = false; function setProgress(percent) { percent = Math.max(0, Math.min(100, percent)); fill.style.width = percent + '%'; thumb.style.left = percent + '%'; } progress.addEventListener('mousedown', (e) => { dragging = true; update(e); }); document.addEventListener('mousemove', (e) => { if (dragging) update(e); }); document.addEventListener('mouseup', () => { dragging = false; }); function update(e) { const rect = progress.getBoundingClientRect(); const percent = ((e.clientX - rect.left) / rect.width) * 100; setProgress(percent); }getBoundingClientRect()拿到的是进度条相对于视口的位置,e.clientX - rect.left就是鼠标在进度条内的偏移量。Math.max(0, Math.min(100, percent))防止拖出边界时数值溢出。mousemove绑在document上而不是progress上,是因为拖拽时鼠标可能移出进度条区域,绑在元素上会丢失事件。
3.2 歌单列表的「选中态」切换
左侧歌单点击后要有高亮,同时右侧内容区切换。静态页面没有真实数据,但可以用><ul class="playlist" id="playlist"> <li>const list = document.getElementById('playlist'); list.addEventListener('click', (e) => { const li = e.target.closest('li'); if (!li) return; list.querySelectorAll('li').forEach(item => item.classList.remove('active')); li.classList.add('active'); // 这里可以触发右侧内容切换 console.log('切换到歌单:', li.dataset.id); });
closest('li')比e.target.tagName === 'LI'更稳,因为li里面可能还有<span>或<i>图标,点到子元素时e.target不是li本身。事件委托只绑一个监听器,歌单项增减时不用重新绑定。
3.3 用localStorage记住上次选中的歌单
静态页面刷新后状态会丢,但可以用localStorage存一个简单的偏好:
// 点击时保存 localStorage.setItem('activePlaylist', li.dataset.id); // 页面加载时恢复 window.addEventListener('DOMContentLoaded', () => { const saved = localStorage.getItem('activePlaylist'); if (saved) { const target = list.querySelector(`[data-id="${saved}"]`); if (target) target.click(); } });localStorage存的是字符串,>:root { --primary: #31c27c; --primary-dark: #2aa86b; --bg: #f5f5f5; --text: #333; --text-light: #888; }
之后所有地方用var(--primary)。想换主题时,只改:root里的值,或者用body.dark覆盖:
body.dark { --bg: #1e1e1e; --text: #eee; --text-light: #aaa; }JavaScript 切换主题只需要document.body.classList.toggle('dark')。这个技巧在静态页面里性价比极高,代码量不大,但显得很「完整」。
5.2 歌词滚动用scrollTop还是transform
QQ音乐歌词是逐行高亮并向上滚动的。静态页面没有真实时间轴,但可以模拟:给每行歌词一个>const lyrics = document.getElementById('lyrics'); let currentLine = 0; const lineHeight = 32; // 每行歌词高度 function scrollToLine(i) { currentLine = i; lyrics.style.transform = `translateY(-${i * lineHeight}px)`; }
lineHeight必须和 CSS 里line-height一致,否则滚着滚着就偏了。如果歌词行高不固定,就得用offsetTop累加计算,复杂度会上去。静态页面里固定行高是最稳的。
5.3 一个我踩过的坑:transform和position: fixed冲突
底部播放条如果用position: fixed,而它的父元素有transform,fixed会相对于父元素定位而不是视口。这个玄学问题排查了我一个下午。后来把播放条移到body直接子元素,或者父元素不加transform,问题消失。静态页面布局尽量扁平,嵌套越深,这种坑越多。
5.4 验证方法:用 Lighthouse 跑一遍
静态页面没有接口,性能瓶颈通常在图片和字体。Chrome DevTools 的 Lighthouse 面板跑一次,重点看三个指标:LCP(最大内容绘制)是否超过 2.5s,CLS(累积布局偏移)是否小于 0.1,Total Blocking Time是否小于 200ms。图片加width和height属性可以显著降低 CLS,轮播图第一张用loading="eager",后面的用loading="lazy"。
我现在的习惯是:每做完一个静态页面,先不急着加新功能,用 Lighthouse 跑一遍,把红色项消掉再继续。这个习惯帮我省了很多「上线后才发现慢」的后悔药。仿QQ音乐这个项目,结构清晰、交互可控,是练手的好选择,但别指望它替代真实产品——它的价值在于让你把 CSS 布局、DOM 事件、浏览器兼容性这些基本功串一遍。希望帮到你。
本文还有配套的精品资源,点击获取