基于vue3音乐播放器进度条的实现

3,855 阅读3分钟

进度条功能

更新

一、简介

一些零基础小白学习vue3的开发记录,目标实现web端音乐播放器。github开发代码,欢迎一起交流学习。

二、相关准备

需求分析:

  1. 歌曲播放时,显示进度条,随着时间一起前进
  2. 拖动进度条,能够定位到歌曲对位位置播放
  • <video>标签中可以通过监听H5触发的媒体相关事件,精准控制音频内容
  • 说到进度条,在html里面首先想到的是input组件,其中的range类型,通过vue的props传递属性可以很方便的实现想要的功能。
  • 拖动进度条首先想到的vue的双向绑定,但这里有个问题就是:双向绑定数据后拖动改变时间,但是歌曲还在播放,穿过来的数据导致进度条向前。两者有明显冲突。

三、实现过程

滚动条的显示

获取相关数据

需要用到的<video>中属性currentTimeduration及监听方法timeupdatedurationchange,将数据发送给对应需要用到的组件。

相关代码如下:

<template>
  <audio
    ref="audio"
    class="audio-player"
    :src="src"
    ···
    @timeupdate="timeupdateListener"
    @durationchange="durationchangeListener"
    ···
  / >
</ template>
export default {
  name: "Audio",
  emits: {
    "update-currentTime": null,
    "update:duration": payload => {
      if (payload !== 0) {
        return true;
      }
      console.warn("获取歌曲长度失败,值为", payload);
      return false;
    }
  },
  ···
  methods: {
    timeupdateListener(e) {
      //元素的currentTime属性表示的时间已经改变。
      this.$emit("update-currentTime", this.$refs.audio.currentTime);
    },
    durationchangeListener() {
      //元信息已载入或已改变,表明媒体的长度发生了改变
      this.$emit("update:duration", this.$refs.audio.duration);
    },
    ···
  }
};

利用数据显示进度条

这里查阅相关文档修改进度条样式,相关style这里不显示了。然后对通过props接收到的值进行显示也没有问题。

相关代码如下:

<template>
  <div class="player-content">
    <div class="progress-bar">
      <input
        type="range"
        ref="progress"
        :class="{ active: isTouch }"
        min="0"
        v-model="currentTime"   //待会将去除
        :max="duration"
        step="0.05"
        ···
      />
      <div class="progerss-time">
        <span class="left">{{ parseTimeString(currentTime) }}</span>
        <span class="right">{{ parseTimeString(duration) }}</span>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "PlayerContent",
  props: {
    currentTime: {
      type: Number,
      default: 0
    },
    duration: {
      type: Number,
      default: 1
    }
  },
  data() {
    return {
      isTouch: false
    };
  },
  methods: {
    parseTimeString(num) {
      let min = Math.floor(num / 60);
      let sec = Math.floor(num - min * 60);
      return `${min}:${sec}`;
    },
    ···
  }
};
</script>

拖动进度条定位功能的实现

这里首先考虑到分析过的问题2,决定再显示一个一样的input标签,绑定临时定义的变量进行单向控制。也就是从video传过来的值显示一个进度条,临时进度条拖动后双向绑定的值传回给video设置currentTime

然后测试了之后经过分析还是发现不能通过双向绑定实现,这样数据流形成了闭环,拖动产生的Time和传过来currentTime会相互干扰

分析过后,觉得只要没有控制的时候接收传过来的currentTime,但是控制的时候取消接收,这时候将拖动产生的Time传给video进行设置就行了。

vue文档中查了一下,$watch这个实例方法监听的时候会返回一个unwatch取消监听函数,那么就很容易实现想要的功能了。

相关代码如下,其中上面绑定的v-model="currentTime"需要去掉:

export default {
  name: "PlayerContent",
  emits: {
    "set-currentTime": null
  },
  ···
  data() {
    return {
      isTouch: false
    };
  },
  created() {
    this.unwatch = this.progressWatcher();
  },
  methods: {
      ···
    progressWatcher() {
      return this.$watch("currentTime", (newVal, oldVal) => {
        this.$refs.progress.value = newVal;
      });
    },
    touchStart(e) {
      // 暂停监听value
      this.unwatch();
      this.isTouch = true;
    },
    touchEnd(e) {
      // 跳转至对应时间
      this.$emit("set-currentTime", Number.parseFloat(e.target.value));
      // 开始监听
      this.unwatch = this.progressWatcher();
      // 改变样式
      this.isTouch = false;
    }
  }
};

四、实现效果

这里不贴图了,完整的详细代码放在github代码,可以拉下来,测试一下效果

五、参考资料整理