vue3+element-plus el-slider音乐播放器进度条bug

661 阅读5分钟

问题:鼠标点击或者拖动slider滑块到指定位置修改音乐播放进度偶尔会失效(失效概率更大)

原因:我是用slider change事件去修改音乐播放进度的,element plus官方文档说明change事件是值改变时触发(使用鼠标拖拽时,只在松开鼠标后触发)即松开鼠标才会触发change事件;同时,我slider的进度是通过audio的更新timeupdate去赋值到slider的值达到实时更新的。当我在按下鼠标但还没松开时,还未触发change事件修改进度,audio的timeupdate方法就再次触发了,当我松开鼠标时,change事件里面赋的值就不是鼠标点击/拖拽的指定进度,而是audio的timeupdate方法的进度值,就导致点击/拖拽slider修改音乐进度失效

之前的关键代码:

//html 部分
<audio
        ref="audioRef"
        @pause="onPause"
        @play="onPlay"
        @timeupdate="onTimeupdate"
        @loadedmetadata="onLoadedmetadata"
        @ended="onTimeOver"
        :src="MusicUrl"
      ></audio>
      
<el-slider
            v-model="currentTime"
            :format-tooltip="audioFormatterTooltip"
            :max="totalTime"
            @change="changeAudioCurrentTime"
          />
          
//js 部分
const audioRef = ref()
const totalTime = ref()
const currentTime = ref()
const audioType = ref(false)

const audioFormatterTooltip = val => {
  return formatTime(val)
}

const onPause = () => {
  audioType.value = false
}

const onPlay = () => {
  audioType.value = true
}

const onTimeupdate = e => {
  currentTime.value = parseInt(e.target.currentTime)
}

const onLoadedmetadata = e => {
  totalTime.value = parseInt(e.target.duration)
}

const onTimeOver = () => {
  audioType.value = false
  currentTime.value = 0
}

const playAudio = () => {
  audioRef.value.play()
}

const pauseAudio = () => {
  audioRef.value.pause()
}

const handleToggleAudio = () => {
  return audioType.value ? pauseAudio() : playAudio()
}

const changeAudioCurrentTime = val => {
  console.log(val)

  audioRef.value.currentTime = val
}

解决办法:在鼠标按下时将audio的timeupdate方法禁止掉,在松开的时候再触发,所以添加一个参数来判断是否禁止该事件的时间更新

修改的代码:

//html
<el-slider
            v-model="currentTime"
            :format-tooltip="audioFormatterTooltip"
            :max="totalTime"
            @mousedown.native="mouseDown"
            @mouseup.native="mouseUp"
            @change="changeAudioCurrentTime"
          />

//js
// 判断是否在更新slider
const isTimeupdate = ref(false)
const onPause = () => {
  audioType.value = false
  isTimeupdate.value = false
}

const onPlay = () => {
  audioType.value = true
  isTimeupdate.value = true
}
const onTimeupdate = e => {
  if (isTimeupdate.value === false) {//当在slider按下鼠标时,slider的数据不跟着audio播放进度进行更新
    return
  }
  currentTime.value = parseInt(e.target.currentTime)
}
const mouseDown = () => {//按下停止更新
  isTimeupdate.value = false
}

const mouseUp = () => {//松开继续更新
  isTimeupdate.value = true
}

之前这个问题困恼了我很久,在网上找了很多文档都没有真正解决,是看了这个文档 vue+element制作音乐播放器播放进度条bug(鼠标拖拽slider滑块滑动到指定位置无效) 才解决的,特此记录一下,防止之后碰到相似问题不知如何解决

完整代码:

<script setup>
import { ref, onMounted } from 'vue'
import MusicUrl from '@/assets/mp3/Russ-Psycho,Pt.2.mp3' //引入音乐
import { formatTime } from '@util/format' //格式化时间

const audioRef = ref()
const totalTime = ref()
const currentTime = ref()
const audioType = ref(false)
// 判断是否在更新
const isTimeupdate = ref(true)

const audioFormatterTooltip = val => {//slider tooltip格式化
  return formatTime(val)
}

const onPause = () => {//audio 停止时
  audioType.value = false
}

const onPlay = () => {//audio 播放时
  audioType.value = true
}

const onTimeupdate = e => {//audio timeupdate 时间更新
  if (isTimeupdate.value === false) {//依据isTimeupdate的值判断是否将audio当前进度更新到slider
    return
  }
  currentTime.value = parseInt(e.target.currentTime)
}

const onLoadedmetadata = e => {//audio 加载完成
  totalTime.value = parseInt(e.target.duration)
}

const onTimeOver = () => {//audio 播放完成
  audioType.value = false
  currentTime.value = 0
}

const playAudio = () => {//播放audio
  audioRef.value.play()
}

const pauseAudio = () => {//暂停audio
  audioRef.value.pause()
}

const handleToggleAudio = () => {//依据播放状态判断是 播放/暂停audio
  return audioType.value ? pauseAudio() : playAudio()
}

const changeAudioCurrentTime = val => {//slider change事件 将slider进度赋值给audio
  console.log(val)

  audioRef.value.currentTime = val
}

const mouseDown = () => {//slider 鼠标按下 停止slider进度更新
  isTimeupdate.value = false
}

const mouseUp = () => {//slider 鼠标按下 恢复slider进度更新
  isTimeupdate.value = true
}

onMounted(() => {
})
</script>

<template>
  <div class="audio-item-wrapper">
    <div class="audio-top">
      <div class="name">歌曲名称歌曲名称歌曲名称歌曲名称歌曲名称</div>
      <div class="delete"></div>
    </div>
    <div class="audio-control">
      <div
        class="button"
        :class="audioType === true ? 'pause' : 'play'"
        @click="handleToggleAudio"
      ></div>
      <audio
        ref="audioRef"
        @pause="onPause"
        @play="onPlay"
        @timeupdate="onTimeupdate"
        @loadedmetadata="onLoadedmetadata"
        @ended="onTimeOver"
        :src="MusicUrl"
      ></audio>
      <div class="control-wrapper">
        <div class="control">
          <el-slider
            v-model="currentTime"
            :format-tooltip="audioFormatterTooltip"
            :max="totalTime"
            @mousedown.native="mouseDown"
            @mouseup.native="mouseUp"
            @change="changeAudioCurrentTime"
          />
        </div>
        <div class="time">{{ formatTime(totalTime) }}</div>
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
.audio-item-wrapper {
  width: 510px;
  height: 110px;
  background: #ffffff;
  border: 1px solid #ebeaef;
  border-radius: 10px;
  padding: 20px;
  //cursor: pointer;

  &.active,
  &:hover {
    border: 1px solid #005aff;

    .audio-top {
      .name {
        color: #005aff;
      }

      .delete {
        display: block;
      }
    }
  }

  .audio-top {
    display: flex;
    justify-content: space-between;

    width: 100%;
    height: 20px;

    .name {
      font-size: 20px;
      color: #374051;
    }

    .delete {
      display: none;
      width: 20px;
      height: 20px;
      background-image: url('@img/space/delete.png');
      cursor: pointer;
    }
  }

  .audio-control {
    display: flex;
    align-items: center;
    height: 30px;
    margin-top: 20px;

    .button {
      width: 30px;
      height: 30px;
      cursor: pointer;

      &.play {
        background-image: url('@img/space/play.png');
      }

      &.pause {
        background-image: url('@img/space/pause.png');
      }
    }
  }
}

.control-wrapper {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: calc(100% - 30px - 12px);
  margin-left: 12px;

  .control {
    width: 378px;
    //height: 30px;
    height: 100%;
    //background: red;
  }

  .time {
    //width: 39px;
    font-size: 16px;
    color: #727d91;
  }
}
</style>

<style lang="scss">
.el-slider {
  height: 30px;
}

.el-slider__runway {
  //滑块进度条 整个
  width: 378px;
  height: 6px;
  background: #f4f6fa;
  border: 1px solid #ebeaef;
  border-radius: 3px;
}

.el-slider__bar {
  //进度
  height: 6px;
  background: #005aff;
  border-radius: 3px;
}

.el-slider__button {
  // 拖动的滑块的样式
  width: 12px;
  height: 12px;
  background: #005aff;
  border-radius: 50%;
  border: none;
}
</style>
//format.js
export function formatTime(s) {
  let h
  h = Math.floor(s / 60)
  s = s % 60
  h += ''
  s += ''
  h = h.length === 1 ? '0' + h : h
  s = s.length === 1 ? '0' + s : s
  if (isNaN(h)) {
    return '00:00'
  }
  return h + ':' + s
}
几个事件 解释 在代码处的用法

ended 在媒体播放到尽头发生此事件 =>audio @ended
pause 当媒体被用户暂停或以编程方式暂停时,发生此事件 =>audio @pause
play 当媒体已启动或以不再暂停时,发生此事件 =>audio @play
loadedmetadata 加载元数据(比如尺寸和持续时间)时,发生此事件 =>audio @loadedmetadata
timeupdate 当播放位置更改时发生此事件 =>audio @updatetime

change 当form元素的内容、选择的内容或选中的状态发生改变时,发生此事件 =>el-slider @change
mousedown 当用户在元素上按下鼠标按钮时,发生此事件 =>el-slider @mousedown.native
mouseup 当用户在元素上释放鼠标按钮时,发生此事件 =>el-slider @mouseup.native

vue+element制作音乐播放器播放进度条bug(鼠标拖拽slider滑块滑动到指定位置无效)