效果图是这样的,网上和线上小程序大部分都是用slider做的,方便快捷。
我也有试过效果 发现都有些丢帧,因为slider + progrss 滑动不够流畅
所以自己写了一个这样的东西分享记录一下,完成后样式可以随意自定义。
需求:
1、开始播放后,每秒钟自动变化进度条。根据音频时长,进度条长度计算位移多少。
2、手指拖拽时候,时间随拖拽变化,方便定位到那个时间点然后从那一刻开始播放。
3、适配机型宽度,整体效果一致。
4、可以暂停,可以暂停后拖拽后在播放。加载中的时候,显示加载icon。
5、显示缓存进度条,用户可以知道当前缓存多少。
实现思路和方式:
1、用三个div分别代表 进度条底儿、当前缓存(红色)、当前进度条位置,然后通过小程序动画 移动后两个,通过定时器去每秒钟移动,进度条用transform:translateX 比较流畅,缓存用width比较实用。
2、进度条div里面在来个div绑定上3个touch事件,然后让进度条整体translateX(-100%)
3、计算 rpx和和px的比值 如果你的设计稿是750 ,就用你 设备宽度/750 这样才可以计算位置。因为小程序的touch只能拿到px
也就是clientX的值。
4、计算好拖动时候的动画,和左右两边的限制。
代码:
<template>
<div class="audio_box">
<div class="currentTimeBack">{{currentTimeBack}}</div>
<div class="currentTimeForward">{{currentTimeForward}}</div>
<!-- <div class="audio_details_box">
<div class="words_count">单词数: {{audioData.wordsCount}}</div>
<div class="switch_en_ch">显示中文释义</div>
</div> -->
<!-- <text class="current-process">{{current_process}}</text> -->
<div class="sliderbox">
<div class="sliderbg"></div>
<div class="sliderbufferedbg" v-bind:style="{width:bufferedWidthVal}"></div>
<div class="sliderLine" id="linebox" :animation="animatedata">
<div class="sliderPoint" @touchstart="touchLineStart" @touchmove="touchLineMove" @touchend="toudLineEnd"></div>
</div>
</div>
<div class="audio_control_box">
<img @click="prev" mode="widthFix" src="/static/audio/audio_pre.png" class="audio_pre"/>
<img @click="audio_play" v-show="!waiting" mode="widthFix" :src="[is_play?'/static/audio/audio_pause.png': '/static/audio/audio_play.png']" class="audio_play_bt" />
<img mode="widthFix" v-show="waiting" src="/static/audio/audio_waiting.png" class="audio_play_bt waitingbt" />
<img @click="next" mode="widthFix" src="/static/audio/audio_next.png" class="audio_next"/>
</div>
</div>
</template>
<script>
export default {
porps: ['audioManager'],
data() {
return {
isIos: null,
waiting: false,
dpi: -1,
screenW: wx.getSystemInfoSync().screenWidth,
audioManager: null,
is_play: false,
currentTimeForward: '',
newCurentTime: -1,
currentTimeBack: '',
audioData: {
duration: 0
},
animatedata: {},
timer: null,
isTouching: false,
bufferedVal: 0,
bufferedWidthVal: 0
};
},
async onLoad() {
this.isIos = await global.isIos();
this.audioManager = wx.getBackgroundAudioManager();
this.dpi = parseFloat((this.screenW / 750).toFixed(3)); // dpi指数
const url =
'https://ssl-public.langlib.com/pe/aha/test/GRE_demo.mp3?jjj=44';
this.audioData.url = url;
this.audioData.title = '雅思托福串讲音频';
this.audioData.lessonName = 'langlib';
// 置灰上一首下一首
// if (response.preArticleId == 0) {
// that.setData({
// is_first_page: true
// });
// }
// if (response.nextArticleId == 0) {
// that.setData({
// is_last_page: true
// });
// }
this.audioManager.onSeeking(() => {
console.log('onSeek');
});
this.audioManager.onPlay(() => {
console.log('onplay');
clearInterval(this.timer);
this.is_play = true;
this.waiting = false;
this.setDuration();
this.timer = setInterval(() => {
this.setDuration();
}, 1000);
});
this.audioManager.onCanplay(() => {
console.log('oncanplay');
this.waiting = false;
});
// 背景音频播放进度更新事件
this.audioManager.onTimeUpdate(() => {
});
// 背景音频播放完毕
this.audioManager.onEnded(() => {
clearInterval(this.timer);
console.log('audioEnd');
this.lineAnimate('-2px');
});
this.audioManager.onPause(() => {
console.log('onPause');
this.is_play = false;
});
this.audioManager.onWaiting(() => {
console.log('onwating');
this.waiting = true;
});
},
methods: {
touchLineStart() {
console.log('touchLineStart');
this.isTouching = true;
clearInterval(this.timer);
},
touchLineMove(e) {
this.isTouching = true;
const rate = e.clientX / 750;
const moveLeftpx = e.clientX;
const boxWidth = 560 * this.dpi;
const limitLeftpx = (this.screenW - boxWidth) / 2;
if (
moveLeftpx <= limitLeftpx - 1.5 ||
e.clientX >= (limitLeftpx + boxWidth) - 2
) {
return;
}
const sliderValue = `${moveLeftpx - limitLeftpx - boxWidth}px`;
this.lineAnimate(sliderValue);
// 不仅要让滑块运动,在滑动过程中 当前时间也要跟随变动,这样在抬起时可以定点播放
// 1.计算当前滑动的点 占整个进度条长度(px)的百分比
// 2.百分比 * 当前总音频时长 = 当前点所在的时长
// 3.当比例小于1或者大于1 代表 超出了范围 就return吧
const movePercent = (moveLeftpx - limitLeftpx) / boxWidth;
this.newCurentTime =
movePercent * this.audioManager.duration > 0
? movePercent * this.audioManager.duration
: 1;
if (movePercent < 0 || movePercent > 1) return;
this.currentTimeForward = this.stotime(this.newCurentTime);
},
toudLineEnd(e) {
console.log(this.newCurentTime);
console.log(this.audioManager.buffered);
if (this.audioManager.paused && this.newCurentTime) {
this.isTouching = false;
this.is_play = false;
this.audio_play(this.newCurentTime);
return;
}
if (this.newCurentTime) {
this.audioManager.seek(this.newCurentTime);
this.is_play = true;
}
this.isTouching = false;
},
// 点击播放暂停
audio_play(newCurentTime) {
if (this.is_play) {
this.is_play = false;
this.audioManager.pause();
clearInterval(this.timer);
} else {
this.is_play = true;
if (typeof newCurentTime === 'number') {
this.audioManager.seek(newCurentTime);
this.audioManager.play();
return;
}
// if (this.audioManager.paused) {
// this.audioManager.play();
// return;
// }
this.playAudio();
}
},
// 上一首
prev() {
},
// 下一首
next() {
},
playAudio() {
if (this.isIos) this.waiting = true;
this.audioManager.title = this.audioData.title;
this.audioManager.src = this.audioData.url;
this.audioManager.play();
},
setDuration() {
// 显示 播放时间 音频时长
if (this.isTouching) return;
const currentTimeForward = this.audioManager.currentTime;
const currentTimeBack = this.audioManager.duration || 0;
const brffer = this.audioManager.buffered;
this.currentTimeForward = this.stotime(currentTimeForward);
this.currentTimeBack = this.stotime(currentTimeBack);
// 让当前的进度条 滑动到对应的位置
// 1、当前音频进行到了总音频的占比
// 2、计算进度条应该位移多少
// 3、使用动画让进度条移动
// 4、如果在我手动去滑动进度条的时候,就别走动画了
const linePointMovePercent = currentTimeForward / currentTimeBack;
const boxWidth = 560 * this.dpi;
const linePointMovePx = `${(linePointMovePercent * boxWidth).toFixed() -
boxWidth}px`;
// console.log(`${boxWidth}长px left:${linePointMovePx}`);
console.log(brffer);
if (brffer && this.audioManager.duration) {
this.bufferedVal = this.audioManager.buffered;
this.bufferedWidthVal =
`${parseFloat(brffer / currentTimeBack) *
600 *
this.dpi}px`;
}
this.lineAnimate(linePointMovePx);
},
lineAnimate(movepx) {
const animation = wx.createAnimation({
duration: 250,
timingFunction: 'ease-out'
});
const animate = animation.translate(`${movepx}`, '-50%').step();
this.animatedata = animate.export();
},
// 时间转换
stotime(s) {
let t = '';
let ss = s;
if (ss > -1) {
if (s > 0 && s < 1) {
ss = 1;
}
const min = Math.floor(ss / 60) % 60;
const sec = Math.floor(ss % 60);
if (min < 10) {
t += '0';
}
t += `${min}:`;
if (sec < 10) {
t += '0';
}
t += sec;
}
return t;
}
}
};
</script>
<style scoped>
.box {
width: 200rpx;
height: 20rpx;
background: red;
position: absolute;
top: 50%;
left: 0;
}
.audio_box {
width: 100vw;
height: 400rpx;
position: absolute;
bottom: 0;
left: 0;
background: #fff;
}
.audio_details_box {
width: 750rpx;
height: 90rpx;
background: #ccc;
display: flex;
align-items: center;
justify-content: space-between;
}
.words_count,
.switch_en_ch {
font-size: 30rpx;
color: rgba(51, 51, 51);
}
.words_count {
margin-left: 40rpx;
}
.switch_en_ch {
margin-right: 40rpx;
}
.sliderbox {
width: 600rpx;
height: 50rpx;
margin: 20rpx auto;
position: relative;
overflow-x: hidden;
}
.slider,
.sliderbg,
.sliderbufferedbg,
.sliderLine {
width: 600rpx;
height: 20rpx;
position: absolute;
top: 50%;
margin: 0;
transform: translateY(-50%);
left: 0;
}
.sliderbufferedbg {
background: red;
width: 0;
z-index: 4;
}
.slider {
top: 60rpx;
}
.sliderbg {
background: rgba(206, 206, 206, 1);
z-index: 3;
}
.sliderLine {
background: rgba(64, 64, 64, 1);
transform: translate(calc(-100% + 36rpx), -50%);
z-index: 4;
}
.sliderPoint {
position: absolute;
right: -2rpx;
top: -10rpx;
width: 40rpx;
height: 40rpx;
background: rgba(64, 64, 64, 1);
border-radius: 50%;
}
.audio_control_box{
width: 80%;
height: 120rpx;
margin:0 auto;
display: flex;
align-items: center;
justify-content:space-around;
}
.audio_play_bt,.audio_pre,.audio_next {
width: 40rpx;
height: 40rpx;
}
.currentTimeBack,
.currentTimeForward {
position: absolute;
right: 30rpx;
top: 60rpx;
font-size: 32rpx;
font-weight: 500;
color: black;
width: 100rpx;
height: 50rpx;
text-align: center;
}
.currentTimeForward {
left: 30rpx;
}
.waitingbt{
animation:rotate 3s linear infinite;
}
@keyframes rotate2{
from{transform: rotate(0deg)}
to{transform: rotate(359deg)}
}
</style>
最后的样子: