JS es6仿网易云音乐播放器

展示

音乐播放器

功能

  1. 3个互相跳转页面
  2. 点击歌单歌曲或播放列表切换歌曲
  3. 上一首,下一首切换歌曲
  4. 顺序,循环,随机播放
  5. 进度条和音量拖拽,点击改变
  6. 歌词滚动,高亮

具体实现过程

1. 轮播图

  1. 轮播图的思路是将多张图片排列成一行,只显示第一张图片,其他图片隐藏,通过改变父盒子的位置,让下一张出现。
  2. 小圆点颜色改变是通过记录当前显示的是第几张图片,把所有小圆点样式清空,给对应的第几个小圆点添加样式。
  3. 点击圆点切换图片,是利用循环判断是第几个小圆点,并跳转到第几个图片。
const container=document.getElementById('center-container')
const imgList=container.getElementsByClassName('imgs')
const dots=document.getElementById('dot')
const dot=dots.getElementsByTagName('span')
const imgWidth=600
let currentPage=0
let timer
const td=1000
const btl=document.getElementById('btn-left')
const btr=document.getElementById('btn-right')

function changePic(){  
    if(currentPage==imgList.length){
        currentPage=0
    }
    if(currentPage==-1){
        currentPage=imgList.length-1
    }
    container.style.left="-"+imgWidth*currentPage+"px"
    for(let i=0;i<dot.length;i++){   
        dot[i].style.backgroundColor='#B0AFBA'
    }
    dot[currentPage].style.backgroundColor='#B20A0A'

}
// 上一张
function forward(){
    changePic()
    currentPage++
}
// 下一张
 function back(){
    changePic()
    currentPage--
}
// 轮播图开始
function start(){
    timer=setInterval(forward,td)
}
// 轮播图停止
function stop(){
    clearInterval(timer)
}
//点击圆点切换图片
for(let i=0;i<dot.length;i++){   
    dot[i].addEventListener('click',()=>{
        currentPage=i
        changePic();
    })
}
btl.addEventListener('click',back)
btr.addEventListener('click',forward)
container.addEventListener('mouseover',stop)
container.addEventListener('mouseout',start)
//设置初始页面呈现的图片
container.style.left=imgWidth*currentPage+"px"
//设置第一个小圆点的颜色为红色
dot[0].style.backgroundColor='#B20A0A'
//开始轮播
start()

2. 页面跳转

思路是利用url中的hash来实现页面的跳转,#和#后面的字符串就是url的hash值,可以通过这个hash值来实现页面的跳转。
在这里插入图片描述

  1. 如何获取hash值
window.location.hash    //#center
  1. 如何修改hash值
window.location.hash='#words'
  1. 如何检测hash值的改变
    利用hashchange事件,监听页面hash的改变
let header=document.getElementById('header')
let center=document.getElementById('center')
let songs=document.getElementById('songsList')
let words=document.getElementById('songsWords')
let inforImg=document.getElementById('infor-img')
let infora=inforImg.getElementsByTagName('a')
songs.classList.add('hidden')
words.classList.add('hidden')
// 页面切换
window.addEventListener('hashchange',()=>{
    let hash=window.location.hash.slice(1)
    change(hash);
})
// 页面显示
function change(hash){
    if(hash=='songsList'){
        songs.classList.remove('hidden')
        center.classList.add('hidden')
        words.classList.add('hidden')
        stop()
        header.innerHTML='歌单'
    }
    else if(hash=='center'){
        center.classList.remove('hidden')
        songs.classList.add('hidden')
        words.classList.add('hidden')
        start()
        header.innerHTML=`<div class="header-img"></div>`
    }
    else if(hash=='words'){   
        words.classList.remove('hidden')
        center.classList.add('hidden')
        songs.classList.add('hidden')  
        header.innerHTML='播放器'
        stop()
    }
}
// 页面二和页面三的跳转
var count=0
inforImg.addEventListener('click',()=>{
    if(window.location.hash=='#words'){
        count++
        change('songsList')
       if(count%2){
        change('words')
       }
    }
})

3. 点击歌单或播放列表切换歌曲

点击歌单切换歌曲的思路是利用for循环,为每一行歌曲信息添加事件监听,当点击某一行时,将歌曲信息添加到集合中,防止重复添加,同时获取所点击的歌曲内容:歌曲名,歌手,在所有的audio中,寻找与所点击歌曲名字一样的audio,播放对应的audio,并把audio添加到song集合中,以免添加歌曲重复,同时同步底部歌曲信息。 点击播放列表切换歌曲也是同理。

//添加到songMap集合的内容
let addContent
//歌单内容的集合
const songMap =new Map()
//歌曲音频的集合
const songs=new Set()

// 当前播放的音频在arr数组中的索引值
let position
export let arr=[]
//当前播放的音频 
export let audioMax=audio1
// 点击歌单向播放列表添加歌曲
for(let songsListUl of songsListUls){
    songsListUl.addEventListener('click',()=>{
        //点击歌单的内容
        addContent=songsListUl.innerHTML.substring(110)
        // 向播放列表中添加歌曲信息
        if(!songMap.has(addContent)){
            songMap.set(addContent)
            dirList.innerHTML+=`<ul>${addContent}</ul>`
        }
        // 获取所点击歌曲内容:歌曲名,歌手
        let clickSong=songsListUl.querySelector('.name').textContent
        let clickAuthor=songsListUl.querySelector('.author').textContent  
        // 对所有的audio标签进行遍历,寻找与所点击歌曲名字一样的audio,播放对应的audio,
        // 并把audio添加到song集合中,以免添加歌曲重复
        au.forEach((a)=>{
            if(addContent.includes(a.getAttribute('data-name'))){         
                audioMax=a
                clearAudio()
                a.currentTime=0
                 // 同步底部歌曲信息
                inforName.textContent=clickSong
                inforAuthor.textContent=clickAuthor
                throttle(playNoError(a))
                btnArr[1].innerHTML='&#xe629;'
                songs.add(a) 
            }  
        })      
    })   
}
// 点击播放列表切换歌曲
for(let dd of dirList.querySelectorAll('ul')){       
    dd.addEventListener('click',()=>{
        let clickSong=dd.querySelector('.name').textContent
        let clickAuthor=dd.querySelector('.author').textContent       
        au.forEach((a)=>{
            if(a.getAttribute('data-name')==clickSong){    
                audioMax=a
                clearAudio()
                a.currentTime=0 
                 // 同步底部歌曲信息
                inforName.textContent=clickSong
                inforAuthor.textContent=clickAuthor
                throttle(playNoError(a))
                btnArr[1].innerHTML='&#xe629;'
                songs.add(a)      
            }   
           
        })
    })
}
arr=Array.from(songs)



//切换歌曲时清除其他播放歌曲
export function clearAudio(){
    for(let au of audioAll){
        au.pause()
    }
}
//方法一:节流
function throttle(fn,delay=2000) {
   let timer=null
   return function(...args){
        if(!timer){
            timer=setTimeout(()=>{
                fn.apply(this,args)
                timer=null
            },delay)
        }   
   }
   
}
function playNoError(a){ 
    setTimeout(() => {
        a.play()
    }, 1000); 
}

4. 上一首、下一首

想进行上一首,下一首的切换首先要知道当前所播放的音频所在的位置,可以利用数组的索引,因此将歌曲音频的集合songs转换为数组arr,并计算当前所播放的音频的索引值,通过索引值的加减实现歌曲的切换。

let btns=document.getElementById('footer-btns')
let btnArr=btns.getElementsByTagName('i')

// 播放按钮
btnArr[0].addEventListener('click',pre)
btnArr[1].addEventListener('click',playAudio)
btnArr[2].addEventListener('click',next)

// 上一首
function pre() {
    position=arr.indexOf(audioMax)>=0?arr.indexOf(audioMax):0
    if (position == 0) {
		position = arr.length - 1;
	} 
    else{
        position=--position%arr.length;
    }
    audioPlay()
}
// 播放暂停和开始
function playAudio(){
    if(audioMax.paused){
        clearAudio()
        throttle(playNoError(audioMax))
        btnArr[1].innerHTML='&#xe629;'   
    }
    else if(!audioMax.paused){
        audioMax.pause()
        btnArr[1].innerHTML='&#xe624;'
    }
    if(songMap.size==0){
        addContent=songsListUls[0].innerHTML.substring(110)
        dirList.innerHTML+=`<ul>${addContent}</ul>`
        songMap.set(addContent)
        songs.add(audio1)
    }
}
// 下一首
export function next() {
    position=arr.indexOf(audioMax)>=0?arr.indexOf(audioMax):0
    position=++position%arr.length;  
    audioPlay()
}

5. 不同播放模式

  1. 顺序播放
    当当前所播放的音频结束时,播放下一首,可实现顺序播放。
  2. 循环播放
    将所有音频的循环属性设为false,只将当前所播放的音频属性设为true,可实现循环模式下,当前播放的歌曲循环播放
  3. 随机播放
    获取播放列表中存放音频的数组arr的长度,利用random函数实现。
// 播放模式,播放列表
let footerList=document.getElementById('footer-list')
let musicMode=footerList.querySelector('.list-icon')
let mode=0
// 通过点击切换播放模式
musicMode.addEventListener('click',()=>{
    mode=++mode%3   
    if(mode==0){
        // 顺序播放
        musicMode.innerHTML='&#xea77;'
    }
    else if(mode==1){
        // 循环播放  
        musicMode.innerHTML='&#xea76;'   
    }
    else if(mode==2){
        // 随机播放
        musicMode.innerHTML='&#xea75;'
    }
})
function changeMode(){
    if(mode==0){
        // 顺序播放
        clearLoop()
        if(audioMax.ended){
            next()
        }
    }
    else if(mode==1){
        // 循环播放  
        clearLoop()
        audioMax.loop=true
    }
    else if(mode==2){
        // 随机播放
        clearLoop()
        if(audioMax.ended){
            randomChange()
        }
    }
}
// 清除所有歌曲的循环播放
function clearLoop(){
    au.forEach((a)=>{
        a.loop=false
    })
}
// 只有一首歌时,所有的模式都相当于循环播放
function onlyOneSong(){
    if(arr.length<=0){

    }
    else if(arr.length==1){
        audioMax.loop=true
    }
    else{
        changeMode()
    }
}
// 随机播放
export function randomChange(){
    position=Math.round(Math.random()*arr.length)
    audioPlay()
}

6. 进度条和音量拖拽,点击改变

  1. 进度条的长度=音乐当前播放的时长/音乐总时长*进度条的总长度
    进度条的长度和音乐当前播放的时长可以相互推算出来
  2. .如何获取改变后进度条的长度
  • 进度条最左端的位置proPosition
  • 为进度条添加事件监听,鼠标点击时获取当前点击的位置e.clientX
  • 改变后的进度条的长度=e.clientX-proPosition
  1. 需要注意拖拽的范围不能超过进度条的宽度范围,需要给进度条设置最大宽度。
  2. 需要注意音量的大小在[0,1]之间,超出范围会报错
// 进度条拖拽,点击
function proChange(e){
    // 获取进度条的位置
    let proPosition=Math.floor(progressBar.getBoundingClientRect().left);
    // 点击进度条后进度条的宽度
    let proWidth=e.clientX-proPosition;
    // 如果进度回退,通过进度条的宽度反向计算目前歌曲播放时间
    if(proWidth<=progressBar.clientWidth){
        proBar.style.width=proWidth+'px'
        proDot.style.left=proWidth-2+'px'
        audioMax.currentTime=audioMax.duration*proWidth/(progressBar.clientWidth)
    }
}
progressBar.addEventListener('click',(e)=> {
    proChange(e);
})
progressBar.addEventListener('mousedown',()=> {
    document.onmousemove = (e)=>{
        if(e.clientX>=progressBar.getBoundingClientRect().left){
            proChange(e);
        }
    }
})
progressBar.addEventListener('mouseup', () => {
    document.onmousemove = null
})

// 音量拖拽,点击
var volWidth
function vulChange(e){
    let volPosition=Math.floor(volumeBar.getBoundingClientRect().left);
    volWidth=e.clientX-volPosition;
    if(volWidth<=volumeBar.clientWidth){
        volBar.style.width=volWidth+'px'
        volDot.style.left=volWidth-2+'px'
        audioMax.volume=volWidth/(volumeBar.clientWidth)>=0?volWidth/(volumeBar.clientWidth):0
    }
}
volumeBar.addEventListener('click',(e)=> {
    vulChange(e);
})
volumeBar.addEventListener('mousedown',()=> {
    document.onmousemove = (e)=>{
        if(e.clientX>=volumeBar.getBoundingClientRect().left){
            vulChange(e);
        }
    }
})
volumeBar.addEventListener('mouseup', () => {
    document.onmousemove = null
})
// 字符格式化
function format(number){
    return number.toString().padStart(2,'0')
}

7. 歌词滚动,高亮

思路是当播放时间大于自定义属性data-time时,清除所有样式,给对应的标签添加高亮样式。
根据高亮歌词的位置,改变滚动条位置,实现歌词滚动。

export const songsP=document.getElementsByClassName('words-flag')
export const songsRig=document.getElementById('songsWords-right')
const songsLef=document.getElementById('songsWords-left')
export const songsPage=songsLef.querySelector('.songsWords-page')
export function wordsScroll(){  
    // 样式清空
    if(!audio1.paused){
        let i=0
        for (let p of songsP){
            let time = p.getAttribute("data-time")
            if(audio1.currentTime>=time) i++
            p.classList.remove('words-light')
        }
        //歌词高亮
        songsP[i-1].classList.add('words-light')
        // 滚动条滚动
        let lightTop=document.querySelector('.words-light').offsetTop
        songsRig.scrollTop=lightTop-170
        //封面旋转
        songsPage.style.animationPlayState='running'
    }
    else{
        songsPage.style.animationPlayState='paused'
    } 
}

遇到的问题以及如何解决

问题1:

在这里插入图片描述
原因:浏览器安全机制导致的,不支持跨域访问,而http, data, chrome, chrome-extension, https这些协议是支持跨域请求的。
解决方法:使用live server打开。

问题2:

在这里插入图片描述
原因:执行了play()方法以后立即执行pause()方法,频繁切换音频播放。
解决方法:

  1. 使用异步
audio.load()
let playPromise = audio.play()
if (playPromise !== undefined) {
    playPromise.then(() => {
        audio.play()
    }).catch(()=> {
       
    })
}
  1. 设置定时器,延时一段时间再切换
this.timer = setTimeout(() => {
    video.play();
},500);

  1. 节流
function throttle(fn,delay=2000) {
   let timer=null
   return function(...args){
        if(!timer){
            timer=setTimeout(()=>{
                fn.apply(this,args)
                timer=null
            },delay)
        }   
   }
   
}
function playNoError(a){ 
    setTimeout(() => {
        a.play()
    }, 1000); 
}
throttle(playNoError(a))

源码

Gitee

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值