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下载后台传过来的乱码流的问题
Dec 05 Vue.js
Vue实现简单购物车功能
Dec 13 Vue.js
vue3使用vue-count-to组件的实现
Dec 25 Vue.js
vue中watch的用法汇总
Dec 28 Vue.js
Vuex实现简单购物车
Jan 10 Vue.js
如何管理Vue中的缓存页面
Feb 06 Vue.js
Vue详细的入门笔记
May 10 Vue.js
vue+elementui 实现新增和修改共用一个弹框的完整代码
Jun 08 Vue.js
vue+element ui实现锚点定位
Jun 29 Vue.js
vue中利用mqtt服务端实现即时通讯的步骤记录
Jul 01 Vue.js
深入讲解Vue中父子组件通信与事件触发
Mar 22 Vue.js
Vue深入理解插槽slot的使用
Aug 05 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
解决phpmyadmin中文乱码问题。。。
2007/01/18 PHP
在项目中寻找代码的坏命名
2012/07/14 PHP
深入解析Session是否必须依赖Cookie
2013/08/02 PHP
linux下安装php的memcached客户端
2014/08/03 PHP
PHP中Socket连接及读写数据超时问题分析
2016/07/19 PHP
针对thinkPHP5框架存储过程bug重写的存储过程扩展类完整实例
2018/06/16 PHP
关于javascript function对象那些迷惑分析
2011/10/24 Javascript
jQuery.each()用法分享
2012/07/31 Javascript
js鼠标点击事件在各个浏览器中的写法及Event对象属性介绍
2013/01/24 Javascript
jquery indexOf使用方法
2013/08/19 Javascript
js 自定义个性下拉选择框示例
2013/08/20 Javascript
在jquery中combobox多选的不兼容问题总结
2013/12/24 Javascript
推荐10 个很棒的 jQuery 特效代码
2015/10/04 Javascript
JavaScript动态设置div的样式的方法
2015/12/26 Javascript
让DIV的滚动条自动滚动到最底部的3种方法(推荐)
2016/09/24 Javascript
Vuex模块化实现待办事项的状态管理
2017/03/15 Javascript
vue双向绑定简要分析
2017/03/23 Javascript
jQuery插件实现非常实用的tab栏切换功能【案例】
2019/02/18 jQuery
ElementUI之Message功能拓展详解
2019/10/18 Javascript
maptalks+three.js+vue webpack实现二维地图上贴三维模型操作
2020/08/10 Javascript
vue-calendar-component 封装多日期选择组件的实例代码
2020/12/04 Vue.js
[01:56]林书豪DOTA2上海特级锦标赛励志短片
2016/03/05 DOTA
Python version 2.7 required, which was not found in the registry
2014/08/26 Python
Python警察与小偷的实现之一客户端与服务端通信实例
2014/10/09 Python
用python结合jieba和wordcloud实现词云效果
2017/09/05 Python
Python3.6安装及引入Requests库的实现方法
2018/01/24 Python
Pandas对DataFrame单列/多列进行运算(map, apply, transform, agg)
2020/06/14 Python
美体小铺印度官网:The Body Shop印度
2019/10/17 全球购物
物理教育专业毕业生推荐信
2013/11/03 职场文书
电脑教师的自我评价
2013/12/18 职场文书
执行总经理岗位职责
2014/02/03 职场文书
八年级语文教学反思
2014/02/11 职场文书
市场推广策划方案
2014/06/02 职场文书
购房委托书
2014/10/15 职场文书
李强为自己工作观后感
2015/06/11 职场文书
mysql中int(3)和int(10)的数值范围是否相同
2021/10/16 MySQL