qq音乐案例
案例效果展示

案例效果结构划分
整体布局

歌曲条目部分

顶部栏

底部栏

歌词显示部分

案例实现功能
a. QQ音乐播放器静态页面布局
* 页面整体布局规划和实现
* 页面顶部布局和静态效果
* 工具条布局和静态效果
* 音乐列表布局和静态效果
* 自定义滚动条效果
* 歌曲信息的展示效果
* 底部播放布局和静态效果
* 页面整体的高斯模糊效果
b. QQ音乐播放器加载并播放歌曲
* 实现音乐动态加载逻辑
* 实现底部音乐控制图标的动态切换
* 实现底部音乐播放状态的动态切换
* 实现音乐序号的动画效果
c. QQ音乐播放器操作歌曲(切换、删除等)
* 实现音乐的播放、暂停功能
* 实现音乐切换(切歌)功能
* 实现音乐删除功能
d. QQ音乐播放器歌词加载、显示和同步
* 实现解析歌词并动态加载功能
* 实现歌词与歌曲播放进度同步功能
e. 拖动音乐进度条调整音乐播放进度功能
案例知识点(参考别人)
- CSS
- 设置距离左边的距离:margin-left: 20px; 设置距离右边的距离:margin-right: 20px;
- 设置透明度:opacity: 0.6; 值[0,1]从透明到全不透明
- 设置背景图片:background: url(…/img/player_logo.png) no-repeat 0 0;
- 设背景颜色和透明度:background: rgba(255,255,255,0.5);
- 设置li的样式:list-style: none;
- 设置显示样式为行内块:display: inline-block;
- 设置圆角:border-radius: 5px;
- 设置相对位置:position: relative;
- 背景图片的起始坐标:background-position: 0 -75px;
- JS
- 通过标签获取jQuery对象:var a u d i o = audio =audio=(“audio”);
- 通过选择符获取jQuery对象并设置文本内容:$(".music_progrss_time").text(timeStr);
- 通过选择符,标签名获取对象并获取第i个子元素:$(".song_lyric ul li").eq(index);
- 通过ajax异步获取数据并刷新页面:$.ajax({});
- 通过类选择符获取元素并进行隐藏或显示:$(this).find(".list_menu").stop().fadeIn(100);
- 通过委托动态设置单击事件,主要针对动态生成元素:$(".content_list").delegate(".list_check", “click”, function() {});
- 通过addClass添加类,removeClass删除类,toggleClass切换类,hasClass是否包含类
- 获取与对象同级的兄弟节点:$musicList.siblings();
- 触发相关事件:$(".music_next").trigger(“click”);
案例思路
首先要先布局
头部header、中间区域content、底部footer和背景mask_bg
<!--头部header --> <div class="header"> <h1 class="logo"><a href="#"></a></h1> <ul class="register"> <li>注册</li> <li>登陆</li> </ul> </div> <!--中间区域content--> <div class="content"> <div class="content_in"></div> </div> <!--底部footer--> <div class="footer"> <div class="footer_in"></div> </div> <!--背景mask_bg--> <div class="mask_bg"></div>工具条(收藏、添加到、下载、删除、清空列表),利用雪碧图完成图片

<div class="content_in"> <!-- 左边内容 --> <div class="content_left"> <!-- 左上部分 --> <div class="content_toolbar"> <!-- 布局文字和图片 --> <span><i></i>收藏</span> <span><i></i>添加到</span> <span><i></i>下载</span> <span><i></i>删除</span> <span><i></i>清空列表</span> </div> </div> </div>初步完成歌曲列表

<!-- 左下部分 --> <ul> <!-- 每一个歌曲条目 --> <li class="list_title"> <!-- 选项框 --> <div class="list_check"><i></i></div> <!-- 数字 --> <div class="list_number"></div> <!-- 歌曲 --> <div class="list_name">歌曲</div> <!-- 歌手 --> <div class="list_singer">歌手</div> <!-- 时长 --> <div class="list_time">时长</div> </li> </ul> </div>完善歌曲部分

- 可以点击选项框
- 再点击每一行的时候会出现暂停,添加,下载,转发,删除等,换到下一行的时候其他行会淡入淡出的消失
function initEvents() { // 1.监听歌曲的移入移出事件 // 要用事件委托因为歌曲都是新增的 $(".content_list").delegate(".list_music", "mouseenter", function () { //find 搜索所有与指定表达式相匹配的元素,这个函数是找出正在处理的元素的后代元素的好方法 //如果直接用$('.list_menu')所有菜单会一起显示出来 // 显示子菜单 $(this).find(".list_menu").stop().fadeIn(100); $(this).find(".list_time a").stop().fadeIn(100); // 隐藏时长 $(this).find(".list_time span").stop().fadeOut(100); }); $(".content_list").delegate(".list_music", "mouseleave", function () { // 隐藏子菜单 $(this).find(".list_menu").stop().fadeOut(100); $(this).find(".list_time a").stop().fadeOut(100); // 显示时长 $(this).find(".list_time span").stop().fadeIn(100); }); /* addClass()是在原有的类基础上增加类属性,仍然保留原有的类的样式 toggleClass() 是切换元素class类别,删除原有的class样式,替换为新的class样式. (切换,没有加,有减) */ // 2.监听复选框的点击事件 $(".content_list").delegate(".list_check", "click", function () { $(this).toggleClass("list_checked");//有就删除,没有就添加 }); }自定义滚动条

自定义滚动条运用到了jQuery的插件,可以利用这个插件来设置不同样式的滚动条 — jQuery custom content scroller
- 首先要从官网上下载

- 在html中添加
<div class="content_list" data-mcs-theme='minimal-dark'></div> - 在JavaScript中添加
$(".content_list").mCustomScrollbar();
如果不喜欢可以修改默认样式

._mCS_1 .mCSB_dragger .mCSB_dragger_bar{ background-color: red; }
._mCS_2 .mCSB_dragger .mCSB_dragger_bar{ background-color: green; }
#mCSB_3_dragger_vertical .mCSB_dragger_bar{ background-color: blue; }
#mCSB_1_scrollbar_vertical .mCSB_dragger{ height: 100px; }
#mCSB_1_scrollbar_horizontal .mCSB_dragger{ width: 100px; }
.mCSB_1_scrollbar .mCSB_dragger .mCSB_draggerRail{ width: 4px; }- 首先要从官网上下载
完成音乐信息部分

- html
<div class="content_right"> <!-- 右上内容 --> <div class="song_info"> <!-- 图片 --> <a href="javascript:;" class="song_info_pic"> <img src="https://s1.ax1x.com/2020/06/20/NQ3rSx.jpg" alt=""> </a> <div class="song_info_name">歌曲名称: <a href="javascript:;">111</a> </div> <div class="song_info_singer">歌手名: <a href="javascript:;">222</a> </div> <div class="song_info_ablum">专辑名: <a href="javascript:;">333</a> </div> </div> <!-- 右下内容 --> <div class="song_lyric_container"> <ul class="song_lyric"> <li class="cur">第一条歌词</li> <li>第二条歌词</li> </ul> </div> </div>- css
/* 右上内容 */ .content_right .song_info{ text-align: center; color: rgba(255,255,255,0.5); line-height: 30px; } .song_info .song_info_pic{ display: inline-block; background: url("https://s1.ax1x.com/2020/06/20/NQ3aTJ.png") no-repeat 0 0; width: 201px; height: 180px; text-align: left; border-radius: 50%; } .song_info_pic img{ width: 180px; height: 180px; border-radius: 50%; } .song_info div a{ text-decoration: none; color: #fff; opacity: 0.5; } .song_info div a:hover{ opacity: 1; } /* 右下歌词 */ .content_right .song_lyric_container{ margin-top: 30px; height: 150px; overflow: hidden; } .content_right .song_lyric{ /* background: greenyellow; */ text-align: center; margin-top: 30px; /* overflow: hidden; */ } .content_right .song_lyric li{ list-style: none; line-height: 30px; font-weight: bold; color: rgba(255,255,255,0.5); } .content_right .song_lyric .cur{ color: #31c27c; }底部footer

- html
<!-- 底部 --> <div class="footer"> <div class="footer_in"> <a href="javascript:;" class="music_pre"></a> <a href="javascript:;" class="music_play"></a> <a href="javascript:;" class="music_next"></a> <div class="music_progress_info"> <div class="music_progress_top"> <span class="music_progress_name">111</span> <span class="music_progress_time">00:00 / 05:23</span> </div> <div class="music_progress_bar"> <div class="music_progress_line"> <div class="music_progress_dot"></div> </div> </div> </div> <a href="javascript:;" class="music_mode"></a> <a href="javascript:;" class="music_fav"></a> <a href="javascript:;" class="music_down"></a> <a href="javascript:;" class="music_comment"></a> <a href="javascript:;" class="music_only"></a> <div class="music_voice_info"> <a href="javascript:;" class="music_voice_icon"></a> <div class="music_voice_bar"> <div class="music_voice_line"> <div class="music_voice_dot"></div> </div> </div> </div> </div> </div>- css
/* 底部 */ .footer{ width: 100%; height: 80px; /* background: green; */ position: absolute; left: 0; bottom: 0; } /* 版型 */ .footer .footer_in{ width: 1200px; height: 100%; /* background: purple; */ margin: 0 auto; user-select: none; } .footer_in a{ display: inline-block; text-decoration: none; color: #fff; background: url("https://s1.ax1x.com/2020/06/20/NQYwDK.png") no-repeat 0 0; margin-right: 20px; } .footer_in .music_pre{ width: 19px; height: 20px; background-position: 0 -30px; } .footer_in .music_play{ width: 21px; height: 29px; background-position: 0 0; vertical-align: -5px; } .footer_in .music_play2{ background-position: -30px 0; } .footer_in .music_next{ width: 19px; height: 20px; background-position: 0 -52px; } .footer_in .music_progress_info{ display: inline-block; width: 670px; height: 40px; position: relative; top: 10px; } .music_progress_info .music_progress_top{ width: 100%; height: 30px; line-height: 30px; color: #fff; } .music_progress_top .music_progress_name{ float: left; opacity: 0.5; } .music_progress_top .music_progress_name:hover{ opacity: 1; } .music_progress_top .music_progress_time{ float: right; opacity: 0.5; } .music_progress_info .music_progress_bar{ width: 100%; height: 4px; background: rgba(255,255,255,0.5); margin-top: 5px; position: relative; } .music_progress_bar .music_progress_line{ width: 0px; height: 100%; background: #fff; } .music_progress_line .music_progress_dot{ width: 14px; height: 14px; border-radius: 50%; background: #fff; position: absolute; top: -5px; left: 0px; } .footer_in .music_mode{ width: 26px; height: 25px; background-position: 0 -205px; } .footer_in .music_mode2{ width: 23px; height: 20px; background-position: 0 -260px; } .footer_in .music_mode3{ width: 25px; height: 19px; background-position: 0 -74px; } .footer_in .music_mode4{ width: 26px; height: 25px; background-position: 0 -232px } .footer_in .music_fav{ width: 24px; height: 21px; background-position: 0 -96px; } .footer_in .music_fav2{ background-position: -30px -96px; } .footer_in .music_down{ width: 22px; height: 21px; background-position: 0 -120px; } .footer_in .music_comment{ width: 24px; height: 24px; background-position: 0 -400px; } .footer_in .music_only{ width: 74px; height: 27px; background-position: 0 -281px; } .footer_in .music_only2{ background-position: 0 -310px; } .footer_in .music_voice_info{ display: inline-block; width: 100px; height: 40px; line-height: 40px; position: relative; top: 10px; } .music_voice_info .music_voice_icon{ width: 26px; height: 21px; background-position: 0 -144px; position: absolute; left: 0; top: 10px; } .music_voice_info .music_voice_icon2{ background-position: 0 -182px; } .music_voice_info .music_voice_bar{ width: 70px; height: 4px; background: rgba(255,255,255,0.5); position: absolute; right: 0; top: 18px; } .music_voice_bar .music_voice_line{ width: 70px; height: 100%; background: #fff; } .music_voice_line .music_voice_dot{ width: 14px; height: 14px; border-radius: 50%; background: #fff; position: relative; top: -5px; left: 70px; }
之后,要加载歌曲

这里就要利用ajax方法
// 1.加载歌曲列表 getPlayerList(); function getPlayerList() { $.ajax({ //从哪加载 url: "./source/musiclist.json", //加载的是什么类型 dataType: "json", // 成功之后做什么 success: function (data) { player.musicList = data; // 3.1遍历获取到的数据, 创建每一条音乐 var $musicList = $(".content_list ul"); $.each(data, function (index, ele) { var $item = crateMusicItem(index, ele); $musicList.append($item); }); initMusicInfo(data[0]); initMusicLyric(data[0]); }, // 失败之后做什么 error: function (e) { console.log(e); } }); } getPlayerList(); // 定义一个方法创建一条音乐 function crateMusicItem(index, music) { var $item = $("" + "<li class=\"list_music\">\n" + "<div class=\"list_check\"><i></i></div>\n" + "<div class=\"list_number\">"+(index + 1)+"</div>\n" + "<div class=\"list_name\">"+music.name+"" + " <div class=\"list_menu\">\n" + " <a href=\"javascript:;\" title=\"播放\" class='list_menu_play'></a>\n" + " <a href=\"javascript:;\" title=\"添加\"></a>\n" + " <a href=\"javascript:;\" title=\"下载\"></a>\n" + " <a href=\"javascript:;\" title=\"分享\"></a>\n" + " </div>\n" + "</div>\n" + "<div class=\"list_singer\">"+music.singer+"</div>\n" + "<div class=\"list_time\">\n" + " <span>"+music.time+"</span>\n" + " <a href=\"javascript:;\" title=\"删除\" class='list_menu_del'></a>\n" + "</div>\n" + "</li>"); return $item; }完成音乐播放时图标切换
保证歌曲条目开始或暂停时让底部的保持同步


// 3.添加子菜单播放按钮的监听
var $musicPlay = $(".music_play");
$(".content_list").delegate(".list_menu_play", "click", function () {
var $item = $(this).parents(".list_music");
// console.log($item.get(0).index);
// console.log($item.get(0).music);
// 3.1切换播放图标
$(this).toggleClass("list_menu_play2");
// 3.2复原其它的播放图标
//先找点击的祖先list_music,再用siblings()方法找list_music的兄弟 $item.siblings().find(".list_menu_play").removeClass("list_menu_play2");
// 3.3同步底部播放按钮
if($(this).hasClass("list_menu_play2") !=-1){
// 能找到list_menu_play2
// 当前子菜单的播放按钮是播放状态
$musicPlay.addClass("music_play2");
// 让文字高亮
$item.find("div").css("color", "#fff");
$item.siblings().find("div").css("color", "rgba(255,255,255,0.5)");
}else{
// 当前子菜单的播放按钮不是播放状态
$musicPlay.removeClass("music_play2");
// 让文字不高亮
$item.find("div").css("color", "rgba(255,255,255,0.5)");
}
});
播放时的动画效果

// 3.4切换序号的状态 $item.find(".list_number").toggleClass("list_number2"); $item.siblings().find(".list_number").removeClass("list_number2");播放暂停音乐
- 首先先封装一个工具库player.js,利用这个工具库实现功能
- 插入一个< audio>标签
- 当我们点击音乐键时,调用工具库中播放音乐的方法
- 主要判断是否是同一首音乐
function Player($audio) { return new Player.prototype.init($audio); } Player.prototype = { constructor: Player, musicList: [], init: function ($audio) { this.$audio = $audio; this.audio = $audio.get(0); }, currentIndex: -1, // 4 3 playMusic: function (index, music) { // 判断是否是同一首音乐 if(this.currentIndex == index){ // 同一首音乐 if(this.audio.paused){ this.audio.play(); }else{ this.audio.pause(); } }else { // 不是同一首 this.$audio.attr("src", music.link_url); this.audio.play(); this.currentIndex = index; } } }实现底部栏的上一曲/下一曲/暂停
- 上一曲/下一曲
- 当歌曲为第一首时,如果在点击上一曲,要回到最后一首
- 当歌曲为最后一首时,如果在点击下一曲,要回到第一首
// 5.监听底部控制区域上一首按钮的点击 $(".music_pre").click(function () { $(".list_music").eq(player.preIndex()).find(".list_menu_play").trigger("click"); }); // 6.监听底部控制区域下一首按钮的点击 $(".music_next").click(function () { $(".list_music").eq(player.nextIndex()).find(".list_menu_play").trigger("click"); });- 暂停(判断)
- 点击播放过
- 继续播放
- 没有播放过
- 播放第一首
- 点击播放过
// 4.监听底部控制区域播放按钮的点击 $musicPlay.click(function () { // 判断有没有播放过音乐 if(player.currentIndex == -1){ // 没有播放过音乐 $(".list_music").eq(0).find(".list_menu_play").trigger("click"); }else{ // 已经播放过音乐 $(".list_music").eq(player.currentIndex).find(".list_menu_play").trigger("click"); } });- 上一曲/下一曲
删除音乐操作
- 保证删除后后台数据不能丢失
- 保证删除后顺序不能错乱
// 7.监听删除按钮的点击
$(".content_list").delegate(".list_menu_del", "click", function () {
// 找到被点击的音乐
var $item = $(this).parents(".list_music");
// 判断当前删除的是否是正在播放的
if($item.get(0).index == player.currentIndex){
$(".music_next").trigger("click");
}
item.remove();
player.changeMusic($item.get(0).index);
// 重新排序
$(".list_music").each(function (index, ele) {
ele.index = index;
$(ele).find(".list_number").text(index + 1);
});
});
- 歌词的切换
在点击切换上一曲/下一曲/删除后,要随时切换歌曲名称、歌手名、专辑名、歌词和时间等

// 2. 初始化歌曲信息
function initMusicInfo(music) {
// 获取对应的元素
var $musicImage = $(".song_info_pic img");
var $musicName = $(".song_info_name a");
var $musicSinger = $(".song_info_singer a");
var $musicAblum = $(".song_info_ablum a");
var $musicProgressName = $(".music_progress_name");
var $musicProgressTime = $(".music_progress_time");
var $musicBg = $(".mask_bg");
// 给获取的到的元素赋值
$musicImage.attr("src", music.cover);
$musicName.text(music.name);
$musicSinger.text(music.singer);
$musicAblum.text(music.album);
$musicProgressName.text(music.name + "/" + music.singer);
$musicProgressTime.text("00:00 / " + music.time);
$musicBg.css("background", 'url("' + music.cover + '")');
}
// 3.初始化歌词信息
function initMusicLyric(music){
lyric = new Lyric(music.link_lrc);
var $lryicContainer = $(".song_lyric");
// 清空上一首音乐的歌词
$lryicContainer.html("");
lyric.loadLyric(function () {
// 创建歌词列表
$.each(lyric.lyrics, function (index, ele) {
var $item = $("<li>"+ele+"</li>");
$lryicContainer.append($item);
});
});
}
$.ajax({
success: function (data) {
initMusicInfo(data[0]);
},
});
$(".content_list").delegate(".list_menu_play", "click", function () {
// 3.6 切换歌曲信息
initMusicInfo($item.get(0).music);
});
进度条
- 时间与进度条同步
musicTimeUpdate: function (callBack) { var $this = this; this.$audio.on("timeupdate", function () { var duration = $this.audio.duration; var currentTime = $this.audio.currentTime; var timeStr = $this.formatDate(currentTime, duration); callBack(currentTime, duration, timeStr); }); }, formatDate: function (currentTime, duration) { var endMin = parseInt(duration / 60); // 2 var endSec = parseInt(duration % 60); if(endMin < 10){ endMin = "0" + endMin; } if(endSec < 10){ endSec = "0" + endSec; } var startMin = parseInt(currentTime / 60); // 2 var startSec = parseInt(currentTime % 60); if(startMin < 10){ startMin = "0" + startMin; } if(startSec < 10){ startSec = "0" + startSec; } return startMin+":"+startSec+" / "+endMin+":"+endSec; } player.musicTimeUpdate(function (currentTime, duration, timeStr) { // 同步时间 $(".music_progress_time").text(timeStr); }- 信息与进度条同步
// 8.监听播放的进度
player.musicTimeUpdate(function (currentTime, duration, timeStr) {
// 同步进度条
// 计算播放比例
var value = currentTime / duration * 100;
progress.setProgress(value);
// 实现歌词同步
var index = lyric.currentIndex(currentTime);
var $item = $(".song_lyric li").eq(index);
$item.addClass("cur");
$item.siblings().removeClass("cur");
// 实现歌词滚动
if(index <= 2) return;
$(".song_lyric").css({
marginTop: (-index + 2) * 30
});
});
案例代码
解决Ajax 跨域问题
什么是Ajax跨域
前端调用后端服务接口时
Ajax跨域原因
- 浏览器限制:浏览器安全校验限制
- 跨域(协议、域名、端口任何一个不一样都会认为是跨域)
- XHR(XMLHttpRequest)请求
解决qq音乐跨域问题
第一种
打开
cmd,然后找到所在项目目录下,先安装npm install -g live-server接下来输入
live-server,回车注意这里默认打开的是index.html
第二种
- 如果第一种不想用的话,可以直接用WebStorm,简单方便
- 下载WebStorm
- WebStorm激活码