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 +WebSocket + WaveSurferJS 实现H5聊天对话交互的实例
Nov 18 Vue.js
解决vue页面刷新,数据丢失的问题
Nov 24 Vue.js
详解vue-cli项目在IE浏览器打开报错解决方法
Dec 10 Vue.js
Vue 实现一个简单的鼠标拖拽滚动效果插件
Dec 10 Vue.js
vue+element UI实现树形表格
Dec 29 Vue.js
Vue实现一种简单的无限循环滚动动画的示例
Jan 10 Vue.js
手动实现vue2.0的双向数据绑定原理详解
Feb 06 Vue.js
详解vite+ts快速搭建vue3项目以及介绍相关特性
Feb 25 Vue.js
vue+django实现下载文件的示例
Mar 24 Vue.js
深入讲解Vue中父子组件通信与事件触发
Mar 22 Vue.js
vue如何实现关闭对话框后刷新列表
Apr 08 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
一个查看session内容的函数
2006/10/09 PHP
php Sql Server连接失败问题及解决办法
2009/08/07 PHP
PHP使用CURL实现对带有验证码的网站进行模拟登录的方法
2014/07/23 PHP
CodeIgniter配置之routes.php用法实例分析
2016/01/19 PHP
php生成Android客户端扫描可登录的二维码
2016/05/13 PHP
cakephp常见知识点汇总
2017/02/24 PHP
php微信开发之音乐回复功能
2018/06/14 PHP
Yii2.0实现的批量更新及批量插入功能示例
2019/01/29 PHP
TP5多入口设置实例讲解
2020/12/15 PHP
JS阻止事件冒泡行为和闭包的方法
2016/06/16 Javascript
nodejs读写json文件的简单方法(必看)
2017/03/09 NodeJs
vue使用Axios做ajax请求详解
2017/06/07 Javascript
基于vue cli重构多页面脚手架过程详解
2018/01/23 Javascript
js获取html页面代码中图片地址的实现代码
2018/03/05 Javascript
浅谈VUE防抖与节流的最佳解决方案(函数式组件)
2019/05/22 Javascript
nodejs 递归拷贝、读取目录下所有文件和目录
2019/07/18 NodeJs
关于vue组件事件属性穿透详解
2019/10/28 Javascript
vue+flask实现视频合成功能(拖拽上传)
2021/03/04 Vue.js
Python中使用装饰器时需要注意的一些问题
2015/05/11 Python
python获取指定目录下所有文件名列表的方法
2015/05/20 Python
Python入门学习之字符串与比较运算符
2015/10/12 Python
Python多继承顺序实例分析
2018/05/26 Python
编写多线程Python服务器 最适合基础
2018/09/14 Python
Python使用字典的嵌套功能详解
2019/02/27 Python
Python动态赋值的陷阱知识点总结
2019/03/17 Python
keras 多gpu并行运行案例
2020/06/10 Python
HTML5上传文件显示进度的实现代码
2012/08/30 HTML / CSS
amaze ui 的使用详细教程
2020/08/19 HTML / CSS
PUMA官方商城:世界领先的运动品牌之一
2016/11/16 全球购物
eHarmony英国:全球领先的认真恋爱约会平台之一
2020/11/16 全球购物
大二学生职业生涯规划书
2014/02/05 职场文书
超市仓管员岗位职责范本
2014/09/18 职场文书
英语投诉信范文
2015/07/03 职场文书
2015年小学语文教师工作总结
2015/10/23 职场文书
致创业您:正能量激励人心句子(48条)
2019/08/15 职场文书
MySQL的索引你了解吗
2022/03/13 MySQL