网易云音乐,凭借其简洁的播放界面设计、歌曲推荐系统和完善的评论机制在市场上拥有超高人气的一款音乐播放器,深受网友喜爱。所以在这个在这个举国欢庆的假日里,我的魔爪终于伸向了她。

更新:
Github传送门,3首有歌曲和图片,大概15M。

主要运用到的知识:gulp 、jQuery、 H5、 css3、 cookie等。

实现的功能有:

  • 播放和暂停音频:播放状态下,碟盘旋转且唱臂搭在碟盘上,暂停状态下,碟盘停止旋转且唱臂弹回,当再次播放时,碟盘从刚才停止的位置进行旋转
  • 滑屏切歌:碟盘可跟随鼠标进行滑动,且切入的碟盘刚一出现就渲染好切入盘要展现的歌曲图片,当碟盘滑动到一定位置进行有过渡动画的切歌;当没有滑到位时松开鼠标,碟盘自动弹回播放位置。
  • 左右切歌:具有切歌过渡动画。
  • 播放列表切歌:当前播放的歌曲,在列表中被选中为播放状态,列表每次被点击出来都会重新获取一遍,达到更新效果,点击列表中的歌曲后,该歌曲被播放且被选中为播放状态,碟盘无过渡动画,但碟盘重新从零开始旋转。
  • 进度条:动态渲染当前歌曲已播放的时间,可进行拖动播放音乐且拖动时,已播放的时间随着拖动进行变化。
  • 红心:可点击的收藏和取消收藏。
  • cookie保存当前正在播放歌曲的索引,以便下次打开直接播放当前音频播放完自动切歌且有切歌动画
  • 背景为当前歌曲图片的高斯模糊。
  • 其他:一些细节问题,具体可以参照移动端的网易云音乐。

因为是模块化开发,所以要介绍起来一两句话说不清楚,所以就简要说明几个难点,而且里面有大量注释,感兴趣的话就不可能看不懂。

一些难点:

  1. 碟盘旋转问题,暂停保留当前需旋转角度,切歌也会进行保留,不能达到理想效果。
  2. 滑屏切歌和左右切歌的过渡动画不能连贯,滑屏切就不能左右切歌。
  3. 进度条问题。

解答

  1. 首先明确一点,需要保留旋转角度的只有歌曲暂停时需要,其余情况都要置零,即不但要从零开始旋转,而且还要将切入和切出的碟盘的样式也要清零。
  2. 首先两种切歌的方式有很大的不同,滑屏的是切入盘跟随切出盘进行移动,切出盘又跟随鼠标移动,所以用拖拽;而左右歌只是一个位置切到另一个位置,中间再加上了过渡动画而已,所以可以用关键帧,而造成这个问题主要原因是运动的属性不同,要么都用left,或者都用transform,这里我用的是3个盘进行切换,然后将这3个盘的初始位置用transform进行固定,用left进行移动,当各盘过渡动画运动完成后更改class类名。
  3. 进度条可以用已播放时间占歌曲总时间的百分比来做,当开始播放音频时用getDate()来获取当前这一时刻,然后设置一个定时器requestAnimationFrame,运用递归来动态获取渲染这一帧动画所用时间占总时间的百分比,然后让小圆点按进度条宽的百分比进行移动;同样的拖拽小圆点后松开鼠标后,计算出当前位置到初始位置长度与进度条的宽度的百分比乘以该音频总时间比来求出当前要播放的时间秒数,然后从这个地方开始播放音频,同样暂停需要需要记录下当前播放的时长,其他情况需要把记录已播放时长置零。

话不多说上代码!

html部分:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="../css/index.css">
</head><body><div class="wrapper"><!-- 歌曲信息 --><div class="song-info"><div class="song-name">丑八怪</div><div class="sliger-name">薛之谦</div></div><!-- 歌曲图片 --><div class="song-img"><div class="arm-img"><img src="../image/play.png" alt=""></div><div class="img-box pro-img" data-deg=0><div><img src="../image/bg.jpg" alt=""></div></div><div class="img-box curr-img" data-deg=0><div><img src="../image/bg.jpg" alt=""></div></div><div class="img-box next-img" data-deg=0><div><img src="../image/bg.jpg" alt=""></div></div></div><!-- 进度条 --><div class="pro"><div class="cur-time">00:00</div><div class="pro-wrap"><div class="pro-buttom"><div class="pro-top"><div class="slider"></div></div></div></div><div class="all-time">03:56</div></div><!-- 控件部分 --><div class="control" direction="none"><div class="btn like"></div><div class="btn prev"></div><div class="btn play"><div class="btn-play"></div></div><div class="btn next"></div><div class="btn list" song-index="0"><div class="list-top"><div class="head"><div class="title"><div>当前播放</div><span class="count"></span></div><ul><li class="song-cycle">单曲循环</li><li class="collect-all">收藏全部</li></ul></div><div class="songList"><ul></ul></div></div></div></div><div class="shadow"></div></div><script src="../js/zepto.min.js"></script><script src="../js/gaussBlur.js"></script><script src="../js/render.js"></script><script src="../js/audioControl.js"></script><script src="../js/indexControl.js"></script><script src="../js/songList.js"></script><script src="../js/progress.js"></script><script src="../js/imageSitching.js"></script><script src="../js/index.js"></script>
</body>
</html>

less部分:

@keyframes moveIn-left {0%{left: 0;}to{left: -132%;}
}@keyframes moveOut-left {0%{left: 0px;}to{left:-142% ;}
}@keyframes moveIn-right {0%{left: 0;}to{left: 142%;}
}@keyframes moveOut-right {0%{left: 0px;}to{left: 132%;}
}@keyframes songNameRuning {form{transform: translate3d(1%, 0px, 0px);}100%{transform: translate3d(-100%, 0px, 0px);}
}*{margin:0px;padding:0px;list-style:none;body{font-size: 12px;background-repeat: no-repeat;background-size: 100% 100%;background-position: center;box-sizing: border-box;.wrapper{height: 100Vh;width: 100Vw;background-color: rgba(0, 0, 0, 0.2);overflow: hidden;//歌曲信息.song-info{position: absolute;width: 80%;height: 80px;top: 0%;left: 50%;transform: translatex(-50%);margin-top:5px;text-align: left;font-family: 'fangsong';color: #fff;overflow: hidden;.song-name{margin-bottom: -5px;font-size: 18px;font-weight: 700;line-height: 36px;white-space: nowrap;transform: translate3d(1%, 0px, 0px);}.sliger-name{margin-left: 1%;font-size: 14px;color: rgba(255, 255, 255, .5);}}//歌曲图片部分.song-img{position: relative;width: 70%;height: 0px;top: 20%;padding-top: 70%;               //top和left的百分比都是参照父级的宽margin: 0px auto;.arm-img{position: absolute;height: 100px;width: 125px;top: -30%;left: 45%;transform-origin: 7% 6.5%; transform: translatez(0px) rotatez(-5deg);transition: transform .3s cubic-bezier(1, 0.8, 1, 1);z-index: 1;&.playing{transform: translatez(0px) rotatez(20deg);}img{display: block;height: 100%;width: 100%;}}.arm-img::before{content:'';position: absolute;height: 25px;width: 25px;left: -3.5%;top: -5%;background-color: rgba(0, 0, 0, .2);border-radius: 50%;}.img-box{position: absolute;top: 0px;width: 100%;height: 0px;padding-top: 100%;border-radius: 50%;background-color: #000;div{position: absolute;height: 100%;width: 100%;top: 0%;left: 0;transform-origin: center;img{position: absolute;left: 50%;top: 50%;transform: translate3d(-50%, -50%, 0px); width: 70%;height: 70%;border-radius: 50%;}}&.pro-img{transform : translate3d(-142%, 0px, 0px);}&.curr-img{transform : translate3d(0px, 0px, 0px);}&.next-img{transform : translate3d(132%, 0px, 0px);}}}.song-img::before{position: absolute;content: '';height:102%;width: 102%;top: -1%;left: -1%;background-color: rgba(255, 255, 255, .2);border-radius: 50%;}// 歌曲进度条.pro{display: flex;position: absolute;width: 100%;top: 80%;left: 0px;.cur-time,.all-time{height: 40px;width: 60px;text-align: center;font-size: 10px;font-family: '宋体';font-weight: 100;line-height: 40px;color: #ffff;}.pro-wrap{position: relative;display:flex;justify-content: center;align-items: center;flex: 1;overflow: hidden;.pro-buttom{position: absolute;height: 2px;width: 96%;background-color: rgba(0, 0, 0, .2);.pro-top{position: absolute;width: 98%;height: 2px;left: -100%;background-color: rgba(0, 0, 0, .8);.slider{position: absolute;top: -3px;left: 96%;height: 8px;width: 8px;background-color: #fff;margin-left: 9px;border-radius: 50%;}}}}}//歌曲控件.control{position: absolute;display: flex;justify-content: center;width: 100%;height: 12%;top: 86%;.btn{flex: 1;height: 100%;background-size: 20px;background-repeat: no-repeat;background-position: center 45%;}.like{background-image: url('../image/icon-like.png');&.liking{       //同时具有该类名background-image: url('../image/icon-like-solid.png')}}.prev{background-image: url('../image/icon-prev.png');}.play{display: flex;justify-content: center;align-items: center;.btn-play{height: 80%;width: 80%;background-image: url('../image/icon-play.png');background-size: 20px;background-repeat: no-repeat;background-position: center;border:2px solid #fff;border-radius: 50%;box-sizing: border-box;}&.playing{.btn-play{background-image: url('../image/icon-pause.png')}}}.next{background-image: url('../image/icon-next.png');}.list{background-image: url('../image/icon-list.png');overflow:hidden;.list-top{opacity: 0;transform: translate3d(-50%, 25%, 0);transition: transform .2s cubic-bezier(1, 0.5, 1, 1);}&.playList{z-index: 10;.list-top{position: absolute;height: 460px;width: 90%;left: 50%;border-radius: 20px;overflow: hidden;background-color: #fff;.head{width: 100%;height: 80px;margin-top: 10px;.title{display: flex;justify-content: flex-start;div{height: 50px;width: 110px;font-size: 20px;font-weight: 500;text-align: center;line-height: 50px;margin-left: 10px;}span{margin-top: 20px;color: rgba(0, 0, 0, 0.5);}}ul{display: flex;justify-content: space-between;width: 100%;height: 30px;li{height: 30px;width: 50%;line-height: 30px;margin-left: 50px;}li:before,.collect-all:after{position: absolute;content:'';height: 25px;width: 25px;left: 22px;top: 62px;background-image: url('../image/1.jpg') ;background-size: 150px 150px;background-position: 217px 418px;}.collect-all:before{left: 184px;}.collect-all:after{left: 284px;}}}.songList{height: 360px;width: 100%;margin-top: 10px;ul{overflow: scroll;width: 100%; height: 100%;li{position: relative;height: 40px;width: 100%;line-height: 40px;span{display: block;width: 85%;margin-left: 15px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;}p.bj-clear{float:right;height: 10px;width: 10px;margin-top: -25px;margin-right: 10px;background-image:url('../image/icon-close.png');background-size: 10px;background-position: 5px 5px;background-color: rgba(0, 0, 0, .5);border-radius: 50%;}&.active::before{position: absolute;content:'';height: 20px;width: 20px;top: 12px;left: 5px;background-image:url(../image/1.jpg);background-size: 612%;background-position: -27px -76px;background-repeat: no-repeat;background-color: red;border-radius: 50%;border:2px solid red;box-sizing: border-box;} }}}}}}}}//遮罩层.shadow{display: none;position: absolute;height: 100Vh;width :100%;top: 0%;background-color: rgba(0, 0, 0, 0.5);z-index: 5;}}
}

接下来是js部分:
首先是主JS文件index.js
我用的不是jQuery库,而是更小的Zepto库,其用法和jQuery一样,只是为了更快的在移动端加载。


var root = window.player;// 记录请求到的数据
var dataList;var audio = root.audioManager;
var list;
var control;// 记录请求到的数据总长
var len;
var timer;
var deg = 0;var currSongIndex;
getCookie();// 记录当前歌曲在列表的位置
var scroTop = 0;// 歌名太长进行移动
function songName () {var $SongName = $('.song-info').find('.song-name')var str = $SongName.text();var len = str.length;var count = 0;var nameLen;for (var i = 0; i < len; i++) {if (str[i].charCodeAt() > 255) {count++;}}nameLen = len + count;if (nameLen > 30) {$SongName.css({'animation': 'songNameRuning 10s cubic-bezier(0, 0, 1, 1) alternate infinite'})} else {$SongName.css({'transform': 'translate3d(1%, 0px, 0px)'})}
}// 获取数据
function getData(url) {$.ajax({type: 'GET',url: url,success: function (data) {dataList = data;len = data.length;control = new root.controlIndex(len, currSongIndex);// 初始化渲染图片和背景高斯模糊root.rendering(data, currSongIndex);// 初始化歌曲audio.getAudio(data[currSongIndex].audio)// 获取歌曲总时长root.progress.renderTotalTime(data[currSongIndex].duration);// 绑定非列表控件的点击事件bindEvent();//绑定移动端拖拽事件 bindTouch();},error: function (e) {console.log('未请求到音频资源,请检查网络。');}})
}//  绑定移动端事件
function bindTouch() {function proWrap() {var left = $('.pro-wrap').offset().left;var width = parseInt($('.pro-wrap').css('width'));$('.slider').on({touchstart: function () {$(this).css({'height': '13px','width': '13px','top': '-5px'})root.progress.stop();},touchmove: function (e) {var x = e.changedTouches[0].clientX;var pro = (x - left) / width;if (pro < 0) {pro = 0} else if (pro > 1) {pro = 1;}audio.pause();root.progress.updata(pro);if ($('.play').attr('class').indexOf('playing') > -1) {$('.play').add('.arm-img').removeClass('playing');// 碟盘停止旋转cancelAnimationFrame(timer);};},touchend: function (e) {$(this).css({'height': '8px','width': '8px','top': '-3px'})var x = e.changedTouches[0].clientX;var pro = (x - left) / width;if (pro < 0) {pro = 0} else if (pro > 1) {pro = 1;}var currPlay = root.progress.conversonSecondTime(dataList[currSongIndex].duration) * proroot.progress.updata(pro)setTimeout(function () {root.audioManager.playTo(currPlay);$('.play').add('.arm-img').addClass('playing');songName();currSongIndex = $('.list').attr('song-index');root.progress.start(dataList[currSongIndex], pro);deg = $('.curr-img').attr('data-deg');rotate(deg);}, 1500)}})}function songImg() {var currStartX;var currLastX;var currBoxLeft;var width = $('.curr-img').width();var bodyWidth = $('body').width();$('.img-box').on({touchstart: function (e) {currStartX = e.changedTouches[0].pageX;//最近有定位的父级$('.curr-img').css({'transition': 'none'})cancelAnimationFrame(timer);$('.arm-img').add('.play').removeClass('playing');},touchmove: function (e) {currLastX = e.changedTouches[0].pageX;// 让当前展示的碟盘随鼠标拖拽移动$('.curr-img').css('left', currLastX - currStartX);// 如果当前展示的碟盘右碰到边框,展示上一首歌的碟盘currBoxLeft = $('.curr-img').offset().left;if (currBoxLeft + width >= bodyWidth) {$('.pro-img').css({'display': 'block','transform': 'translate3d(-142%, 0px, 0px)','left': currLastX - currStartX,'transition': 'none'});// 如果当前展示的碟盘右碰到边框,展示下一首歌的碟盘} else if (currBoxLeft <= 0) {$('.next-img').css({'display': 'block','transform': 'translate3d(132%, 0px, 0px)','left': currLastX - currStartX,'transition': 'none'})} else {$('.moveIn').css('left', 0)}},touchend: function (e) {// 切下一首歌if (currBoxLeft < (-width / 2)) {$('body').trigger('play-changer', control.next());root.switch.imgTouchMove($('.next-img'), $('.pro-img'), 'next-img', 'pro-img', '-132%', '-142%', '-132%')// 切上一首歌} else if (currBoxLeft >= bodyWidth - (width / 2)) {$('body').trigger('play-changer', control.prev());root.switch.imgTouchMove($('.pro-img'), $('.next-img'), 'pro-img', 'next-img', '142%', '132%', '0')// 滑动碟盘切歌不成功       } else {$('.img-box').css({'left': 0,'transition': 'left .3s cubic-bezier(0, 0, 1, 1)'});if (audio.status == 'play') {deg = $('.curr-img').attr('data-deg');rotate(deg);$('.arm-img').add('.play').addClass('playing')}}}})}songImg();proWrap();
}// 绑定事件
function bindEvent() {//自定义一个播放状态改变的事件$('body').on('play-changer', function (e, index) {audio.getAudio(dataList[index].audio);$('.play').add('.arm-img').removeClass('playing');//切歌时唱针有回位动作setTimeout(function () {// 唱针解锁$('.play').add('.arm-img').addClass('playing');// 碟盘旋转rotate(0);// 延迟播放不但更真实,而且不会在列表里切歌发上冲突audio.play();}, 500)$('.list').attr('song-index', index)// 渲染歌曲总时间root.progress.renderTotalTime(dataList[index].duration);//渲染当前播放的时长及进度条root.progress.start(dataList[index]);// 歌曲索引缓存到浏览器的cookie中saveCookie(index)//每次切歌都将上次播放盘的旋转角度清零$('.curr-img').find('div').css({'transform': 'translatez(0px) rotateZ(0deg)','transition': 'none'}).attr('data-deg', '0')})// 上一曲$('.prev').on('click', function (e) {// 磁盘切换root.switch.imgClickMove($('.pro-img'), $('.next-img'), 'pro-img', 'next-img','moveIn-right', 'moveOut-right', '132%', '-142%')// 切歌$('body').trigger('play-changer', control.prev());})// 下一曲$('.next').on('click', function (e) {root.switch.imgClickMove($('.next-img'), $('.pro-img'), 'next-img', 'pro-img', 'moveIn-left', 'moveOut-left', '-142%', '132%' )$('body').trigger('play-changer', control.next());})// 播放&暂停$('.play').on('click', function () {// pause为暂停状态if (audio.status == 'pause') {audio.play();///碟盘旋转deg = $('.curr-img').attr('data-deg');rotate(deg);songName();// 渲染进度条移动和已播放的时间root.progress.start(dataList[currSongIndex], '', true)} else {audio.pause();// 碟盘停止旋转cancelAnimationFrame(timer);// 进度条和已播放时间停止渲染root.progress.stop();}$('.play').add('.arm-img').toggleClass('playing');})// 收藏到我喜欢$('.like').on('click', function () {if ($(this).attr('class').indexOf('liking') != -1) {$(this).removeClass('liking');dataList[currSongIndex].isLike = false;} else {$(this).addClass('liking');dataList[currSongIndex].isLike = true;}});//列表播放音乐$('.list').on('click', function (e) {e.stopPropagatoion ? e.stopPropagatoion() : e.cancelBubble = true;list = new root.songList(dataList);$(this).addClass('playList');// 初始化音乐列表并给音乐绑定事件list.renderListDom();//获取最新歌曲索引getCookie();    //给点击到的li标签动态添加样式$('ul', '.songList').find('li').eq(currSongIndex).addClass('active');$('.active').find('span').css('margin-left', '30px');// 展示列表,并移到指定地方$(this).find('.list-top').css({'opacity': '1','transform': ' translate3d(-50%, -82%, 0)',})// 点击列表进行切歌$('ul', '.songList').on('click', function (e) {var event = e.target || e.srcElement;// 让歌曲列表滑到上次点歌的坐标e.offsetY = scroTop;// 判断点击到的事件源if (parseInt($(event).css('width')) > 10) {// 暂停当前正在播放的音乐audio.pause();// 记录当前在列表点击的歌曲索引$('.list').attr('song-index', $(event).parent().attr('data-index'));// 刷新左右切歌的索引,并将字符串用隐式类型转化转为数字currSongIndex = $('.list').attr('song-index')control = new root.controlIndex(dataList.length, +currSongIndex);// 歌曲索引缓存到浏览器的cookie中saveCookie(currSongIndex);// 找到音频和图片root.rendering(dataList, $('.list').attr('song-index'));audio.getAudio(dataList[$('.list').attr('song-index')].audio);// 播放$('body').trigger('play-changer', $('.list').attr('song-index'));//记录当前的ul到屏幕的高度scroTop = e.offsetY;// 每次点击列表都会重绘ul所以不能在这里給li加active$('ul', '.songList').find('li').eq(currSongIndex).addClass('active');$('.img-box').css({'left': 0,'transition': 'left .3s cubic-bezier(0, 0, 1, 1)'});rotate(0)} else {// 点击到的是删除该歌曲console.log('待完成.....')}})// 绑定关闭列表的遮罩层上事件$('.shadow').css({ 'display': 'block' }).on('click', function () {//将列表移到屏幕下方$('.list').find('.list-top').css({'transform': ' translate3d(-50%, 25%, 0)',});//等列表退回原位才取消列表的样式setTimeout(function () {$('.list').removeClass('playList');}, 300)$(this).css({ 'display': 'none' })})})
}//旋转
function rotate(deg) {cancelAnimationFrame(timer);// 旋转值被记录到行间var deg = Number(deg);function frame () {deg += .2;$('.curr-img').attr('data-deg', deg.toFixed(1));$('.curr-img').find('div').css({'transform': 'translatez(0px) rotateZ(' + deg + 'deg)','transition': 'transform .1s cubic-bezier(0, 0, 1, 1) '})timer = requestAnimationFrame(frame)}frame();
}//当前歌曲播放完毕自动切歌
audio.bindMediaEnd(function () {cancelAnimationFrame(timer);//默认下一首root.switch.imgClickMove($('.next-img'), $('.pro-img'), 'next-img', 'pro-img', 'moveIn-left', 'moveOut-left', '-142%', '132%' )$('body').trigger('play-changer', control.next());
});// 把当前的歌曲索引缓存到浏览器的cookie中
function saveCookie(currSongIndex) {document.cookie = 'index=' + currSongIndex + ';max-age=1000000000';
}//获取cookie
function getCookie() {if (document.cookie.match(/\w+=\d+$/g) != null) {var reg = /^\w+=/gcurrSongIndex = +document.cookie.match(/\w+=\d+$/g)[0].replace(reg, function ($) {return $ = '';});} else {currSongIndex = 0;}
}getCookie();getData('../moke/data.json')

高斯模糊背景功能js文件:


(function ($, root) {'use strict';function gaussBlur(imgData) {var pixes = imgData.data;var width = imgData.width;var height = imgData.height;var gaussMatrix = [],gaussSum = 0,x, y,r, g, b, a,i, j, k, len;var radius = 10;var sigma = 5;a = 1 / (Math.sqrt(2 * Math.PI) * sigma);b = -1 / (2 * sigma * sigma);//生成高斯矩阵for (i = 0, x = -radius; x <= radius; x++, i++) {g = a * Math.exp(b * x * x);gaussMatrix[i] = g;gaussSum += g;}//归一化, 保证高斯矩阵的值在[0,1]之间for (i = 0, len = gaussMatrix.length; i < len; i++) {gaussMatrix[i] /= gaussSum;}//x 方向一维高斯运算for (y = 0; y < height; y++) {for (x = 0; x < width; x++) {r = g = b = a = 0;gaussSum = 0;for (j = -radius; j <= radius; j++) {k = x + j;if (k >= 0 && k < width) {//确保 k 没超出 x 的范围//r,g,b,a 四个一组i = (y * width + k) * 4;r += pixes[i] * gaussMatrix[j + radius];g += pixes[i + 1] * gaussMatrix[j + radius];b += pixes[i + 2] * gaussMatrix[j + radius];// a += pixes[i + 3] * gaussMatrix[j];gaussSum += gaussMatrix[j + radius];}}i = (y * width + x) * 4;// 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题// console.log(gaussSum)pixes[i] = r / gaussSum;pixes[i + 1] = g / gaussSum;pixes[i + 2] = b / gaussSum;// pixes[i + 3] = a ;}}//y 方向一维高斯运算for (x = 0; x < width; x++) {for (y = 0; y < height; y++) {r = g = b = a = 0;gaussSum = 0;for (j = -radius; j <= radius; j++) {k = y + j;if (k >= 0 && k < height) {//确保 k 没超出 y 的范围i = (k * width + x) * 4;r += pixes[i] * gaussMatrix[j + radius];g += pixes[i + 1] * gaussMatrix[j + radius];b += pixes[i + 2] * gaussMatrix[j + radius];// a += pixes[i + 3] * gaussMatrix[j];gaussSum += gaussMatrix[j + radius];}}i = (y * width + x) * 4;pixes[i] = r / gaussSum;pixes[i + 1] = g / gaussSum;pixes[i + 2] = b / gaussSum;}}//endreturn imgData;}// 模糊图片function blurImg(img, ele) {var w = img.width,h = img.height,canvasW = 40,canvasH = 40;var canvas = document.createElement('canvas'),ctx = canvas.getContext('2d');canvas.width = canvasW;canvas.height = canvasH;ctx.drawImage(img, 0, 0, w, h, 0, 0, canvasW, canvasH);var pixel = ctx.getImageData(0, 0, canvasH, canvasH);gaussBlur(pixel);ctx.putImageData(pixel, 0, 0);var imageData = canvas.toDataURL();ele.css('background-image', 'url(' + imageData + ')');}root.blurImg = blurImg;})(window.Zepto, window.player || (window.player = {}));

初始化渲染页面(歌曲图片、歌名、歌手、收藏按钮状态)

(function ($, root) {function renderImage (dataList, index) {var img = new Image();var len = dataList.length;img.src = dataList[index].image;// 图片加载回来才渲染,img.onload = function () {$('.pro-img img').attr('src', dataList[((index - 1) + len ) % len].image);$('.curr-img img').attr('src', dataList[index].image);$('.next-img img').attr('src', dataList[(index + 1) % len].image);root.blurImg(img, $('body'));}}function renderInfo (info) {var str = '<div class="song-name">'+ info.song +'</div>\<div class="sliger-name">'+ info.singer +'</div>';$('.song-info').html(str);}function renderIsLike(like) {if (like) {$('.like').addClass('liking');}else{$('.like').removeClass('liking');}}root.rendering = function (dataList, index) {renderImage(dataList, index);renderInfo(dataList[index]);renderIsLike(dataList[index].isLike)}})(window.Zepto, window.player || (window.player = {}))

控制进度条功能文件

(function ($, root) {var frameId;var lastPercent = 0;var time = 0;   //当前歌曲已播放的时间var secondTime;var startTime;//将小于1小时的00:00:00时间格式转换为00:00形式function renderTotalTime(totalTime) {// 记录当前展示的歌曲的总时长的秒数时间conversonSecondTime(totalTime)var regHou = /^\d+/g;if (totalTime.match(regHou)[0] == 0 && totalTime.length > 4) {var regRemovrHou = /^\d+:/g;var allTime = totalTime.replace(regRemovrHou, function ($) {return $ = '';})$('.all-time').text(allTime);} else {$('.all-time').text(totalTime);};}//将00:00:00形式的时间转换为秒的时间function conversonSecondTime(data) {var time = data.duration || data;var regHou = /^\d+/g;var regMin = /:\d+:/g;var regSec = /\d+$/g;var hours = +time.match(regHou)[0];var minute = +time.match(regMin)[0].split(':')[1];var second = +time.match(regSec)[0];secondTime = hours * 3600 + minute * 60 + second;return secondTime;}// 将一个时间秒转换为00:00:00形式function renderPlayTime(time) {var str, hours, minute, second;if (time > 3600) {var num = parseInt(parseInt(time / 3600));hours = num >= 10 ? num + ':' : '0' + num + ':';minute = (time - num * 3600) / 60 >= 10 ?parseInt((time - num * 3600) / 60) + ':' : '0' + parseInt((time - num * 3600) / 60) + ':';} else {hours = parseInt(time / 3600) > 0 ? (parseInt(time / 3600) >= 10 ? (parseInt(time / 3600) + ':') : ('0' + parseInt(time / 3600) + ':')) : '';minute = parseInt(time / 60) >= 10 ? (parseInt(time % 3600) >= 59 ? (parseInt(parseInt(time % 3600) / 60) + ':') : (parseInt(time / 60) + ':')) : ('0' + parseInt(time / 60) + ':');}second = parseInt(time % 60) >= 10 ? parseInt(time % 60) : '0' + parseInt(time % 60);str = hours + minute + second;$('.cur-time').html(str).css({ 'line-height': '38px' }) //修改样式是点击播放有颤动}function updata(percent) {// 渲染当前已播放的时间renderPlayTime(secondTime * percent);var percentage = percent * 100$('.pro-top').css({'transform ': 'translate3d(' + percentage + '%, 0px, 0px)'});//有自动切歌的话就不需要这个判断,会冲突。// if (percentage == 100) {//     stop();//     $('.play').removeClass('playing');//     lock--;//     percentage = 0;// }}// 时间和进度条的改变function start(data, pro, lock) {//每次切歌都将歌曲上次持续播放的时长置0,pro为拖拽进度条的已播放时长 lock点击暂停的情况lastPercent = lock ? lastPercent :  pro || 0;cancelAnimationFrame(frameId);// 记录当前点击播放的时刻 startTime = new Date().getTime();var percent;function frame() {var currTime = new Date().getTime();time = (currTime - startTime) / 1000;percent = lastPercent + (time / conversonSecondTime(data));frameId = requestAnimationFrame(frame);updata(percent);}frame()}function stop() {//记录当前暂停播放的时刻var stopTime = new Date().getTime();//歌曲上次持续播放的时长 = 上次暂停 + 上次开始 + 当前暂停lastPercent += ((stopTime - startTime) / secondTime / 1000);cancelAnimationFrame(frameId);}root.progress = {renderTotalTime: renderTotalTime,start: start,stop: stop,updata: updata,conversonSecondTime: conversonSecondTime,}})(window.Zepto, window.player || (window.player = {}))

左右切歌控制文件

(function ($, root) {function Control(len, index) {this.index = index;this.len = len;}Control.prototype = {prev: function () {return this.getIndex(-1);},next: function () {return this.getIndex(1);},getIndex: function (val) {//当前歌曲的索引var index = this.index;//数据总数var len = this.len;// 改变后的索引var currIndex = (index + val + len) % len;this.index = currIndex;return currIndex;}}root.controlIndex = Control;
})(window.Zepto, window.player || (window.player = {}))

音频播放和暂停控制文件

(function ($, root) {// 播放 暂停 获取音频不播放function AudioManager() {//创建一个audio对象this.audio = new Audio();// audio默认状态this.status = 'pause';}AudioManager.prototype = {play: function () {this.audio.play();this.status = 'play';},pause: function () {this.audio.pause();this.status = 'pause';},playTo: function (pro) {console.log('lllll')this.audio.currentTime = pro;this.play();},getAudio: function (src) {this.audio.src = src;this.audio.load();},bindMediaEnd  : function (handle) {this.audio.onended = handle;}}root.audioManager = new AudioManager();})(window.Zepto, window.player || (window.player = {}))

列表切歌控制文件

(function ($, root) {function List(data) {this.data = data;this.index;}List.prototype = {renderListDom: function () {var str = '';var len = this.data.length;for (var i = 0; i < len; i++) {str += ' <li data-index="' + i + '">\<span>'+ this.data[i].song + '-' + this.data[i].singer + '</span>\<p class="bj-clear"></p>\</li>';}// 每次点击list图标都重新渲染一个新的list列表$('.count', '.playList').text('(' + len + ')');$('ul', '.songList').html(str);},}root.songList = List;
})(window.Zepto, window.player || (window.player = {}))

歌曲图片切换控制文件

(function ($, root) {// 类似窗口向后移动一个碟盘,然后将最右边碟盘移动到最左边,并在切盘碟的过渡动画完成后将class类名改正     //点击切歌图片过渡运动 形参:$切入盘,$补充盘,切入盘class类名,补充盘class类名,切入的关键帧名,切出盘的关键帧名,切出盘切出到的位置,补充盘补充的位置function imgClickMove ($In, $Move, In, Move, FuncIn, FuncOut, OutTo, MoveTo) {$('.arm-img').removeClass('playing');cancelAnimationFrame(timer);$In.css({'animation': FuncIn + '.5s cubic-bezier(0, 0, 1, 1) forwards'}).addClass('moveIn')$('.curr-img').css({'animation': FuncOut + '.5s cubic-bezier(0, 0, 1, 1) forwards'}).addClass('moveOut');//点击切歌后重新渲染这三张图片setTimeout(function () {$('.moveIn').attr('class', 'img-box curr-img').css({'left': '0px','transform' : 'translate3d(0px, 0px, 0px)','animation': 'none'// 每次让切出去的磁盘的初始旋转角度置0}).find('div').css({'transform': 'translatez(0px) rotateZ(0deg)','transition': 'none'}).attr('data-deg', '0');$Move.addClass(In).removeClass(Move).css({'left': '0px','transform' : 'translate3d(' + MoveTo + ', 0px, 0px)',  'animation': 'none'})$('.moveOut').addClass(Move).removeClass('curr-img moveOut').css({'left': '0px','transform' : 'translate3d(' + OutTo + ', 0px, 0px)',   'animation': 'none'})rotate(0);getCookie();root.rendering(dataList, currSongIndex);songName()}, 600)}//  形参分别为:$切入盘,$补充盘,切入盘class类名,补充盘class类名,切入盘切入到的位置,切出盘切出到的位置,补充盘补充的位置function imgTouchMove($In, $Move, In, Move, InTo, OutTo, MoveTo) {$In.css({'left': InTo,'transition': 'left .3s cubic-bezier(0, 0, 1, 1)'}).addClass('moveIn')$('.curr-img').css({'left': OutTo,'transition': 'left .3s cubic-bezier(0, 0, 1, 1)'}).addClass('moveOut')$Move.css({'left': MoveTo,'transform' : 'translate3d(-142%, 0px, 0px)','transition': 'none',})getCookie();setTimeout(function () {// 移动图片后重新排列图片类名并重绘图片$('.moveIn').attr('class', 'img-box curr-img').css({'left': 0,'transform': 'translate3d(0px, 0px ,0px)','transition': 'none'})$Move.addClass(In).removeClass(Move).css({'left': '0','transform' : 'translate3d('+ -InTo +', 0px, 0px)','transition': 'none',})$('.moveOut').addClass(Move).removeClass('moveOut curr-img').css({'left': 0,'transform' : 'translate3d('+ OutTo +', 0px, 0px)','transition': 'none'})root.rendering(dataList, currSongIndex)            }, 400)setTimeout(function () {deg = $('.curr-img').attr('data-deg');rotate(deg);$('.arm-img').addClass('playing')}, 500)}root.switch = {imgClickMove:imgClickMove,imgTouchMove:imgTouchMove}})(window.Zepto, window.player || (window.player = {}))

以上

数据格式

总结:

  • 左右大幅滑动切歌会有bug,在网易上有卡顿的问题。
  • 项目中是会有出现一个警告说主动触发了一个可以被动触发的事件,有些搞不明白,但不影响播放,不过如果可以优化可以使页面更具有响应性。

上一个我做的和si一样的动态图,需要展示的东西太多,超过限制,所以删除了动态图的重复帧,大家先凑合着看吧

真实效果比这好看流畅多了,我去好辣眼

有空会将项目上传到GitHub上…

gulp仿移动端网易云音乐播放界面相关推荐

  1. PC端网易云音乐播放云盘音乐时显示加载失败,自动调转下一首的解决方法

    PC端网易云音乐播放云盘音乐时显示加载失败,自动调转下一首解决方法 注意: 一定要看看是不是和你的情况一样,不一样不要用这种方法!!! 具体情况: 我们经常会下载歌曲存到电脑文件夹里,然后通过网易云音 ...

  2. 使用css动画实现网易云音乐播放界面波浪动画效果

    通过实现CSS实现仿网易云音乐播放界面动画效果,最终的效果如下 界面布局 图片也是实现滚动效果的,使用四个div,来标识每一帧波动的效果. <div class="container- ...

  3. Android仿网易云音乐播放界面

    概述 网易云音乐是一款非常优秀的音乐播放器,尤其是播放界面,使用唱盘机风格,显得格外古典优雅.这里抛砖引玉,原文地址:http://www.jianshu.com/p/cb54990219d9 首先来 ...

  4. java4android网易云,Android仿网易云音乐播放界面

    概述 网易云音乐是一款非常优秀的音乐播放器,尤其是播放界面,使用唱盘机风格,显得格外古典优雅. 首先来看一下网易的播放效果. 要实现上面的功能,我们需要对界面进行一个拆分,拆分后大概包含如下结构: 主 ...

  5. 仿网易云音乐播放界面

    前言 网易云音乐是一款非常优秀的音乐播放器,尤其是播放界面,使用唱盘机风格,显得格外古典优雅.笔者出于学习与挑战的想法,思考播放界面背后的实现原理,并写了一个小程序. 笔者尽可能地去模仿官方的视觉.交 ...

  6. 微信小程序 --- CSS实现仿网易云音乐播放界面效果(黑胶唱片与唱针纯CSS实现)

    下面代码的效果是网易云音乐唱针和黑胶唱片的CSS效果实现方式,播放等并没贴出来 实现效果的范围 动态图效果预览: stylusW,panW是获取系统宽度计算后的参数 wxml部分: <!-- 黑 ...

  7. 自定义仿网易云音乐播放界面

    清单文件添加 <service android:name="com.rookie.shiyue20180528.model.MusicService"android:enab ...

  8. Android高仿网易云音乐播放界面

    现在很多的播放器的播放界面都是采用光盘的转动,下面是我仿造网易的播放界面.先上两张图: 第一张为播放前的界面,第二张为点击播放按钮的图片.布局文件如下: <RelativeLayout xmln ...

  9. 微信小程序网易云音乐播放界面

    微信小程序网易云 效果图 HTML JS CSS 效果图 HTML <view class="box"><!-- 毛玻璃 --><view class ...

最新文章

  1. Scala 中下划线的用法
  2. Framelayout
  3. 【Linux系统编程】浅谈进程地址空间与虚拟存储空间
  4. aspnet_regsql
  5. JavaScript权威指南笔记
  6. iostat linux,iostat 命令详解
  7. 保存网站快捷方式到桌面的php代码,转的一个朋友的了,加了点判断
  8. OC category(分类)
  9. linux shell grep 多个文件
  10. 三大框架SSM基础知识点
  11. C# Json转list List转json
  12. python新闻爬虫_基于Python的网络新闻爬虫与检索
  13. xctf crazy
  14. 一个人九月份开始考北邮的经验
  15. photoshop抠图场景二
  16. css压缩有啥好处呢?
  17. (学习笔记)JAVA开发需要掌握哪些技术?
  18. AM5728调试经历
  19. Clock skew too great
  20. 【Monkey测试】Monkey测试详解

热门文章

  1. Linux硬盘管理:分区、GParted分区操作
  2. php excel给excel批量插入图片
  3. 如何在VC中调用第三方lib库(step by step)
  4. 爬取王者荣耀高清皮肤
  5. 【激光雷达】之点云数据滤波处理
  6. 什么是XSL,它有什么用途
  7. Mac电脑调用自带的命令行窗口
  8. 用汇编语言写51单片机程序开头为什么一般都这样:ORG 0000H LJMP MAIN ORG 0030H
  9. 宾得rtk手簿说明书_那曲宾得RTK操作说明
  10. 幂律分布 计算机科学,Numpy 发现幂律分布