原生JS仿写QQ音乐页面:从结构拆解到交互实现完整指南

发布时间:2026/10/19 8:15:29

原生JS仿写QQ音乐页面:从结构拆解到交互实现完整指南 很多人学完前端三件套脑子里装了一堆标签和属性真要让手写一个页面却不知道从哪下笔。我自己的经验是直接挑一个成熟的音乐平台网站用纯HTML、CSS和JavaScript把它仿写出来是打通“会语法”和“会做页面”之间那段路最高效的方式。这篇文章就完整记录我用无框架方案实现一个仿QQ音乐页面的全过程包括怎么拆页面结构、怎么搭HTML骨架、CSS怎么还原布局和细节、原生JS怎么做动态交互以及过程中踩过的坑和最终的源码组织方式。它适合刚学完前端基础、想通过完整实战项目来验证能力的同学也适合准备面试前需要一份能讲清楚原理的练手作品的人。先交代一下成果整个页面只有一个index.html、一个style.css、一个main.js不需要安装任何依赖也不需要启动本地服务双击就能打开。页面上包含顶部导航、左侧菜单、推荐歌单卡片、热门排行榜、底部播放栏这些核心模块点击歌单卡片会切换播放信息底部按钮可以控制播放暂停进度条也会按秒推进。麻雀虽小但该有的交互链路是完整的。1. 仿写前先拆结构QQ音乐页面到底有哪些模块动手写代码之前最重要的事情其实是把目标页面拆成一个个独立的模块。这一步很多人会跳过结果写着写着页面越来越乱改一个地方崩三处。先花十几分钟把页面结构理清楚后面会轻松很多。1.1 一屏三区顶部导航、内容主体、底部播放条打开QQ音乐的PC页面从布局角度看其实就是一个非常经典的三段式结构。最上面是顶部导航负责产品Logo、搜索入口、主导航Tab和用户操作入口中间是内容主体主体内部又分成左侧的次级菜单栏和右侧的内容区内容区里有Banner焦点图、快捷分类、推荐歌单网格、排行榜这些模块最底下是一条固定的播放条不管页面怎么滚动它始终贴在视口底部显示当前播放歌曲的封面、标题、进度条和控制按钮。这个三段式结构几乎是所有音乐平台的通用布局所以我仿写的时候没有去逐像素复刻而是抓住了它的骨架逻辑顶部一条、底部一条、中间双栏。只要这三个层级关系对了页面散发出来的气质就已经像了七成。我把这个结构用注释形式写在HTML的最上面相当于给自己画了一张施工图页面结构规划 ├── header.site-header │ ├── .logo │ ├── .search-box │ ├── nav.nav-tabs │ └── .user-area ├── main.content │ ├── aside.sidebar │ │ └── .menu-list │ └── section.main-stage │ ├── .banner │ ├── .quick-entries │ ├── .section.song-list │ └── .section.top-list └── footer.player-bar ├── .song-info ├── .player-controls └── .player-progress这张结构图就是整个页面的顶层设计。后面所有CSS和JavaScript都是围绕这个结构展开的哪块区域显示什么、点击哪里触发什么事件、数据往哪个容器里塞一眼就能找到位置。拿到一个网站页面先画这种结构图再动手比直接开编辑器从头敲要靠谱得多。区域主要模块对应布局方式顶部导航产品Logo、搜索框、导航Tab、用户入口Flex横向排列内容主体侧边栏、焦点图、歌单网格、排行榜Flex Grid双栏底部播放条歌曲信息、控制按钮、进度条Fixed Flex悬浮1.2 为什么选择无框架方案可能有人会问仿写一个页面而已直接用Vue或者React不是更快吗确实更快但选择无框架是有意为之。原生HTML、CSS、JavaScript最大的优势是所有问题都暴露在外面没有框架帮你兜底。写Vue的时候一个v-for就把列表渲染了但你可能并不清楚数组和DOM节点之间到底怎么对应用React写组件状态一更新视图自动变但你可能说不上来事件委托和DOM操作的底层逻辑是什么。无框架方案要求你自己负责数据到视图的映射、自己处理事件绑定和状态切换。这个过程走一遍再去看任何框架理解成本都会低很多。另外从交付角度看无框架页面没有打包步骤不用维护node_modules一个文件夹拷到哪都能跑用作练习作品也特别方便。当然这不是说原生方案万能页面交互复杂到一定程度该上框架还是要上这个话题后面会单独讲。2. HTML骨架把每块区域翻译成标签结构拆完之后下一步就是用HTML把这些区域翻译成标签。这一步的核心原则有两个一是语义化该用header用header该用main用main不要一个div走天下二是提前想好哪些区域需要挂事件给它们设计好不会冲突的类名。2.1 顶部导航和主体内容的标签设计顶部导航我用了header标签内部再拆成四个子区域。Logo是一段文字加一个方形容器这里我没用图片直接用CSS画了一个近似音乐符号的图形减少外部资源依赖。搜索框是标准的input加button组合注意input要设置一个合适的placeholder否则默认边框和样式会很丑。主导航Tab用nav包裹一组a标签当前激活的Tab上加一个active类后面JS切换全靠这个类名找状态。主体部分我用aside作为左侧类别菜单用main作为右侧主舞台。右侧主舞台里Banner焦点图是一个带渐变背景的区块图片用占位图地址填充推荐歌单和排行榜都是典型的卡片集合用section做分区每个分区给一个标题栏和一个卡片容器。这里有个值得注意的细节卡片容器我故意没有写死里面的子元素数量而是让JavaScript根据数据数组的长度自动渲染。HTML里只留一个空的div.grid-container列表项全部由JS填充这样以后要加歌单、删歌单都不用动HTML结构。2.2 底部播放条的DOM结构播放条是全页面最特殊的一块因为它是固定定位始终悬浮在底部。结构上我用了footer标签内部按从左到右的顺序放三组内容歌曲信息、控制按钮、进度条。歌曲信息里面是一个封面缩略图、歌名和歌手名控制按钮组是上一首、播放/暂停、下一首三个按钮播放按钮的图标状态由JS切换进度条由一个range类型的input和两个时间文本组成。这里有个容易被忽略的坑播放条使用fixed定位之后会覆盖页面底部的内容。如果不提前处理页面滚动到底时最后一行文字会被播放条挡住。解决办法是给body设置一个不小于播放条高度的padding-bottom给内容留出安全区。另外播放条内部要用flex布局三个部分用flex: 0和flex: 1配合确保歌曲信息、按钮、进度条三者的宽度关系稳定不至于挤压变形。3. CSS还原布局、配色与动效的关键细节HTML骨架搭完之后页面是灰蒙蒙一片的接下来就是CSS的工作。这一章是整个仿写过程中最花时间的部分因为布局谁都会写难的是写出来之后能让人一眼看出“这就是音乐平台页面”的感觉。3.1 用CSS变量统一主题色与间距仿写页面最容易出现的毛病是颜色不一致这里一个绿那里一个绿看起来都是绿放在一起就是杂。解决办法是在:root里定义一组CSS变量把主色、辅色、背景色、文字色、卡片圆角、间距都统一管理起来:root { --primary: #31c27c; --primary-light: #eafaf2; --bg-page: #f7f8fa; --bg-card: #ffffff; --text-main: #1f1f1f; --text-sub: #888888; --radius-card: 12px; --space-sm: 8px; --space-md: 16px; --space-lg: 24px; --player-height: 80px; }有了这套变量之后写任何样式都不要直接写死颜色值而是引用变量。后面想换一套主题配色只需要改:root里的几个值全站所有组件都会跟着变。间距也是同理用变量的好处是页面看起来更整齐卡片与卡片之间的空隙完全一致不会出现某一块很挤、另一块很散的情况。3.2 双栏布局与歌单卡片的实现主体区域的双栏布局我用的是Flex和Grid配合完成。左侧栏固定宽度用flex属性让右侧自适应剩余空间右侧的歌单卡片区我用Grid的auto-fill和minmax实现自动换行.content { display: flex; gap: var(--space-lg); max-width: 1280px; margin: 0 auto; } .sidebar { flex: 0 0 200px; } .main-stage { flex: 1; min-width: 0; } .grid-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-md); }这里最值得说的是min-width: 0这一条。Flex子项默认有min-width: auto当内容过长时会强制撑开导致Grid列数计算失真。这个坑我在仿写的时候踩过右侧内容一多卡片被挤变形排查半天才想起来是Flex的隐式最小宽度在作怪。加了min-width: 0之后右侧区域才能正常收缩Grid才能按照真实宽度分配列。卡片本身的样式核心是圆角、阴影、hover上浮三个要点。圆角用变量统一阴影用浅色且不发灰的box-shadowhover时把卡片整体translateY向上移2px并加深阴影再加一个transition过渡手感立刻就出来了。封面图我强制设成正方形使用aspect-ratio: 1保证宽高一致再加border-radius让图片变成圆角下方的歌单标题和播放量用两行文本超出宽度的部分省略号处理。3.3 固定播放栏与进度条样式播放条固定到视口底部用position: fixed就可以但要注意它永远需要显示在最上层所以z-index要给一个足够大的值。内部布局继续用Flex歌曲信息部分用flex: 0固定宽度控制按钮居中进度条占据剩余空间。封面图在播放状态下我加了一个旋转动画用CSS的keyframes持续旋转播放时动画运行暂停时animation-play-state设为paused。进度条是range类型的input原生样式在不同浏览器里长得完全不一样所以要做自定义。基本思路是用appearance: none去掉默认外观再用::-webkit-slider-thumb和::-webkit-slider-runnable-track分别定义滑块和轨道。轨道我用线性渐变填充已播放部分的颜色滑块做成圆形白色按钮带一点点阴影。这里要注意Firefox用的是::-moz-range-track和::-moz-range-thumb必须单独写一套兼容样式。边距、按钮大小这些细节调对了播放栏才能有精致感不是一个孤零零的灰色长条。4. 原生JavaScript交互从静态页面到可点可播页面如果只到CSS这一步充其量是一张长得好看的图真正让它活过来的是JavaScript。这一章我会按照实际的开发顺序把数据渲染、Tab切换、播放模拟这三块内容分别讲清楚。4.1 用数据驱动方式渲染歌单卡片我强烈建议不要在HTML里手写十几个歌单卡片而是定义一份JavaScript数组然后用循环动态生成DOM。这样数据与视图分离后续无论是增删歌单还是接入真实接口都只改数据不动结构。数据结构大概长这样const songList [ { id: 1, title: 午后咖啡馆精选, count: 128万, cover: https://t.alcy.cc/mj }, { id: 2, title: 深夜情绪电台, count: 86万, cover: https://t.alcy.cc/mj }, { id: 3, title: 通勤路上的白噪音, count: 64万, cover: https://t.alcy.cc/mj } ]; function renderGrid(containerSelector, list) { const container document.querySelector(containerSelector); container.innerHTML ; list.forEach(item { const card document.createElement(div); card.className card; card.dataset.id item.id; card.innerHTML div classcard-cover img src${item.cover} alt${item.title} loadinglazy span classcard-count${item.count}/span /div p classcard-title${item.title}/p ; container.appendChild(card); }); }用innerHTML拼接字符串看起来很直接但构造HTML时如果数据来自用户输入一定要做转义防止注入问题。我这里数据是写死的所以没问题如果以后换成动态数据建议改用createElement配合textContent或者写一个转义函数把尖括号、引号都处理掉。用loadinglazy属性可以让首屏之外的图片懒加载页面打开速度会更快。4.2 Tab切换与事件委托顶部导航和左侧菜单都有多个Tab点击切换时通常需要做两件事把当前Tab的高亮状态移交给被点击的那一项同时让对应的内容区块显示出来。做法是先给所有Tab元素绑定点击事件在事件处理函数里统一移除旧的active类再给当前项加上。这里有个性能优化点就是不要给每个Tab单独绑监听器应该利用事件冒泡把监听器挂在它们的共同父层级上用event.target去判断实际点的是谁这就是事件委托。事件委托的代码很简洁整体思路是先确定监听范围再通过closest判断点击的目标是否落在Tab元素上。下面这个例子就在nav-tabs容器上挂了一个click监听不管里面新增多少Tab都不需要重新绑定。如果用户点的是Tab内部的文字或小图标closest也能正确向上找到Tab元素边界情况都被处理掉了。Tab映射到内容面板的方式我用的是data-target属性把目标面板的id放在Tab上JS读取后配合display切换显示隐藏这样点哪个导航就显示对应的内容区块逻辑一目了然。4.3 播放器的伪播放逻辑播放器部分我没有接真实音频而是用定时器模拟播放进度核心逻辑包括三个模块播放按钮状态切换、进度条自动推进、歌曲信息联动。点击歌单卡片时把当前歌曲的标题和歌手信息填充到播放条同时把封面图的地址替换成被点击卡片封面点击播放按钮判断当前是否处于播放状态是则暂停否则开始播放并切换按钮图标。进度条推进的原理很简单设置一个interval定时器每秒钟把当前进度加一如果播放时长是200秒进度到200就停止并切回暂停状态。同时要把进度值同步给range输入框和右侧的时间文本。暂停时清除定时器恢复播放时重新启动。所有这些状态我统一放在一个playerState对象里管理const playerState { currentIndex: 0, isPlaying: false, currentTime: 0, duration: 200, timer: null };把状态集中在一个对象里比散落在多个全局变量里强得多。后面加功能比如上一首下一首、随机播放都只需要操作这个状态对象再调用一次renderPlayerInfo更新界面即可。这种“状态集中函数更新视图”的思想其实就是现在各种框架状态管理的雏形用原生JS提前感受一遍非常有价值。如果你在仿写时想加一个真实的audio元素只要把定时器改成监听audio的timeupdate事件其他结构基本不用动。5. 无框架方案的边界容易翻车的几个细节写完一遍、跑通一遍才算真正开始理解这个方案。无框架虽然简单但在一些地方的体验天然不如工程化方案把这些边界摸清楚后面的路才能走稳。5.1 布局兼容与滚动类问题原生的position: sticky是我特别想夸又想骂的一个属性。它用来做导航吸顶效果很方便但它生效是有条件的父元素不能设置overflow: hidden而且元素本身要待在离它最近的滚动容器内。在仿写页面时如果你给body或者某个包裹容器顺手写了overflow-x: hiddensticky可能瞬间失效。排查这类问题最快的办法是用浏览器开发者工具选中那个应该吸顶的元素看它的定位上下文到底是谁再检查父级有没有overflow属性捣乱。另一个常见问题是播放条遮挡内容前面说了用body的padding-bottom解决。但别忘了移动端适配如果播放条高度在窄屏下发生了变化padding的值要跟着动态调整否则又会出现遮挡。还有一种情况是多列布局在内容不足时底部留白不一致这可以用Grid的align-content或者给容器一个min-height来兜底不要让背景色断层。5.2 图片、图标字体与资源依赖完全无依赖是不现实的页面上至少要放图片和图标。歌单封面我建议先用占位图服务写链接的时候注意尺寸参数避免加载超大原图。对于正式项目图片一定要给宽高或aspect-ratio否则图片加载前后页面会发生位移也就是常见的CLS布局偏移问题。图片加载失败时最好给img元素绑一个onerror事件替换成一张默认占位图不然页面出现碎图会非常难看。图标方面不需要为了几个按钮去准备一套雪碧图直接用一个图标字体库或者SVG就够。引入图标字体的方式是在HTML里link一个CDN样式但这种写法依赖网络如果离线打开页面图标会变成方块。为了解决这个问题要么把字体文件下载到本地要么改用内联SVG。我的建议是仿写阶段用CDN偷个懒交付前把用到的图标改成内联SVG既稳定又少一个请求。5.3 交互复杂后的代码组织无框架页面写多了最容易出现的问题就是main.js越来越长所有功能堆在一个文件里变量互相引用改一处崩一片。我的经验是当代码量超过两百行就应该做简单的模块拆分。原生JS可以用对象或IIFE把功能分区隔离开比如把数据处理、渲染函数、事件绑定分别放到独立的命名空间里再用一个init入口统一启动。分享一个小模式所有事件绑定都放在init函数里所有渲染函数都命名为renderXxx这样打开main.js第一眼看到的是项目全貌init里注册了哪几个事件、渲染了哪几个区域。后续维护新功能也只需要在对应区域追加代码不会动不动就动到全局。这个习惯一旦养成后面学任何框架都会很顺手因为框架里组件化、模块化的思路本质上是同一件事。6. 源码结构、运行方式与后续扩展建议最后把整个项目的文件组织和运行方式交代清楚。毕竟这篇标题带了“附源码”前面给出的代码片段已经可以拼装出一份可运行的基础版本这里再说明一下完整项目该怎么组织以及从仿写练习到真实项目之间还差哪几步。6.1 一个清晰的文件组织方案我做这个项目时文件组织刻意保持得很精简一共就三个文件零依赖目录结构如下music-page/ ├── index.html ├── css/ │ └── style.css └── js/ └── main.js打开运行极其简单直接把index.html拖进浏览器就行不需要启动本地服务器也不需要任何打包流程。之所以能这么省事是因为我把数据写死在main.js里没有发网络请求素材图片用了占位图没有涉及跨域。唯一需要说明的是如果以后要从文件系统加载JSON数据来渲染内容就可能会出现浏览器的跨域限制这种情况下建议用编辑器自带的Live Server插件起一个本地静态服务本质上这也是真实开发环境的标准姿势。6.2 从仿写走向真实项目的三个方向如果这个仿写项目已经跑通想继续往下走我个人建议按顺序试三个方向。第一个方向是接入真实的数据接口把歌单数据换成公开的或自建的接口这会引入fetch和异步流程你需要处理加载态、错误反馈这些更实际的问题。第二个方向是做响应式适配目前的布局在桌面上是完整的但一缩到手机宽度就会变形尝试用媒体查询重新排布双栏结构让侧边栏收起、歌单卡片变成两列这又是一块新的知识。第三个方向是把页面里的重复区块抽象成可配置的组件比如写一个createCardList(config)函数让不同分区共用同一套渲染逻辑这就是走向组件化思想的开始。我在实际完成这个项目之后最大的感受是无框架并不是“退而求其次”的选择而是一种很好的认知地基。因为它没有任何遮蔽DOM、样式、事件、状态全部坦露在眼前每一个现象都能找到直接原因。写完之后再去看那些框架的文档你会突然发现它们不过是把你自己手动做过的那些事情换了一种更优雅的封装方式而已。对于刚学完基础想进阶的人来说先把这一遍原生的路走通比直接上手框架要扎实得多。
延伸阅读

更多相关文章

2026/10/19 8:10:28

大学物理实验仿真软件:分光计实验预习与误差分析指南

简介:这份资源是面向高校物理实验学习者与授课教师的大学物理仿真实验软件第六辑,聚焦分光计实验的虚拟操作与原理探究。它解决了真实分光计设备昂贵、调节困难、光源不稳等痛点,让使用者在无风险环境中反复练习光路调整、色散元件选择与波长…

2026/10/19 8:10:28

整数翻转与32位溢出检测的底层原理与工程实践

1. 项目概述:一道被严重低估的“简单题”,藏着整套整数处理的底层逻辑LeetCode 7.Reverse Integer 这道题,标题写着“简单难度”,但凡是真正手敲过三遍以上、在面试中被追问过边界 case、或者用它带过新手刷题的开发者&#xff0c…

2026/10/19 10:32:01

GGUF推理引擎怎么选?Gufo与Halogen在千问模型上的实战对比

如果你最近一直在看本地大模型相关的帖子,肯定绕不开“千问”这个名字。中文能力扎实、开源权重全、量化生态成熟,让它成了很多人本地部署的第一个大模型。但模型选好了,真正让人纠结的是推理引擎:同一个 GGUF 文件,在…

2026/10/19 10:32:01

VS Code 配置 Docker 开发环境:从 Remote-Containers 到 Dockerfile 实战指南

很多人在学 Docker 的时候,第一个想法都是“这玩意儿到底能干嘛”。我当初也一样,光看文档里那些镜像、容器、仓库之类的概念,知道是什么,但始终找不到一个能让自己沉浸进去的切入点。后来我试着把每天写代码用的 VS Code 搬进 Do…

2026/10/19 10:32:01

Linux隐藏文件显示全攻略:从ls参数到常见误区

Linux里最让人“一头雾水”的操作之一,就是显示隐藏文件。我刚用Linux那会儿就被狠狠坑过:明明把配置文件下载到了家目录,打开终端用ls一瞧,干净的像新装系统,什么都没看到。后来才明白,文件名前面多了一个…

2026/10/19 10:32:01

Android Java蓝牙开发实战:BLE连接、通知与线程安全全链路解析

简介:本资源是一个面向Java开发者与物联网初学者的蓝牙通信实践项目,聚焦JSR-82规范在桌面端的落地实现,解决Java SE环境下缺乏原生蓝牙支持的开发痛点,适用于智能家居设备控制、低功耗传感器通信等IoT场景。压缩包为RAR格式&…

2026/10/19 10:26:53

MySQL CASE表达式实战:从排序到行转列,一篇讲透

相信不少写SQL的朋友都遇到过这样的场景:订单表里存的是 status1/2/3 ,老板却要你在报表里显示“已支付/已发货/已取消”;统计的时候,明明只想看一行结果,却要把不同状态的数据拆成好几列;排序的时候&…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/19 0:04:56

悟空开源无代码平台实战:从实体建模到应用设计避坑指南

简介:基于悟空开源无代码平台的多种应用设计源码,面向希望在不编写代码的情况下快速搭建CRM、HRM、财务系统、ERP、SRM等业务应用的企业与技术人员,通过拖放式界面和内置模板即可完成应用定制与部署。压缩包共697个文件,约10.74MB…

2026/10/19 0:04:56

基于.NET9的多租户权限系统:数据权限与动态API落地实践

简介:这份资源是一套面向 .NET 与 Vue 全栈开发者的中台权限管理系统,后端基于 .Net9,前端采用 Vue3 Element Plus,并配套 uniapp uviewui 移动端方案,实现前后端分离。系统覆盖多租户隔离、数据权限、动态 Api、任务…

2026/10/19 0:04:56

场地预约小程序毕设实战:SSM+微信小程序+MySQL数据库设计到避坑

简介:面向毕业设计场景的场地预约小程序完整项目,基于微信小程序SSMMySql技术栈开发,适合计算机相关专业学生作为选题参考、功能扩展或二次开发起点。系统实现场地类型管理、场地管理、预约管理、取消申请与押金退回等功能,分为管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑