简介这是一套可直接运行的网页版年度音乐报告前端模板面向具备基础HTML/CSS/JS能力的前端学习者与开发者用于复刻网易云音乐年度听歌报告的交互逻辑与视觉风格解决从零搭建数据可视化报告页面的问题也可用于个人年度总结、企业用户行为报告或活动数据展示等二次开发场景。资源包共25个文件约825KB以9个HTML页面构成多页报告结构6个JS文件承载滑动翻页、数据渲染与埋点逻辑3个CSS文件统一管理样式另含PNG图片、部署说明文档及仓库同步脚本整体采用静态文件架构无需后端即可本地打开查看效果。目前已有20人学习。读者可借此掌握Swiper翻页、二维码生成、资源哈希命名与加载优化等实用技巧并基于清晰的目录结构快速定制专属报告页面。1. 仿网易云年度听歌报告一份能直接跑起来的前端源码包年底朋友圈被各种年度报告刷屏的时候我盯着网易云那份翻页动画看了很久——每一页的渐变背景、数字滚动、卡片翻转其实都是很标准的前端活儿。这份源码包就是把那套视觉语言拆开揉碎用纯 HTML、CSS、JS 复刻出来不依赖任何框架也没有构建工具双击index.html就能看到完整效果。它解决的是「想做一个数据可视化年度报告页面但不知道从哪下手」的问题适合前端新手练手、也适合接私活时当模板改。整套结构围绕 Swiper 翻页展开配合 CSS 动画和 JS 数据渲染把「听歌时长」「最爱歌手」「单曲循环」这些模块串成一条完整的叙事线。你拿到手就能改数据、换配色、调动画节奏不用从零搭骨架。2. 拆开源码包目录结构与 Swiper 翻页机制2.1 文件清单与职责划分拿到一个前端源码包我第一件事不是打开index.html看效果而是先看目录结构判断它是不是「能改」的。这份包的目录很干净没有多余的构建产物和依赖目录annual-report/ ├── index.html # 页面骨架所有 section 写在这里 ├── css/ │ ├── reset.css # 基础样式重置 │ ├── style.css # 主样式含渐变背景与动画 │ └── swiper.min.css # Swiper 官方样式 ├── js/ │ ├── swiper.min.js # Swiper 库 │ ├── data.js # 模拟数据改这里就能换内容 │ └── main.js # 初始化 Swiper、渲染数据、绑定动画 └── images/ └── ... # 背景图与图标这个划分的好处是职责清晰data.js只放数据main.js只放逻辑style.css只放样式。你想换一套数据只动data.js想换配色只动style.css顶部的 CSS 变量。常见做法是把数据和逻辑混在一起改一个数字要翻半天这份包避开了这个坑。2.2 Swiper 的初始化与翻页参数翻页是整个报告的核心交互靠 Swiper 的vertical模式实现上下滑动切换。main.js里的初始化代码是这样的// main.js const swiper new Swiper(.swiper-container, { direction: vertical, // 垂直方向翻页符合报告上下滑的直觉 speed: 800, // 翻页过渡时长800ms 接近网易云的节奏 mousewheel: true, // 支持鼠标滚轮翻页 pagination: { el: .swiper-pagination, // 右侧小圆点指示器 clickable: true // 允许点击圆点跳页 }, on: { slideChangeTransitionStart: function () { // 每次翻页开始时给当前页的数字加滚动动画 animateNumbers(this.activeIndex); } } });direction: vertical决定了翻页方向如果你想要左右滑改成horizontal即可。speed控制过渡时长数值越大越「慢镜头」网易云那种略带阻尼感的翻页大概在 700 到 900 之间。mousewheel打开后桌面端滚轮也能翻页但要注意它和页面本身滚动会冲突所以body上一般要加overflow: hidden。on.slideChangeTransitionStart是翻页动画开始的回调我在这里触发数字滚动保证动画和翻页同步不会出现「页面还没翻过去数字就先滚完」的尴尬。2.3 数据渲染把 data.js 变成页面内容data.js导出一个对象结构大致如下// data.js const reportData { totalHours: 328, // 年度听歌总时长 topArtist: 陈奕迅, // 最爱歌手 topSong: 富士山下, // 单曲循环最多 listenDays: 289, // 有听歌记录的天数 topGenre: 粤语流行, // 最常听的曲风 lateNightCount: 47 // 深夜听歌次数 };main.js里用document.querySelector把这些值填进对应的 DOM 节点// main.js function renderData() { document.querySelector(.total-hours).textContent reportData.totalHours; document.querySelector(.top-artist).textContent reportData.topArtist; document.querySelector(.top-song).textContent reportData.topSong; document.querySelector(.listen-days).textContent reportData.listenDays; document.querySelector(.top-genre).textContent reportData.topGenre; document.querySelector(.late-night).textContent reportData.lateNightCount; }这里的关键是「数据与视图分离」。你不需要在 HTML 里写死任何数字所有内容都从data.js来。想换成自己的数据只改这一个文件页面自动更新。参数命名也尽量语义化totalHours一看就知道是总时长lateNightCount是深夜次数后面接真实接口时字段名可以直接对齐。3. 视觉还原CSS 渐变背景与数字滚动动画3.1 渐变背景与氛围色网易云年度报告的背景不是单一颜色而是多层径向渐变叠加营造出「光晕」感。style.css里用 CSS 变量统一管理主色/* style.css */ :root { --primary: #c20c0c; /* 网易云红 */ --bg-dark: #1a1a2e; /* 深色底 */ --bg-glow: rgba(194, 12, 12, 0.3); /* 光晕色 */ } .swiper-slide { background: radial-gradient(circle at 50% 30%, var(--bg-glow), transparent 60%), linear-gradient(180deg, var(--bg-dark), #0f0f1a); }radial-gradient负责中间那团光晕linear-gradient负责整体从深到更深的过渡。两个渐变叠加页面就有了层次。circle at 50% 30%把光晕放在偏上位置符合视觉重心。想换主题色只改--primary和--bg-glow两个变量整页氛围跟着变。常见翻车点是光晕太亮导致文字看不清所以--bg-glow的透明度一般控制在 0.2 到 0.4 之间。3.2 数字滚动动画的实现数字从 0 滚到目标值是年度报告里最有「仪式感」的细节。main.js里用一个requestAnimationFrame循环实现// main.js function animateNumbers(index) { const slide document.querySelectorAll(.swiper-slide)[index]; const numberEl slide.querySelector(.animated-number); if (!numberEl) return; const target parseInt(numberEl.dataset.target, 10); const duration 1200; // 动画总时长 const startTime performance.now(); function update(currentTime) { const elapsed currentTime - startTime; const progress Math.min(elapsed / duration, 1); // easeOutCubic 缓动结尾更自然 const eased 1 - Math.pow(1 - progress, 3); numberEl.textContent Math.floor(eased * target); if (progress 1) requestAnimationFrame(update); } requestAnimationFrame(update); }>.swiper-slide-active .card { opacity: 1; transform: translateY(0); transition: all 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .card { opacity: 0; transform: translateY(30px); }只有当前激活页.swiper-slide-active的卡片才会归位到translateY(0)并显示。cubic-bezier(0.25, 0.46, 0.45, 0.94)是一条先快后慢的曲线卡片从下方 30px 处滑入带一点「浮上来」的感觉。这个动画不需要 JS 参与纯 CSS 就能跑性能比 JS 改样式好。注意transition写在激活态里非激活态不写这样翻走的时候卡片直接消失不会拖泥带水。4. 避坑与排查翻页失效、动画错位、移动端适配4.1 翻页没反应滚轮和触摸都失效现象页面打开后鼠标滚轮滚动没有任何反应手机上滑也不翻页。原因Swiper 的容器高度没有撑满视口或者body的overflow没处理好。Swiper 垂直翻页要求容器有明确高度通常是100vh。解决检查.swiper-container是否设了height: 100vh同时html, body要加height: 100%; overflow: hidden;。如果用了mousewheel: true但页面本身还能滚动滚轮事件会被页面抢走Swiper 收不到。把body的滚动禁掉滚轮才会交给 Swiper。4.2 数字滚动动画在第二页之后不触发现象第一页数字正常滚动翻到第二页数字直接显示最终值没有动画。原因animateNumbers里用document.querySelectorAll(.swiper-slide)[index]取当前页但index来自this.activeIndex如果回调绑定的是slideChangeTransitionStart此时activeIndex已经更新但 DOM 里.animated-number的>// main.js function setVH() { document.documentElement.style.setProperty(--vh, window.innerHeight * 0.01 px); } window.addEventListener(resize, setVH); setVH();CSS 里写height: calc(var(--vh, 1vh) * 100);。这样视口高度跟着实际窗口变地址栏收起也不会遮内容。4.4 背景渐变在低端机上卡顿现象翻页时背景渐变出现明显掉帧尤其在安卓中低端机上。原因radial-gradient叠加linear-gradient在每一帧都要重绘如果还加了filter: blurGPU 压力更大。解决把渐变背景固定在一个不参与翻页的容器上不要每个 slide 都写一遍。或者用一张预先做好的渐变图代替 CSS 渐变减少实时计算。filter: blur能不用就不用光晕效果用box-shadow或半透明层模拟。5. 进阶玩法换数据、接接口、导出静态页5.1 把模拟数据换成真实数据data.js现在是写死的对象实际项目里数据通常来自接口。你可以把renderData改成异步// main.js async function fetchData() { const res await fetch(/api/annual-report); const data await res.json(); Object.assign(reportData, data); renderData(); } fetchData();字段名对齐后renderData不用改。如果接口返回的字段和data.js不一致在Object.assign之前做一层映射就行。常见做法是加一个normalize函数把后端字段转成前端字段避免改渲染逻辑。5.2 用 URL 参数控制报告年份年度报告往往要支持切换年份。可以在 URL 上带?year2025main.js读取后请求对应年份的数据// main.js const params new URLSearchParams(location.search); const year params.get(year) || new Date().getFullYear(); fetch(/api/annual-report?year${year}) .then(res res.json()) .then(data { Object.assign(reportData, data); renderData(); });这样同一个页面能服务多个年份不用每年重新部署一份。参数校验别忘了year要做成数字范围判断防止传入非法值导致接口报错。5.3 导出为静态页分享如果想把报告发给朋友看又不想部署服务可以把数据内联进 HTML生成一个自包含的静态文件。做法是在构建时把data.js的内容直接写进index.html的script标签里去掉fetch调用。这样双击就能打开不依赖任何后端。注意图片要转成 base64 或者用相对路径一起打包否则换台电脑图就挂了。5.4 一个我常犯的错早些年做类似页面我总喜欢把动画时长调得很短觉得「快就是流畅」。结果用户反馈说「还没看清就翻过去了」。后来我强制自己把speed和数字动画时长都放慢到 800ms 以上翻页节奏反而更舒服。从那以后我每次做翻页类页面都强制走一遍「慢速预览」——把speed临时改成 2000看每一页的动画是否完整、文字是否来得及读。这个习惯帮我省掉了很多「看起来没问题但用户觉得赶」的返工。希望帮到你。本文还有配套的精品资源点击获取