网易云音乐,凭借其简洁的播放界面设计、歌曲推荐系统和完善的评论机制在市场上拥有超高人气的一款音乐播放器,深受网友喜爱。所以在这个在这个举国欢庆的假日里,我的魔爪终于伸向了她。
更新: 项目已传GitHub上,音频文件有点多,大概15M,请理性下载Android-Music
主要运用到的知识:gulp 、jQuery、 H5、 css3、 cookie等。
实现的功能有:
播放和暂停音频:播放状态下,碟盘旋转且唱臂搭在碟盘上,暂停状态下,碟盘停止旋转且唱臂弹回,当再次播放时,碟盘从刚才停止的位置进行旋转滑屏切歌:碟盘可跟随鼠标进行滑动,且切入的碟盘刚一出现就渲染好切入盘要展现的歌曲图片,当碟盘滑动到一定位置进行有过渡动画的切歌;当没有滑到位时松开鼠标,碟盘自动弹回播放位置。左右切歌:具有切歌过渡动画。播放列表切歌:当前播放的歌曲,在列表中被选中为播放状态,列表每次被点击出来都会重新获取一遍,达到更新效果,点击列表中的歌曲后,该歌曲被播放且被选中为播放状态,碟盘无过渡动画,但碟盘重新从零开始旋转。进度条:动态渲染当前歌曲已播放的时间,可进行拖动播放音乐且拖动时,已播放的时间随着拖动进行变化。红心:可点击的收藏和取消收藏。cookie保存当前正在播放歌曲的索引,以便下次打开直接播放当前音频播放完自动切歌且有切歌动画背景为当前歌曲图片的高斯模糊。其他:一些细节问题,具体可以参照移动端的网易云音乐。因为是模块化开发,所以要介绍起来一两句话说不清楚,所以就简要说明几个难点,而且里面有大量注释,感兴趣的话就不可能看不懂。
一些难点:
碟盘旋转问题,暂停保留当前需旋转角度,切歌也会进行保留,不能达到理想效果。滑屏切歌和左右切歌的过渡动画不能连贯,滑屏切就不能左右切歌。进度条问题。解答:
首先明确一点,需要保留旋转角度的只有歌曲暂停时需要,其余情况都要置零,即不但要从零开始旋转,而且还要将切入和切出的碟盘的样式也要清零。首先两种切歌的方式有很大的不同,滑屏的是切入盘跟随切出盘进行移动,切出盘又跟随鼠标移动,所以用拖拽;而左右歌只是一个位置切到另一个位置,中间再加上了过渡动画而已,所以可以用关键帧,而造成这个问题主要原因是运动的属性不同,要么都用left,或者都用transform,这里我用的是3个盘进行切换,然后将这3个盘的初始位置用transform进行固定,用left进行移动,当各盘过渡动画运动完成后更改class类名。进度条可以用已播放时间占歌曲总时间的百分比来做,当开始播放音频时用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) * pro root.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+=/g currSongIndex = +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; } } //end return 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上…
