本篇文章给大家带来了关于vue3的相关知识,其中主要跟大家聊一聊怎么用vue3写个播放器,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。
ps:音乐可能播放失败。原因是 audio 的链接是临时的,手动替换下即可。
todo
实现播放/暂停;实现开始/结束时间及开始时间和滚动条动态跟随播放动态变化;实现点击进度条跳转指定播放位置;实现点击圆点拖拽滚动条。页面布局及 css 样式如下
<template> <div class="song-item"> <audio src="" /> <!-- 进度条 --> <div class="audio-player"> <span>00:00</span> <div class="progress-wrapper"> <div class="progress-inner"> <div class="progress-dot" /> </div> </div> <span>00:00</span> <!-- 播放/暂停 --> <div style="margin-left: 10px; color: #409eff; cursor: pointer;" > 播放 </div> </div> </div></template><style lang="scss"> * { font-size: 14px; } .song-item { display: flex; flex-direction: column; justify-content: center; height: 100px; padding: 0 20px; transition: all ease .2s; border-bottom: 1px solid #ddd; /* 进度条样式 */ .audio-player { display: flex; height: 18px; margin-top: 10px; align-items: center; font-size: 12px; color: #666; .progress-wrapper { flex: 1; height: 4px; margin: 0 20px 0 20px; border-radius: 2px; background-color: #e9e9eb; cursor: pointer; .progress-inner { position: relative; width: 0%; height: 100%; border-radius: 2px; background-color: #409eff; .progress-dot { position: absolute; top: 50%; right: 0; z-index: 1; width: 10px; height: 10px; border-radius: 50%; background-color: #409eff; transform: translatey(-50%); } } } } }</style>
实现播放/暂停思路:给 ”播放“ 注册点击事件,在点击事件中通过 audio 的属性及方法来判定当前歌曲是什么状态,是否播放或暂停,然后声明一个属性同步这个状态,在模板中做出判断当前应该显示 ”播放/暂停“。
关键性 api:
audio.paused:当前播放器是否为暂停状态
audio.play():播放
audio.pause():暂停
const audioisplaying = ref(false); // 用于同步当前的播放状态const audioele = ref<htmlaudioelement | null>(null); // audio 元素/** * @description 播放/暂停音乐 */const toggleplayer = () => { if (audioele.value) { if (audioele.value?.paused) { audioele.value.play(); audioisplaying.value = true; } else { audioele.value?.pause(); audioisplaying.value = false; } }};onmounted(() => { // 页面点击的时候肯定是加载完成了,这里获取一下没毛病 audioele.value = document.queryselector('audio');});
最后把属性及事件应用到模板中去。
<div style="margin-left: 10px; color: #409eff; cursor: pointer;" @click="toggleplayer"> {{ audioisplaying ? '暂停' : '播放'}}</div>
实现开始/结束时间及开始时间和滚动条动态跟随播放动态变化思路:获取当前已经播放的时间及总时长,然后再拿当前时长 / 总时长及得到歌曲播放的百分比即滚动条的百分比。通过侦听 audio 元素的 timeupdate 事件以做到每次当前时间改变时,同步把 dom 也进行更新。最后播放完成后把状态初始化。
关键性api:
audio.currenttime:当前的播放时间;单位(s)
audio.duration:音频的总时长;单位(s)
timeupdate:currenttime 变更时会触发该事件。
import dayjs from 'dayjs';const audiocurrentplaytime = ref('00:00'); // 当前播放时长const audiocurrentplaycounttime = ref('00:00'); // 总时长const pgsinnerele = ref<htmldivelement | null>(null);/** * @description 更新进度条与当前播放时间 */const updateprogress = () => { const currentprogress = audioele.value!.currenttime / audioele.value!.duration; pgsinnerele.value!.style.width = `${currentprogress * 100}%`; // 设置进度时长 if (audioele.value) audiocurrentplaytime.value = dayjs(audioele.value.currenttime * 1000).format('mm:ss');};/** * @description 播放完成重置播放状态 */const audioplayended = () => { audiocurrentplaytime.value = '00:00'; pgsinnerele.value!.style.width = '0%'; audioisplaying.value = false;};onmounted(() => { pgsinnerele.value = document.queryselector('.progress-inner'); // 设置总时长 if (audioele.value) audiocurrentplaycounttime.value = dayjs(audioele.value.duration * 1000).format('mm:ss'); // 侦听播放中事件 audioele.value?.addeventlistener('timeupdate', updateprogress, false); // 播放结束 event audioele.value?.addeventlistener('ended', audioplayended, false);});
实现点击进度条跳转指定播放位置思路:给滚动条注册鼠标点击事件,每次点击的时候获取当前的 offsetx 以及滚动条的宽度,用宽度 / offsetx 最后用总时长 * 前面的商就得到了我们想要的进度,再次更新进度条即可。
关键性api:
event.offsetx:鼠标指针相较于触发事件对象的 x 坐标。
/** * @description 点击滚动条同步更新音乐进度 */const clickprogresssync = (event: mouseevent) => { if (audioele.value) { // 保证是正在播放或者已经播放的状态 if (!audioele.value.paused || audioele.value.currenttime !== 0) { const pgswrapperwidth = pgswrapperele.value!.getboundingclientrect().width; const rate = event.offsetx / pgswrapperwidth; // 同步滚动条和播放进度 audioele.value.currenttime = audioele.value.duration * rate; updateprogress(); } }};onmounted({ pgswrapperele.value = document.queryselector('.progress-wrapper'); // 点击进度条 event pgswrapperele.value?.addeventlistener('mousedown', clickprogresssync, false);});// 别忘记统一移除侦听onbeforeunmount(() => { audioele.value?.removeeventlistener('timeupdate', updateprogress); audioele.value?.removeeventlistener('ended', audioplayended); pgswrapperele.value?.removeeventlistener('mousedown', clickprogresssync);});
实现点击圆点拖拽滚动条。思路:使用 hook 管理这个拖动的功能,侦听这个滚动条的 鼠标点击、鼠标移动、鼠标抬起事件。
点击时: 获取鼠标在窗口的 x 坐标,圆点距离窗口的 left 距离及最大的右移距离(滚动条宽度 - 圆点距离窗口的 left)。为了让移动式不随便开始计算,在开始的时候可以弄一个开关 flag
移动时: 实时获取鼠标在窗口的 x 坐标减去 点击时获取的 x 坐标。然后根据最大移动距离做出判断,不要让它越界。最后: 总时长 * (圆点距离窗口的 left + 计算得出的 x / 滚动条长度) 得出百分比更新滚动条及进度
抬起时:将 flag 重置。
/** * @method usesongprogressdrag * @param audioele * @param pgswrapperele * @param updateprogress 更新滚动条方法 * @param startsongdragdot 是否开启拖拽滚动 * @description 拖拽更新歌曲播放进度 */const usesongprogressdrag = ( audioele: ref<htmlaudioelement | null>, pgswrapperele: ref<htmldivelement | null>, updateprogress: () => void, startsongdragdot: ref<boolean>) => { const audioplayer = ref<htmldivelement | null>(null); const audiodotele = ref<htmldivelement | null>(null); const dragflag = ref(false); const position = ref({ startoffsetleft: 0, startx: 0, maxleft: 0, maxright: 0, }); /** * @description 鼠标点击 audioplayer */ const mousedownprogresshandle = (event: mouseevent) => { if (audioele.value) { if (!audioele.value.paused || audioele.value.currenttime !== 0) { dragflag.value = true; position.value.startoffsetleft = audiodotele.value!.offsetleft; position.value.startx = event.clientx; position.value.maxleft = position.value.startoffsetleft; position.value.maxright = pgswrapperele.value!.offsetwidth - position.value.startoffsetleft; } } event.preventdefault(); event.stoppropagation(); }; /** * @description 鼠标移动 audioplayer */ const mousemoveprogresshandle = (event: mouseevent) => { if (dragflag.value) { const clientx = event.clientx; let x = clientx - position.value.startx; if (x > position.value.maxright) x = position.value.maxright; if (x < -position.value.maxleft) x = -position.value.maxleft; const pgswidth = pgswrapperele.value?.getboundingclientrect().width; const reat = (position.value.startoffsetleft + x) / pgswidth!; audioele.value!.currenttime = audioele.value!.duration * reat; updateprogress(); } }; /** * @description 鼠标取消点击 */ const mouseupprogresshandle = () => dragflag.value = false; onmounted(() => { if (startsongdragdot.value) { audioplayer.value = document.queryselector('.audio-player'); audiodotele.value = document.queryselector('.progress-dot'); // 在捕获中去触发点击 dot 事件. fix: 点击原点 offset 回到原点 bug audiodotele.value?.addeventlistener('mousedown', mousedownprogresshandle, true); audioplayer.value?.addeventlistener('mousemove', mousemoveprogresshandle, false); document.addeventlistener('mouseup', mouseupprogresshandle, false); } }); onbeforeunmount(() => { if (startsongdragdot.value) { audioplayer.value?.removeeventlistener('mousedown', mousedownprogresshandle); audioplayer.value?.removeeventlistener('mousemove', mousemoveprogresshandle); document.removeeventlistener('mouseup', mouseupprogresshandle); } });};
最后调用这个 hook
// 是否显示可拖拽 dot// 可以在原点元素上增加 v-if 用来判定是否需要拖动功能const startsongdragdot = ref(true);usesongprogressdrag(audioele, pgswrapperele, updateprogress, startsongdragdot);
【相关推荐:vue.js视频教程】
以上就是手把手教你用vue3写播放器的详细内容。