Vue3实现简易音乐播放器组件


Posted in Vue.js onAugust 14, 2022

前言

用Vue3实现一个简易的音乐播放器组件

其效果图如下所示:

Vue3实现简易音乐播放器组件

实现这个组件需要提前做的准备:

  • 引入ElementUI
  • 引入字节跳动图标库
  • 一张唱见图片
  • 将要播放的音乐上传到文件服务器上,并提供一个能在线访问的链接【这里使用的是阿里云的OSS服务】

准备

ElementUI

ElementUI的引入可以参照其官网的引入方式;

字节跳动图标库

组件的【上一首】【播放】【下一首】【音量】等图标都是来源自这个图标库,这是其安装文档

在main.js中,我是这样引入的:

//引入字节跳动图标库
import {install} from '@icon-park/vue-next/es/all';
import '@icon-park/vue-next/styles/index.css';

......

//这种加载方式进行加载的话,代表使用默认的前缀进行加载:icon
//也就是说假如要使用一个主页图标,使用图标时标签该这么写: 
//<icon-home theme="outline" size="24" fill="#FFFFFF" :strokeWidth="2"/>
//install(app,'prefix') 用这种方式进行加载的话,可以自定义使用图标库时的标签前缀
install(app)

唱见图片

Vue3实现简易音乐播放器组件

音乐源

将要播放的音乐放到文件服务器上,我这里是使用阿里云的OSS服务进行音乐文件的存储,然后在整个页面加载时【也就是在onMounted生命周期函数中获取这些数据源】。在后面的代码中,这一步体现在:

//初始化歌曲源
const initMusicArr = () => {
        requests.get("/Music/QueryAllMusic").then(function (res) {
            musicState.musicArr = res

            musicState.musicCount = res.length
        })
    }

    onMounted(() => {
        initMusicArr()

            ......
    })

完整代码

<template>

  <!--音乐播放器-->
  <div class="music-container" :class="{'music-active-switch': offsetThreshold}">
    <div class="music-disk">
      <!--唱片图片-->
      <img class="music-disk-picture" :class="{'music-disk-playing-style': playState}" src="./images/R-C.png"
           alt="">
    </div>

    <!--进度条-->
    <div class="music-slider">
      <el-slider
          v-model="playTime"
          :format-tooltip="tooltipFormat"
          size="small"
          :max="sliderLength"
          @change="changePlayTime"/>
    </div>

    <!--按钮组-->
    <div class="button-group">
      <!--上一曲 按钮-->
      <button class="play-button" @click="lastButtonClick">
        <icon-go-start theme="outline" size="23" fill="#939393" :strokeWidth="3" strokeLinejoin="miter"
                       strokeLinecap="butt"/>
      </button>
      <!--播放 按钮-->
      <button class="play-button" @click="playButtonClick">
        <icon-play-one v-if="!playState" theme="outline" size="23" fill="#939393" :strokeWidth="3"
                       strokeLinejoin="miter" strokeLinecap="butt"/>
        <icon-pause v-if="playState" theme="outline" size="23" fill="#939393" :strokeWidth="3"
                    strokeLinejoin="miter" strokeLinecap="butt"/>
      </button>
      <!--下一曲 按钮-->
      <button class="play-button" @click="nextButtonClick">
        <icon-go-end theme="outline" size="23" fill="#939393" :strokeWidth="3" strokeLinejoin="miter"
                     strokeLinecap="butt"/>
      </button>
      <!--音量按钮-->
      <div class="voice-container">
        <button class="voice-button" @click="voiceButtonClick">
          <icon-volume-notice v-if="!voiceMute" theme="outline" size="23" fill="#939393" :strokeWidth="3"
                              strokeLinejoin="miter" strokeLinecap="butt"/>
          <icon-volume-mute v-if="voiceMute" theme="outline" size="23" fill="#939393" :strokeWidth="3"
                            strokeLinejoin="miter" strokeLinecap="butt"/>
        </button>
        <div class="voice-slider">
          <el-slider
              v-model="voicePower"
              :max="1"
              :step="0.1"
              size="small"
              @change="changeVoicePower"/>
        </div>
      </div>
    </div>

    <audio
        ref="musicAudio"
        class="audio-component"
        controls
        preload="auto"
        @canplay="changeDuration">
      <source ref="musicSource" type="audio/mpeg"/>
    </audio>
  </div>

</template>

<script>
import {computed, onMounted, onUnmounted, reactive, ref, watch} from "vue";

//这里是自己封装的axios请求,可以将这里替换成自己的请求逻辑
import requests from "@/api/ajax";

export default {
  name: "index",
  setup() {

    //是否正在播放
    const playState = ref(false);

    //现在的播放时间
    const playTime = ref(0.00);

    //歌曲的时间长度
    const playDuration = ref(0.00);

    //进度条长度
    const sliderLength = ref(100);

    //歌曲URL
    const musicUrl = ref("");

    //播放器标签
    const musicAudio = ref(null);

    //实现音乐播放的标签
    const musicSource = ref(null);

    //是否静音
    const voiceMute = ref(false);

    //音量大小
    const voicePower = ref(0.5);

    const musicState = reactive({
      musicArr: [],
      musicCount: 0
    })

    const musicCursor = ref(0);

    //页面偏移量
    const pageOffset = ref(0)

    //是否达到阈值,达到阈值就显示播放器,反之
    const offsetThreshold = ref(false)

    //激活播放器
    const operateMusicPlayer = () => {
      pageOffset.value = window.scrollY
      //当页面滚动偏移达到800,激活用户框
      if (pageOffset.value > 800) {
        offsetThreshold.value = true
      } else {
        //反之
        offsetThreshold.value = false
      }
    }

    //播放按钮点击回调
    const playButtonClick = () => {

      if (playState.value) {
        musicAudio.value.pause()
      } else {
        musicAudio.value.play()
      }

      //修改播放时间【设置这个,当一首歌正常播放结束之后,再次点击播放按钮,进度条会得到重置】
      playTime.value = musicAudio.value.currentTime

      //重新设置播放状态
      playState.value = !playState.value
    }

    //上一曲按钮点击回调
    const lastButtonClick = () => {
      musicCursor.value -= 1

      changeMusic()
    }

    //下一曲按钮点击回调
    const nextButtonClick = () => {
      musicCursor.value += 1

      changeMusic()
    }

    //歌曲进度条文本提示
    const tooltipFormat = (val) => {

      let strTime = playTime.value

      let strMinute = parseInt(strTime / 60 + '')

      let strSecond = parseInt(strTime % 60 + '')

      return strMinute + ":" + strSecond
    }

    //当歌曲能播放时【亦即在canplay钩子函数中】,musicAudio.value.duration才不会是NaN,才能进行歌曲长度的设置
    const changeDuration = () => {
      if (playDuration.value != musicAudio.value.duration) {

        //修改进度条的最大值
        sliderLength.value = musicAudio.value.duration

        //修改歌曲播放时间
        playDuration.value = musicAudio.value.duration
      }
    }

    //el-slider的钩子函数,拖动进度条时快进歌曲,改变当前播放进度
    const changePlayTime = (val) => {
      musicAudio.value.currentTime = val
    }

    //音量按钮点击回调
    const voiceButtonClick = () => {
      voiceMute.value = !voiceMute.value

      if (!voiceMute.value) {
        voicePower.value = 1

        musicAudio.value.volume = 1
      } else {
        voicePower.value = 0

        musicAudio.value.volume = 0
      }
    }

    //el-slider的钩子函数,用于调节音量
    const changeVoicePower = (val) => {
      musicAudio.value.volume = val

      voicePower.value = val

      if (val > 0) {
        voiceMute.value = false
      } else {
        voiceMute.value = true
      }

    }

    //播放状态下,进度条里的数值每秒递增。而Audio因为在播放状态下,currentTime会自己递增,所以不用处理
    const updatePlayTimePerSecond = () => {
      if (playState.value) {
        playTime.value += 1

        if (playTime.value >= playDuration.value) {
          //代表当前歌曲已经播放完毕,进行切歌
          musicCursor.value++

          changeMusic()
        }
      }
    }

    //切歌
    const changeMusic = () => {
      //切歌【这里的music_url是后端返回给前端的json字符串中,用于存储歌曲在线链接的属性名是:music_url,所以要实现自己的请求逻辑,将这里的music_url改为自己的即可】
      musicSource.value.src = musicState.musicArr[musicCursor.value % musicState.musicCount].music_url

      // 当刷新了url之后,需要执行load方法才能播放这个音乐
      musicAudio.value.load()

      playTime.value = musicAudio.value.currentTime

      sliderLength.value = musicAudio.value.duration

      musicAudio.value.play()

      playState.value = true
    }

    //初始化歌曲源【将这里替换成自己的请求逻辑】
    const initMusicArr = () => {
      requests.get("/Music/QueryAllMusic").then(function (res) {
        musicState.musicArr = res

        musicState.musicCount = res.length

      })
    }

    onMounted(() => {
      initMusicArr()

      //播放状态下,使播放进度自增1,以与Audio内置的currentTime相匹配
      setInterval(updatePlayTimePerSecond, 1000)

      //添加滚动事件
      window.addEventListener("scroll", operateMusicPlayer)
    })

    onUnmounted(() => {
      window.removeEventListener("scroll", operateMusicPlayer)
    })


    return {
      musicAudio,
      musicSource,
      playState,
      playTime,
      playDuration,
      sliderLength,
      musicUrl,
      voiceMute,
      voicePower,
      musicState,
      musicCursor,
      pageOffset,
      offsetThreshold,
      playButtonClick,
      lastButtonClick,
      nextButtonClick,
      voiceButtonClick,
      tooltipFormat,
      changeMusic,
      changeDuration,
      changePlayTime,
      changeVoicePower,
      updatePlayTimePerSecond,
      initMusicArr
    }
  },
}
</script>

<style scoped>

.music-container {
  position: fixed;
  justify-content: center;
  width: 280px;
  height: 110px;
  background-color: white;
  border-radius: 15px;
  bottom: 15px;
  left: 10px;
  opacity: 0;
  transition: 0.5s;
}


.music-disk {
  position: absolute;
  width: 90px;
  height: 90px;
  left: 15px;
  top: 10px;
  border-radius: 50%;
}

.music-disk-picture {
  width: 90px;
  height: 90px;
  border-radius: 50%;
  /*设置图片不可点击*/
  pointer-events: none;
}

.music-disk-playing-style {
  animation: music-disk-rotate 5s linear infinite;
}

@keyframes music-disk-rotate {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.button-group {
  position: absolute;
  width: 330px;
  height: 38px;
  left: 90px;
  bottom: 13px;
  margin-left: 10px;
}

.button-group > button {
  margin-left: 10px;
}

.play-button {
  float: left;
  width: 31px;
  height: 31px;
  padding: 4px;
  /*margin: 0px;*/
  border: 0px;
  border-radius: 50%;
  margin: 7px 0px 0px 0px;
}

.voice-button {
  float: left;
  width: 31px;
  height: 31px;
  padding: 0px;
  /*margin: 0px;*/
  border: 0px;
  border-radius: 50%;
  margin: 7px 0px 0px 0px;
  background-color: transparent;
}


.music-slider {
  position: absolute;
  top: 20px;
  left: 120px;
  width: 50%;
}

.voice-container {
  float: left;
  margin-left: 12px;
  width: 31px;
  height: 38px;
  overflow: hidden !important;
  transition: 0.5s;
}

.voice-container:hover {
  width: 160px;
}


.voice-slider {
  position: relative;
  top: 2px;
  right: -30px;
  width: 90px;
  height: 35px;
  background-color: white;
  border-radius: 10px;
  padding: 0px 15px 0px 15px;
  transition: 0.2s;
}

.audio-component {
  width: 300px;
  height: 200px;
  top: 100px;
  display: none;
}

.music-active-switch{
  opacity: 1;
}

</style>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Vue.js 相关文章推荐
vue中音频wavesurfer.js的使用方法
Feb 20 Vue.js
vue打开其他项目页面并传入数据详解
Nov 25 Vue.js
Vue实现图书管理小案例
Dec 03 Vue.js
如何正确解决VuePress本地访问出现资源报错404的问题
Dec 03 Vue.js
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
Jan 05 Vue.js
基于VUE实现简单的学生信息管理系统
Jan 13 Vue.js
如何管理Vue中的缓存页面
Feb 06 Vue.js
Vue3.0中Ref与Reactive的区别示例详析
Jul 07 Vue.js
Vue如何清空对象
Mar 03 Vue.js
vue实现Toast组件轻提示
Apr 10 Vue.js
vue3.0 数字翻牌组件的使用方法详解
Apr 20 Vue.js
el-table-column 内容不自动换行的解决方法
Aug 14 #Vue.js
vue el-table实现递归嵌套的示例代码
Aug 14 #Vue.js
vue实现input输入模糊查询的三种方式
Aug 14 #Vue.js
vue本地构建热更新卡顿的问题“75 advanced module optimization”完美解决方案
Aug 05 #Vue.js
Vue深入理解插槽slot的使用
Aug 05 #Vue.js
vue3 自定义图片放大器效果的示例代码
Jul 23 #Vue.js
vue递归实现树形组件
Jul 15 #Vue.js
You might like
?繁体转换的class
2006/10/09 PHP
判断PHP数组是否为空的代码
2011/09/08 PHP
获取php页面执行时间,数据库读写次数,函数调用次数等(THINKphp)
2013/06/03 PHP
php保存信息到当前Session的方法
2015/03/16 PHP
PHP判断一个字符串是否是回文字符串的方法
2015/03/23 PHP
使javascript也能包含文件
2006/10/26 Javascript
Jquery 获得服务器控件值的方法小结
2010/05/11 Javascript
javascript 函数参数限制说明
2010/11/19 Javascript
js实现动态改变字体大小代码
2014/01/02 Javascript
JavaScript的9种继承实现方式归纳
2015/05/18 Javascript
jQuery 遍历map()方法详解
2016/11/04 Javascript
微信小程序动态显示项目倒计时效果
2017/06/13 Javascript
探究react-native 源码的图片缓存问题
2017/08/24 Javascript
通过vue-cli来学习修改Webpack多环境配置和发布问题
2017/12/22 Javascript
关于node-bindings无法在Electron中使用的解决办法
2018/12/18 Javascript
微信小程序引入模块中wxml、wxss、js的方法示例
2019/08/09 Javascript
微信小程序转化为uni-app项目的方法示例
2020/05/22 Javascript
浅谈vue 组件中的setInterval方法和window的不同
2020/07/30 Javascript
js节流防抖应用场景,以及在vue中节流防抖的具体实现操作
2020/09/21 Javascript
在vue中实现某一些路由页面隐藏导航栏的功能操作
2020/09/21 Javascript
Python中常用操作字符串的函数与方法总结
2016/02/04 Python
详解Python下载图片并保存本地的两种方式
2019/05/15 Python
Python Pandas实现数据分组求平均值并填充nan的示例
2019/07/04 Python
布隆过滤器的概述及Python实现方法
2019/12/08 Python
Web Service面试题:如何搭建Axis2的开发环境
2012/06/20 面试题
电力公司个人求职信范文
2014/02/04 职场文书
《植物妈妈有办法》教学反思
2014/02/25 职场文书
小学班干部竞选演讲稿
2014/04/24 职场文书
英文道歉信
2015/01/20 职场文书
医德医风个人总结
2015/02/28 职场文书
公司员工辞职信范文
2015/05/12 职场文书
运动会致辞稿
2015/07/29 职场文书
2016年习主席讲话学习心得体会
2016/01/20 职场文书
餐厅如何利用“营销策略”扭转亏本局面
2019/10/15 职场文书
7个你应该知道的JS原生错误类型
2021/04/29 Javascript
html form表单基础入门案例讲解
2021/07/21 HTML / CSS