vue3封装-自定义audio音频播放【拖拽进度条,点击进度条,快进,后退,音量加减】思路

1.根据业务需求反应,及网上阅读资料,代码缺斤少两,本文复制粘贴即可使用,但注意引用了第三库的字体图标,可注释掉替换自己想要的图标即可,其他全手戳代码,可自行优化封装

效果如下:

效果图


1.Audio文件

<template>
  <div class="container">
    <audio @canplay="getDuration" controls @timeupdate="updateTime" v-show="false" ref="audio"
           :src="audioSrc"/>
    <div class="card">
      <div class="progress" ref="progress" @click="clickProgress" @mouseup="handleMouseup">
        <div class="currentProgress" ref="currentProgress">
          <span class="circle" ref="circle" @mousedown="handleMousedown"></span>
        </div>
      </div>
      <div class="time">
        <span class="startTime">{{ currentDuration }}</span>
        <span class="endTime">{{ duration }}</span>
      </div>
      <div class="option">
        <span class="pre" @click="handleBack">
          <img src="@/assets/img/快退.png" alt=""> <span
            style="color: #C7C7C9;font-size: 13px;margin-left: 10px">-{{ backSecond }}s</span>
        </span>
        <span class="play" @click="handlePauseOrPlay">
          <el-icon size="35px" color="#3853FE">
            <video-pause v-show="!paused"/>
            <video-play v-show="paused"/>
          </el-icon>
        </span>
        <span class="next" @click="handleForward">
          <span style="color: #C7C7C9;font-size: 13px;margin-right: 10px">+{{ forwardSecond }}s </span>
          <img src="@/assets/img/快进.png" alt="">
        </span>
        <div class="volumePanel">
          <lk-select v-model:visible="volumeVisible">
            <template v-slot:reference>
              <img class="volume" v-if="volume > 0" src="@/assets/img/24gf-volumeHigh.png" alt="">
              <img class="volume" v-else src="@/assets/img/24gf-volumeCross.png" alt="">
            </template>
            <div class="progressY" ref="progressY">
              <div class="currentProgressY" ref="currentProgressY">
                <span class="circleY" ref="circleY" @mousedown="handleVolumeMousedown"></span>
              </div>
            </div>
          </lk-select>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { VideoPause, VideoPlay } from '@element-plus/icons-vue'
import lkSelect from '../dropdown/lk-select'

export default {
  name: "index",
  components: { VideoPause, VideoPlay, lkSelect },
  props: {
    audioSrc: {
      type: String,
      default: 'https://img-qn.51miz.com/Audio/2017/02/06/08/20170206081849_A100187_34f7e6b8-thumb.mp3'
    },
    backSecond: {
      type: Number,
      default: 3
    },
    forwardSecond: {
      type: Number,
      default: 10
    }
  },
  data() {
    return {
      duration: '00:00',
      currentDuration: '00:00',
      audio: '',
      volume: 1,
      paused: true,
      isMoveIn: false, //是否在移动中
      showVolumePanel: false,
      volumeVisible: false
    }
  },
  methods: {
    //后退
    handleBack() {
      if (this.audio.currentTime > this.backSecond) {
        this.audio.currentTime = this.audio.currentTime - this.backSecond
      }
    },
    //前进
    handleForward() {
      if (this.audio.duration - this.audio.currentTime > this.forwardSecond) {
        this.audio.currentTime = this.audio.currentTime + 10
      }
    },
    //暂停或播放
    handlePauseOrPlay() {
      this.audio.paused ? this.audio.play() : this.audio.pause()
      this.paused = !this.paused
    },
    //视频在可以播放时触发
    getDuration() {
      this.duration = this.timeFormat(this.$refs.audio.duration)
      this.audio = this.$refs.audio
      this.audio.volume = this.volume //默认百分百音量
      //监听音量的变化
      // this.audio.addEventListener('volumechange',(value)=>{
      //   console.log(this.audio.volume)
      // })
    },
    //将单位为秒的的时间转换成mm:ss的形式
    timeFormat( number ) {
      let minute = parseInt(number / 60);
      let second = parseInt(number % 60);
      minute = minute >= 10 ? minute : "0" + minute;
      second = second >= 10 ? second : "0" + second;
      return minute + ":" + second;
    },
    //进度条发生变化时触发
    updateTime() {
      if (!this.$refs.progress) return
      this.currentDuration = this.timeFormat(this.audio.currentTime)
      //如果不是正在移动 和 没有暂停播放就执行
      if (!this.isMoveIn || !this.audio.paused) {
        // 设置当前时间
        let MoveX = this.$refs.progress.clientWidth * ( this.audio.currentTime / this.audio.duration )
        //播放时更新距离
        this.$refs.currentProgress.style.width = MoveX + 'px'
        this.$refs.circle.style.left = MoveX - ( this.$refs.circle.clientWidth / 2 ) + 'px'
      }
    },
    //点击进度条更新进度
    clickProgress( e ) {
      //如果不是正在移动 和 没有暂停播放就执行
      if (!this.isMoveIn || !this.audio.paused) {
        this.updateProgress(e.offsetX)
      }
    },
    //更新进度
    updateProgress( MoveX ) {
      //当前移动的位置 = 当前移动的位置 / 当前进度条的可视长度    //this.$refs.progress.clientWidth 注意一定要拿总长度 否则会拿进度条已经走过的长度
      let clickProgress = ( MoveX / this.$refs.progress.clientWidth )
      //设置播放的时间 = 总时长 * 当前点击的长度
      this.audio.currentTime = this.audio.duration * clickProgress
      //设置移动的位置
      this.$refs.currentProgress.style.width = MoveX + 'px'
      this.$refs.circle.style.left = MoveX - ( this.$refs.circle.clientWidth / 2 ) + 'px'
    },
    //更新音量进度
    updateVolumeProgress(MoveY){
      //得到音量  【0 - 1】 区间
      const volume = ((this.$refs.currentProgressY.clientHeight / 60) * 100 / 100).toFixed(1)
      this.audio.volume = volume
      this.volume = volume
      //移动音量条
      this.$refs.currentProgressY.style.height = MoveY + 'px'
      //移动小圆圈
      this.$refs.circleY.style.Top = MoveY - ( this.$refs.circleY.clientHeight / 2 ) + 'px'
    },
    //鼠标弹起
    handleMouseup() {
      setTimeout(() => {
        this.audio.play()
        this.paused = false
        this.isMoveIn = false
      }, 200)
    },
    //调整进度
    handleMousedown() {
      this.audio.pause()
      this.paused = true
      this.isMoveIn = true
      let progress = this.$refs.progress
      //进度条 左 边距离页面左边的距离 移动最小值
      let moveMin = progress.offsetParent.offsetLeft + progress.offsetLeft
      //进度条 右 边距离页面左边的距离 移动最大值
      let moveMax = progress.offsetParent.offsetLeft + progress.offsetLeft + progress.clientWidth
      //小圆圈的宽度
      let circleWidth = ( this.$refs.circle.clientWidth / 2 )
      let moveX = ( e ) => {
        if (e.pageX >= moveMax) {
          return
        } else if (e.pageX <= moveMin) {
          return
        }
        this.$refs.circle.style.left = e.pageX - moveMin - circleWidth + 'px'
        this.updateProgress(e.pageX - moveMin)
      }
      //获取当前鼠标的位置 X
      document.addEventListener('mousemove', moveX)
      //鼠标弹起来
      document.addEventListener('mouseup', () => {
        document.removeEventListener('mousemove', moveX)
      })
    },
    //调整音量
    handleVolumeMousedown(){
      let progressY = this.$refs.progressY
      //音量的最小值
      let moveMin = progressY.offsetParent.offsetTop
      //音量的最大值
      let moveMax = progressY.offsetParent.offsetTop + progressY.clientHeight
      //小圆圈的高度
      let circleHeight = ( this.$refs.circleY.clientHeight / 2 )
      let moveY = ( e ) => {
        //禁止移动时复制内容
        e.preventDefault()
        if (e.pageY >= moveMax) {
          return
        } else if (e.pageY <= moveMin) {
          return
        }
        this.$refs.circleY.style.top = e.pageY - moveMin - circleHeight + 'px'
        this.updateVolumeProgress(e.pageY - moveMin)
      }
      //获取当前鼠标的位置 X
      document.addEventListener('mousemove', moveY)
      //鼠标弹起来
      document.addEventListener('mouseup', () => {
        document.removeEventListener('mousemove', moveY)
      })
    }
  }
}
</script>

<style scoped lang="less">
.card {
  width: 300px;
  padding: 30px;
  box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.03);
  border-radius: 10px;

  .volumePanel {
    .volume {
      width: 20px;
      padding-top: 5px;
    }

    .volumeOption {
      cursor: pointer;
    }
  }

  .progress, .progressY {
    height: 7px;
    border-radius: 3px;
    margin-bottom: 5px;
    width: 100%;
    background-color: #DADFEA;
    cursor: pointer;

    .currentProgress, .currentProgressY {
      position: relative;
      height: 100%;
      width: 0;
      background-color: #3853FE;
      border-radius: 3px;

      .circle, .circleY {
        position: absolute;
        right: -6px;
        top: -2px;
        display: inline-block;
        width: 10px;
        height: 10px;
        border-radius: 50%;
        border: 1px solid #3853FE;
        background-color: #fff;

        &:hover {
          width: 12px;
          height: 12px;
          top: -3px;
          border-radius: 50%;
        }
      }
    }
  }

  .progressY {
    width: 7px;
    height: 60px;
    margin: 10px;
    .currentProgressY{
      height: 100%;
      width: 100%;
      .circleY {
        bottom:-2px;
        right: -3px;
        top: unset;
        &:hover {
          width: 12px;
          height: 12px;
          bottom: -2px;
          top: unset;
          border-radius: 50%;
        }
      }
    }
  }

  .time {
    display: flex;
    justify-content: space-between;
    color: #777A85;
    font-size: 12px;
  }

  .option {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 30px 0 30px;

    .play, .pre, .next {
      display: flex;
      padding: 0 2px;
      align-items: center;
      cursor: pointer;
    }
  }
}
</style>

2.lk-select文件

<template>
  <div class="lk-select">
    <div ref="reference" @click="showPopper" >
      <slot name="reference"/>
    </div>
    <div ref="Popper" class="Popper" v-show="visible">
      <slot/>
    </div>
  </div>
</template>

<script setup>
import { watch, ref, nextTick ,onUnmounted} from 'vue'

const emits = defineEmits(['update:visible'])
const props = defineProps({
  visible: [Boolean, String]
})
const reference = ref()
const Popper = ref()

watch(() => props.visible, ( newValue ) => {
  if (newValue) {
    nextTick(() => {
      const { left, top, height} = reference.value.getBoundingClientRect()
      Popper.value.style.left = left +  'px'
      Popper.value.style.top = top + height + 'px'
    })
    window.addEventListener('mousedown',docClick)
  }else {
    window.removeEventListener('mousedown',docClick)
  }
})

function docClick(e){
  //如果点击的是音量 或者 是音量面板 则 不隐藏音量面板
  if (Popper.value.contains(e.target) || reference.value.contains(e.target)){
    return
  }
  emits('update:visible', false)
}

function showPopper() {
  emits('update:visible', !props.visible)
}
onUnmounted(()=>{
  emits('update:visible', false)
})
defineExpose({
  showPopper
})
</script>
<style scoped>
.lk-select {
  cursor: pointer;
  position: relative;
}

.Popper {
  position: fixed;
  top: 0;
  left: 0;
  background: #fff;
  box-shadow: 0 2px 12px 0px rgba(0, 0, 0, 0.1);
}

</style>

拒绝白票,从我做起,对你有思路就动动小指头点个start,收藏关注哦!

  • 31
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值