原生js实现视差风格音乐播放器
简介:
这是一个练习的小案例,支持在线搜索,调用的是免费的音乐接口,
主要用到了:弹性盒,3D效果,Ajax 等技术
效果如下:
接口:
接口地址: https://api.apiopen.top/searchMusic
请求示例: https://api.apiopen.top/searchMusic?name=雅俗共赏
请求结果:
{ "author": "许嵩", "link": "http://music.163.com/#/song?id=411214279", "pic": "http://p2.music.126.net/Wcs2dbukFx3TUWkRuxVCpw==/3431575794705764.jpg?param=300x300", "type": "netease", "title": "雅俗共赏", "lrc": "", "songid": 411214279, "url": "http://music.163.com/song/media/outer/url?id=411214279.mp3" }
主体界面:
整体采用三段式样式,分为左中右三块,
左侧为装饰性样式没有实际作用,计划添加 点击切换歌词面板功能
中间部分为播放器主体,包括搜索框,搜索按钮,歌曲封面,歌曲名,作者,以及audio播放器,
右侧没有进行功能添加,计划添加喜欢收藏 下一首 上一首 分享 。
HTM代码如下:
<body> <div id="box"> <!-- 搜索信息展示版块 ==>默认display:none --> <div id="display"> <div class="close"><span>X</span></div> <h2>歌曲列表</h2> <div id="infoBox"> <!-- <p>歌曲名-Redbone <span>听这首</span></p> --> </div> </div> <!-- 左侧版块 --> <div class="leftBg"> <!-- 滚动信息 --> <marquee behavior="scroll" direction="left">Current version of Baidu Music Interface</marquee> <!-- 文字 ==> THIS IS MUSIC --> <p class="p1">THIS <br>IS </p> <p class="p2">MUSIC</p> <!-- 白色装饰条 --> <div></div> <div></div> </div> <!-- 中间板块 --> <div class="centerBg"> <!-- 搜索框+搜索按钮 --> <input type="text" value="" id="search"><span id="searchSpan">搜索</span> <div id="bfBox"> <!-- 歌曲图片 --> <div class="imgBox"> <img src="http://p2.music.126.net/5i5SKVW_F1ub2BgDeyjI5A==/3225967119049341.jpg?param=300x300" alt=""> </div> <!-- 歌曲信息 --> <div class="info"> <p>Come and Get Your Love</p> <p>Redbone</p> <!-- 播放器 --> <audio src="http://music.163.com/song/media/outer/url?id=28864241.mp3" controls></audio> </div> </div> </div> <!-- 右侧板块 ==> 未开发 --> <div class="rightBg"> </div> </div> </body>
CSS代码如下:
<style> body{ /* 开启视距 */ perspective: 1400px; position: relative; } /* 主页面 */ #box{ width: 800px; height:500px; background-color: rgb(255, 255, 255); margin: 80px auto; /* transition: 2s; */ /* 开启3D空间 */ transform-style: preserve-3d; display: flex; justify-content: flex-start; box-shadow: 0 15px 35px -5px rgba(50,88,130,.32); } /* 左侧版块 */ #box .leftBg{ width: 190px; height: 440px; background-color: #222; padding: 30px; position: relative; } /* 中间板块 */ #box .centerBg{ width: 500px; height: 500px; background-color: rgb(241, 241, 241); } /* 右侧版块 */ #box .rightBg{ width: 100px; height: 500px; background-color: #222; } /* 左侧滚动信息 */ #box .leftBg marquee{ margin-top: 15px; color: #fff; font-size: 12px; } /* 左侧文字 ==> THIS IS MUSIC */ #box .leftBg p{ transform: translateZ(100px) } #box .leftBg .p1{ color: rgb(253, 0, 0); font-size: 50px; margin: 0; padding-top:50px; } #box .leftBg .p2 { color: rgb(253, 0, 0); font-size: 75px; font-weight: bold; margin: 10px auto; } /* 左侧装饰条两根 */ #box .leftBg div{ width: 100%; height: 5px; background-color: rgb(241, 241, 241); position: absolute; } #box .leftBg div:nth-child(4){ left: 0; bottom:60px; } #box .leftBg div:nth-child(5){ left: 0; bottom:45px; } /* 搜索框样式 */ #box #search{ outline:none; border: none; width: 250px; height: 25px; border-top-left-radius: 30px; border-bottom-left-radius: 30px; background-color: #fff; margin-top: 20px; margin-left: 50px; padding: 5px 15px; } /* 搜索按钮样式 */ #box #searchSpan{ display: inline-block; padding: 5px 15px; width: 25px; height: 25px; text-align: center; line-height: 25px; background-color: #222; color: #fff; font-size: 12px; border-top-right-radius: 30px; border-bottom-right-radius: 30px; cursor: pointer; } /* 中间板块 播放器盒子 */ #box .centerBg #bfBox{ width: 240px; height: 330px; border-radius: 10px; background-color: #eef3f7; margin-left:130px; margin-top: 20px; position: relative; box-shadow: 0 15px 35px -5px rgba(50,88,130,.32); display: flex; align-items: flex-end; padding: 30px; } /* 歌曲图片样式 */ #box .centerBg .imgBox{ width: 160px; height: 160px; border-radius: 10px; position: absolute; top: 30px; left: -30px; box-shadow: 0 10px 20px 0 rgba(76,70,124,.5); transform: translateZ(100px) } #box .centerBg .imgBox img{ width: 160px; height: 160px; border-radius: 5px; } /* 歌曲信息样式 */ #box .centerBg #bfBox .info { width: 100%; height: 45%; } #box .centerBg #bfBox .info p{ margin: 0; color:#71829e; } #box .centerBg #bfBox .info p{ margin-top:15px; font-size: 20px; font-weight: bolder; } #box .centerBg #bfBox .info p:nth-of-type(2){ margin-top: 5px; font-size: 16px; } /* audio播放器样式 */ #box .centerBg #bfBox .info audio{ width: 100%; padding-top: 10px; outline:none; } /* 搜索展示界面样式 */ #display{ width: 400px; height: 500px; background-color: rgb(245, 238, 238); box-shadow: 0 10px 20px 0 rgba(76,70,124,.5); display: none; border-radius: 10px; position: absolute; top: 0; left: 25%; transform: translateZ(110px); padding: 30px; } /* 搜索展示界面 标题====>(歌曲列表) */ #display h2{ text-align: center; color: rgb(255, 0, 0); } #display p { color: #607391; font-size: 12px; margin: 5px; 0; padding:0 10px; height: 35px; line-height: 35px; } #display p:hover{ background-color: #fff; } #display p span { margin-top: 5px; float: right; width: 45px; height: 25px;; box-sizing: border-box; border-radius: 5px; background-color: rgb(55, 110, 214); box-shadow: inset 1px 1px 5px 0px rgba(50,88,130,.32); text-align: center; line-height: 25px; font-size: 12px; color: #ccc; cursor: pointer; } /* 展示版块关闭按钮样式 */ #display .close{ position: relative; } #display .close span{ width: 30px; height: 30px; border-radius: 50%; position: absolute; top: 10px; right: 10px; background-color: lightblue; box-shadow: inset 1px 1px 5px 0px rgba(50,88,130,.32); text-align: center; line-height: 30px; cursor: pointer; } </style>
视差风格的实现
视差滚动(Parallax Scrolling)是指让多层背景以不同的速度移动,形成立体的运动效果,带来非常出色的视觉体验。作为网页设计的热点趋势,越来越多的网站应用了这项技术。
这是借鉴了视差滚动的原理,使页面产生实时改变的3D效果,具体实现如下:
1.思路:
将播放器开启3D空间,
在鼠标横向移动时,让播放器沿Y轴旋转,在鼠标纵向移动时,让播放器沿X轴旋转,
将视距增大至1400(此时的视距刚刚好)。
2.代码实现:
//body{perspective: 1400px;} 开启视距 // 鼠标在body上移动,将坐标转换成旋转角度,形成视差 document.onmousemove = function (eve){ var e = eve || window.event; that.x = (e.clientX-that.ele.offsetWidth/2)*0.01; that.y = -(e.clientY-that.ele.offsetTop/2)*0.01;; that.display(); } //形成视差 display(){ this.box.style.transform = "rotateY(" + this.x + "deg) rotateX(" + this.y + "deg)"; }
控制台信息:
ajax请求接口数据:
ajax是前端向后端请求发送数据的重要手段,并且可以实现无刷新加载数据。
该案例使用的接口返回的数据是json,使用ajax可以正确的解析数据
1.思路:
当用户点击搜索按钮时,获取输入框的数据,拼接到接口地址上
使用ajax请求拼接好的地址,拿到数据
2.实现如下:
//搜索框发生改变,生成搜索内容 this.searchBtn.onclick = function(){ var str = ""; that.url = "https://api.apiopen.top/searchMusic"; str = "?name="+that.text.value; that.url += str; that.load(); } // ajax请求数据 load(){ var that = this; ajax({ url:this.url, success:function(res){ that.res = JSON.parse(res); console.log(that.res.result); } }) }
这里使用的是封装好的ajax函数,函数如下
//ajax封装函数 function ajax(options) { var { type, url, success, error, data, timeout } = options; type = type || "get"; data = data || {}; timeout = timeout || 2000; var str = ""; for (var i in data) { str += `${i}=${data[i]}&`; } if (type == "get") { var d = new Date(); url = url + "?" + str + "__qft=" + d.getTime(); } var xhr = new XMLHttpRequest(); xhr.open(type, url, true); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { success && success(xhr.responseText); error = null; } else if (xhr.readyState == 4 && xhr.status != 200) { error && error(xhr.status); success = null; error = null; } } setTimeout(() => { error && error("timeout"); success = null; }, timeout); if (type == "post") { xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.send(str) } else { xhr.send() } }
请求到的数据如下
这里我们需要的是result数组中的信息
所以console.log(that.res.result);得到正确的数据
展示数据
得到数据之后下面要做的就是将请求到的数据展示出来
在页面结构中,有展示页面的结构,需求是点击搜索按钮,展示页面将数据展示出来
展示格式为:歌曲名-歌手 并且可以选择指定歌曲播放
1.思路:
点击搜索按钮后,展示页面显示(dispaly:“block”),
将数据拼接为HTML代码插入到展示页面中,
给所有的信息绑定事件,点击信息记录当前信息的索引(可以使用事件委托优化性能)。
2.代码实现:
//生成搜索内容 //请求完成,获得数据,将数据展示到搜索页面上 display2(){ this.displayPage.style.display = "block" var str = ""; // 如果搜索为空,显示搜索内容为空 //否则,展示搜索内容 if(this.res.result.length == 0){ str = "<h3>搜索内容为空</h3>" }else{ for(var i=0;i<this.res.result.length;i++){ str+=`<p>${this.res.result[i].title}-${this.res.result[i].author} <span>听这首</span></p>` } } this.infoBox.innerHTML = str; this.change(); this.disClose(); } //点击选中的歌曲,改变播放器页面的信息 change(){ var that = this; this.span = document.querySelectorAll("#infoBox p span"); console.log(this.span); // 遍历所有的歌曲span,找到点击所在的索引,保存到num中 //同时:如果选中了歌曲,就关闭搜索展示页面 for(var i=0;i<this.span.length;i++){ this.span[i].index = i; this.span[i].onclick = function (){ this.num = that.span[this.index].index that.reMove(); that.display3(this.num); // console.log(this.num); } } }
关闭展示
点击关闭按钮后,或者选择完成歌曲后,我们需要关闭展示页面
1.思路:
点击关闭很简单实现,选择关闭需要我们调用点击关闭的子方法
2.代码实现:
// 关闭按钮方法 ===> 点击关闭按钮(disPageClose) 执行关闭搜索页面 disClose(){ var that = this; this.disPageClose.onclick = function(){ that.reMove(); } } //移除方法 ====> 执行该函数,清空搜索界面所有内容,并隐藏 reMove(){ this.displayPage.style.display = "none"; this.infoBox.innerHTML = ""; }
展示界面如下:
改变播放器主页面信息
在选择歌曲后,需要根据选择的歌曲改变播放器的封面图 歌曲名 歌手名 以及要播放的歌曲
1.思路:
查看请求的数据我们知道(以搜索成都为例),根据数据的键名可以找到我们需要的信息
{ author: "赵雷" link: "http://music.163.com/#/song?id=436514312" lrc: "" pic: "http://p1.music.126.net/34YW1QtKxJ_3YnX9ZzKhzw==/2946691234868155.jpg?param=300x300" songid: 436514312 title: "成都" type: "netease" url: "http://music.163.com/song/media/outer/url?id=436514312.mp3" }
故而需要将数据写入播放器的html中
2.代码实现:
//播放器音乐信息展示方法 ===> 将ajax请求到的数据,写入到播放内容区(bfBox) display3(num){ var str = ""; for(var i=0;i<this.res.result.length;i++){ str = `<div class="imgBox"> <img src="${this.res.result[num].pic}" alt=""> </div> <div class="info"> <p>${this.res.result[num].title}</p> <p>${this.res.result[num].author}</p> <audio src="${this.res.result[num].url}" controls></audio> </div>` } this.bfBox.innerHTML = str; }
由于使用的是H5的audio标签,这时我们就完成了,播放器的基本功能。
完整代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> body{ /* 开启视距 */ perspective: 1400px; position: relative; } /* 主页面 */ #box{ width: 800px; height:500px; background-color: rgb(255, 255, 255); margin: 80px auto; /* transition: 2s; */ /* 开启3D空间 */ transform-style: preserve-3d; display: flex; justify-content: flex-start; box-shadow: 0 15px 35px -5px rgba(50,88,130,.32); } /* 左侧版块 */ #box .leftBg{ width: 190px; height: 440px; background-color: #222; padding: 30px; position: relative; } /* 中间板块 */ #box .centerBg{ width: 500px; height: 500px; background-color: rgb(241, 241, 241); } /* 右侧版块 */ #box .rightBg{ width: 100px; height: 500px; background-color: #222; } /* 左侧滚动信息 */ #box .leftBg marquee{ margin-top: 15px; color: #fff; font-size: 12px; } /* 左侧文字 ==> THIS IS MUSIC */ #box .leftBg p{ transform: translateZ(100px) } #box .leftBg .p1{ color: rgb(253, 0, 0); font-size: 50px; margin: 0; padding-top:50px; } #box .leftBg .p2 { color: rgb(253, 0, 0); font-size: 75px; font-weight: bold; margin: 10px auto; } /* 左侧装饰条两根 */ #box .leftBg div{ width: 100%; height: 5px; background-color: rgb(241, 241, 241); position: absolute; } #box .leftBg div:nth-child(4){ left: 0; bottom:60px; } #box .leftBg div:nth-child(5){ left: 0; bottom:45px; } /* 搜索框样式 */ #box #search{ outline:none; border: none; width: 250px; height: 25px; border-top-left-radius: 30px; border-bottom-left-radius: 30px; background-color: #fff; margin-top: 20px; margin-left: 50px; padding: 5px 15px; } /* 搜索按钮样式 */ #box #searchSpan{ display: inline-block; padding: 5px 15px; width: 25px; height: 25px; text-align: center; line-height: 25px; background-color: #222; color: #fff; font-size: 12px; border-top-right-radius: 30px; border-bottom-right-radius: 30px; cursor: pointer; } /* 中间板块 播放器盒子 */ #box .centerBg #bfBox{ width: 240px; height: 330px; border-radius: 10px; background-color: #eef3f7; margin-left:130px; margin-top: 20px; position: relative; box-shadow: 0 15px 35px -5px rgba(50,88,130,.32); display: flex; align-items: flex-end; padding: 30px; } /* 歌曲图片样式 */ #box .centerBg .imgBox{ width: 160px; height: 160px; border-radius: 10px; position: absolute; top: 30px; left: -30px; box-shadow: 0 10px 20px 0 rgba(76,70,124,.5); transform: translateZ(100px) } #box .centerBg .imgBox img{ width: 160px; height: 160px; border-radius: 5px; } /* 歌曲信息样式 */ #box .centerBg #bfBox .info { width: 100%; height: 45%; } #box .centerBg #bfBox .info p{ margin: 0; color:#71829e; } #box .centerBg #bfBox .info p{ margin-top:15px; font-size: 20px; font-weight: bolder; } #box .centerBg #bfBox .info p:nth-of-type(2){ margin-top: 5px; font-size: 16px; } /* audio播放器样式 */ #box .centerBg #bfBox .info audio{ width: 100%; padding-top: 10px; outline:none; } /* 搜索展示界面样式 */ #display{ width: 400px; height: 500px; background-color: rgb(245, 238, 238); box-shadow: 0 10px 20px 0 rgba(76,70,124,.5); display: none; border-radius: 10px; position: absolute; top: 0; left: 25%; transform: translateZ(110px); padding: 30px; } /* 搜索展示界面 标题====>(歌曲列表) */ #display h2{ text-align: center; color: rgb(255, 0, 0); } #display p { color: #607391; font-size: 12px; margin: 5px; 0; padding:0 10px; height: 35px; line-height: 35px; } #display p:hover{ background-color: #fff; } #display p span { margin-top: 5px; float: right; width: 45px; height: 25px;; box-sizing: border-box; border-radius: 5px; background-color: rgb(55, 110, 214); box-shadow: inset 1px 1px 5px 0px rgba(50,88,130,.32); text-align: center; line-height: 25px; font-size: 12px; color: #ccc; cursor: pointer; } /* 展示版块关闭按钮样式 */ #display .close{ position: relative; } #display .close span{ width: 30px; height: 30px; border-radius: 50%; position: absolute; top: 10px; right: 10px; background-color: lightblue; box-shadow: inset 1px 1px 5px 0px rgba(50,88,130,.32); text-align: center; line-height: 30px; cursor: pointer; } </style> </head> <body> <div id="box"> <!-- 搜索信息展示版块 ==>默认display:none --> <div id="display"> <div class="close"><span>X</span></div> <h2>歌曲列表</h2> <div id="infoBox"> <!-- <p>歌曲名-Redbone <span>听这首</span></p> --> </div> </div> <!-- 左侧版块 --> <div class="leftBg"> <!-- 滚动信息 --> <marquee behavior="scroll" direction="left">Current version of Baidu Music Interface</marquee> <!-- 文字 ==> THIS IS MUSIC --> <p class="p1">THIS <br>IS </p> <p class="p2">MUSIC</p> <!-- 白色装饰条 --> <div></div> <div></div> </div> <!-- 中间板块 --> <div class="centerBg"> <!-- 搜索框+搜索按钮 --> <input type="text" value="" id="search"><span id="searchSpan">搜索</span> <div id="bfBox"> <!-- 歌曲图片 --> <div class="imgBox"> <img src="http://p2.music.126.net/5i5SKVW_F1ub2BgDeyjI5A==/3225967119049341.jpg?param=300x300" alt=""> </div> <!-- 歌曲信息 --> <div class="info"> <p>Come and Get Your Love</p> <p>Redbone</p> <!-- 播放器 --> <audio src="http://music.163.com/song/media/outer/url?id=28864241.mp3" controls></audio> </div> </div> </div> <!-- 右侧板块 ==> 未开发 --> <div class="rightBg"> </div> </div> </body> <!-- <script src="./js/ajax.js"></script> --> <script> class Music{ constructor(ele){ this.ele = ele; this.box = document.getElementById("box"); this.text = document.getElementById("search"); this.bfBox = document.getElementById("bfBox"); this.url = "https://api.apiopen.top/searchMusic"; this.infoBox = document.getElementById("infoBox"); this.displayPage = document.getElementById("display"); this.disPageClose = document.querySelector("#display .close span"); this.searchBtn = document.getElementById("searchSpan"); this.addEvent(); } // 绑定事件函数 addEvent(){ var that = this; // 鼠标在body上移动,将坐标转换成旋转角度,形成视差 document.onmousemove = function (eve){ var e = eve || window.event; that.x = (e.clientX-that.ele.offsetWidth/2)*0.01; that.y = -(e.clientY-that.ele.offsetTop/2)*0.01;; that.display(); } //搜索框发生改变,生成搜索内容 this.searchBtn.onclick = function(){ var str = ""; that.url = "https://api.apiopen.top/searchMusic"; str = "?name="+that.text.value; that.url += str; that.load(); } } // ajax请求数据 load(){ var that = this; ajax({ url:this.url, success:function(res){ that.res = JSON.parse(res); console.log(that.res.result); that.display2(); } }) } //形成视差 display(){ this.box.style.transform = "rotateY(" + this.x + "deg) rotateX(" + this.y + "deg)"; } //生成搜索内容 //请求完成,获得数据,将数据展示到搜索页面上 display2(){ this.displayPage.style.display = "block" var str = ""; // 如果搜索为空,显示搜索内容为空 //否则,展示搜索内容 if(this.res.result.length == 0){ str = "<h3>搜索内容为空</h3>" }else{ for(var i=0;i<this.res.result.length;i++){ str+=`<p>${this.res.result[i].title}-${this.res.result[i].author} <span>听这首</span></p>` } } this.infoBox.innerHTML = str; this.change(); this.disClose(); } //点击选中的歌曲,改变播放器页面的信息 change(){ var that = this; this.span = document.querySelectorAll("#infoBox p span"); console.log(this.span); // 遍历所有的歌曲span,找到点击所在的索引,保存到num中 //同时:如果选中了歌曲,就关闭搜索展示页面 for(var i=0;i<this.span.length;i++){ this.span[i].index = i; this.span[i].onclick = function (){ this.num = that.span[this.index].index that.reMove(); that.display3(this.num); // console.log(this.num); } } } // 关闭按钮方法 ===> 点击关闭按钮(disPageClose) 执行关闭搜索页面 disClose(){ var that = this; this.disPageClose.onclick = function(){ that.reMove(); } } //移除方法 ====> 执行该函数,清空搜索界面所有内容,并隐藏 reMove(){ this.displayPage.style.display = "none"; this.infoBox.innerHTML = ""; } //播放器音乐信息展示方法 ===> 将ajax请求到的数据,写入到播放内容区(bfBox) display3(num){ var str = ""; for(var i=0;i<this.res.result.length;i++){ str = `<div class="imgBox"> <img src="${this.res.result[num].pic}" alt=""> </div> <div class="info"> <p>${this.res.result[num].title}</p> <p>${this.res.result[num].author}</p> <audio src="${this.res.result[num].url}" controls></audio> </div>` } this.bfBox.innerHTML = str; } } //ajax封装函数 function ajax(options) { var { type, url, success, error, data, timeout } = options; type = type || "get"; data = data || {}; timeout = timeout || 2000; var str = ""; for (var i in data) { str += `${i}=${data[i]}&`; } if (type == "get") { var d = new Date(); url = url + "?" + str + "__qft=" + d.getTime(); } var xhr = new XMLHttpRequest(); xhr.open(type, url, true); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { success && success(xhr.responseText); error = null; } else if (xhr.readyState == 4 && xhr.status != 200) { error && error(xhr.status); success = null; error = null; } } setTimeout(() => { error && error("timeout"); success = null; }, timeout); if (type == "post") { xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.send(str) } else { xhr.send() } } var body = document.body; new Music(body); </script> </html>