使用Vue3+ts封装一个音频audio播放器

封装后音频播放器样式

大概思路是引入audio音频播放器标签,如果不设置control属性,音频标签就会隐藏,这样我们可以自己写音频播放器的样式,然后调用audio标签的方法,达到封装音频播放器的效果。

需要注意的是audio标签的canplay回调方法是音频加载完成之后调用,这时我们可以在这个回调方法里面获取音频的总长度。

代码如下

<template>
  <div class="audio-player">
    <img
      src="@/assets/images/audio/play.png"
      alt=""
      class="play-icon"
      @click="onPlay"
      v-if="!playStatus"
    />
    <img
      src="@/assets/images/audio/pause.png"
      alt=""
      class="play-icon"
      @click="onPause"
      v-else
    />
    <span class="play-time">
      {{ transTime(audioCurrent) }}/{{ transTime(audioDuration) }}
    </span>
    <div class="play-progress">
      <div
        class="play-current-progress"
        :style="{ width: `${playProgress}%` }"
      ></div>
    </div>
    <img
      src="@/assets/images/audio/voice-open.png"
      alt=""
      class="play-voice"
      v-if="audioVolume === 1"
      @click="onSetVolume(0)"
    />
    <img
      src="@/assets/images/audio/voice-close.png"
      alt=""
      class="play-voice"
      v-else
      @click="onSetVolume(1)"
    />
    <el-popover v-model:visible="speedVisible" placement="top" :width="50">
      <div
        v-for="item in speedList"
        :key="item.value"
        @click="onChangeSpeed(item.value)"
        style="margin-bottom: 17px; cursor: pointer; text-align: center"
      >
        <span>{{ item.label }}</span>
      </div>
      <template #reference>
        <span 
          class="play-speed"
          @click="onHandleSpeed"
        >{{ activeSpeed }}x</span>
      </template>
    </el-popover>
  </div>

  <audio ref="audioRef" :src="url" @canplay="onCanplay"></audio>
</template>

<script setup lang="ts">
import { ref, onBeforeMount } from "vue";

withDefaults(defineProps<{
  url: string;
}>(),
  {
    url: "https://xxx.xxx.xx.xx/audio.mp3"
  }
)

const speedList = [
  {
    label: "2x",
    value: 2,
  },
  {
    label: "1.5x",
    value: 1.5,
  },
  {
    label: "1x",
    value: 1,
  },
  {
    label: "0.75x",
    value: 0.75,
  },
];

onBeforeMount(() => {
  clearInterval(timeInterval.value);
});

const speedVisible = ref<boolean>(false); // 设置音频播放速度弹窗
const audioRef = ref(); // 音频标签对象
const activeSpeed = ref(1); // 音频播放速度
const audioDuration = ref(0); // 音频总时长
const audioCurrent = ref(0); // 音频当前播放时间
const audioVolume = ref(1); // 音频声音,范围 0-1
const playStatus = ref<boolean>(false); // 音频播放状态:true 播放,false 暂停
const playProgress = ref(0); // 音频播放进度
const timeInterval = ref(); // 获取音频播放进度定时器

// 音频加载完毕的回调
const onCanplay = () => {
  audioDuration.value = audioRef?.value.duration || 0;
}
const onPlay = async () => {
  // 音频播放完后,重新播放
  if (playProgress.value === 100) audioRef.value.currentTime = 0;

  await audioRef.value.play();
  playStatus.value = true;
  audioDuration.value = audioRef.value.duration;

  timeInterval.value = setInterval(() => {
    audioCurrent.value = audioRef.value.currentTime;
    playProgress.value = (audioCurrent.value / audioDuration.value) * 100;
    if (playProgress.value === 100) onPause();
  }, 100);
};
const onPause = () => {
  audioRef.value.pause();
  playStatus.value = false;
  clearInterval(timeInterval.value);
};
const onChangeSpeed = (value: number) => {
  activeSpeed.value = value;
  // 设置倍速
  audioRef.value.playbackRate = value;
  speedVisible.value = false;
};
const onHandleSpeed = () => {
  speedVisible.value = !speedVisible.value;
};
// 设置声音
const onSetVolume = (value: number) => {
  audioRef.value.volume = value;
  audioVolume.value = value;
};
// 音频播放时间换算
const transTime = (value: number) => {
  let time = "";
  let h = parseInt(String(value / 3600));
  value %= 3600;
  let m = parseInt(String(value / 60));
  let s = parseInt(String(value % 60));
  if (h > 0) {
    time = formatTime(h + ":" + m + ":" + s);
  } else {
    time = formatTime(m + ":" + s);
  }
  return time;
};
// 格式化时间显示,补零对齐
const formatTime = (value: string) => {
  let time = "";
  let s = value.split(":");
  let i = 0;
  for (; i < s.length - 1; i++) {
    time += s[i].length == 1 ? "0" + s[i] : s[i];
    time += ":";
  }
  time += s[i].length == 1 ? "0" + s[i] : s[i];

  return time;
};
</script>

<style lang="less" scoped>
.audio-player {
  width: 378px;
  height: 52px;
  background: linear-gradient(180deg, #505572 0%, #383b4f 100%);
  border-radius: 8px;
  padding: 9px 11px;
  margin: 40px 26px 0;
  box-sizing: border-box;
  display: flex;
  align-items: center;

  .play-icon {
    width: 34px;
    height: 34px;
    margin-right: 7px;
    cursor: pointer;
  }

  .play-time {
    width: 72px;
    display: inline-block;
    margin-right: 16px;
  }

  .play-progress {
    width: 160px;
    height: 4px;
    background-color: #323547;
    box-shadow: inset 0px 1px 0px 0px #20222d;
    border-radius: 2px;
    margin-right: 16px;
    position: relative;
    .play-current-progress {
      height: 4px;
      background: #00e5ff;
      border-radius: 2px;
      position: absolute;
      top: 0;
      left: 0;
    }
  }

  .play-voice {
    width: 20px;
    height: 20px;
    margin-right: 14px;
    cursor: pointer;
  }

  .play-speed {
    cursor: pointer;
    color: #00e5ff;
  }
}
</style>

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个使用 Vue3 和 TypeScript 封装的多选框函数示例: ```typescript import { defineComponent, PropType } from 'vue' interface Option { label: string value: string | number } export default defineComponent({ name: 'MultiSelect', props: { options: { type: Array as PropType<Option[]>, required: true }, value: { type: Array as PropType<(string | number)[]>, default: () => [] } }, emits: ['update:value'], setup(props, { emit }) { const isSelected = (value: string | number) => { return props.value.includes(value) } const toggleOption = (value: string | number) => { const idx = props.value.indexOf(value) if (idx === -1) { emit('update:value', [...props.value, value]) } else { const newValue = [...props.value] newValue.splice(idx, 1) emit('update:value', newValue) } } return { isSelected, toggleOption } }, template: ` <div> <label v-for="option in options" :key="option.value"> <input type="checkbox" :value="option.value" v-model="isSelected(option.value)" @change="toggleOption(option.value)"> {{ option.label }} </label> </div> ` }) ``` 使用时可以传入一个选项数组和一个值数组,选项数组包含多个选项对象,每个选项对象包含 `label` 和 `value` 两个属性,值数组包含已选的选项的值。组件会根据选项数组和值数组自动渲染多个复选框,并根据值数组设置复选框的选中状态。当用户勾选或取消勾选某个复选框时,组件会触发 `update:value` 事件并传递更新后的值数组。通过监听该事件,可以在父组件中更新值数组并实现多选框的功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值