自制 音频 拖拉组件

javascript 复制代码
<template>
    <div>
        <div class="audio-progress" v-if="isShowAudioProgress">
            <!-- 顶部按钮-进度条  -->
            <div class="play-icon-box" @click="playAudio">
                <em class="play-icon" :class="{ 'active': isPlay }"></em>
            </div>
            <div class="currentTime">{{ audioCurrentTime }}</div>
            <div class="progress-box">
                <div class="progress-bar">
                    <span class="blue-progress" :style="{ 'width': progress + '%' }"></span>
                    <span class="dot" v-show="isDot" :style="{ 'left': dotProgress + '%' }"
                        @touchstart.prevent="startDrag"></span>
                    <span class="progress"></span>
                </div>
            </div>
            <div class="allTime">{{ audioAllTime }}</div>
            <p class="audio-tip" v-show="isAudioTip">点击这里开始收听新闻</p>
        </div>
        <!-- 悬浮按钮-圆环  -->
        <div v-show="isShowCircle" class="audio-circle" :class="{ 'active': isCirclePlay }">
            <div class="close-icon" @click="rePlayFn">
                <img src="./images/close-icon.png" alt="">
            </div>
            <div class="circle-box" @click="playAudio">
                <canvas id="audioCanvas" style="width:100%"></canvas>
            </div>
        </div>
    </div>
</template>

<script>
import { setCookie, getCookie } from "@/util/common/cookie";

export default {
    name: "audioProgress",
    props: ["isShowAudioProgress"],
    data() {
        return {
            dotProgress: -5,
            progress: 0, //音频进度百分比
            audioCurrentTime: '00:00', //音频当前播放时间
            audioAllTime: '', //音频总播放时间
            isPlay: false, //是否正在播放
            isAudioTip: false,
            isWidth: 0,
            timer: null,
            isDot: true, ///测试 先显示出来
            //圆环
            isCirclePlay: false, // 圆环是否正在播放
            isShowCircle: false,
            circleTimer: null,
            ctx: null,
            isDragging: false, // 是否正在拖动
            dragStartX: 0,     // 拖动开始时的X坐标
            moveMinX: 0,
            moveMaxX: 0,
            progressBarWidth: 0,
            audioPlayer: null,
        }
    },
    mounted() {
        this.$nextTick(() => {
            this.showTip();
            // 获取音频时间
            this.changeProgress();
            this.clearFill();
        })
    },
    methods: {
        rePlayFn() {
            this.dotProgress = -5;
            this.progress = 0;
            this.isPlay = false;
            this.isCirclePlay = false;
            this.isShowCircle = false;
            this.audioCurrentTime = "00:00";
            this.audioPlayer.currentTime = 0;
            //暂停app的语音播放
            clearInterval(this.timer);
            this.audioPlayer.pause();

        },
        startDrag(event) {
            // 确保音频已加载
            this.audioPlayer = document.getElementById('detailAudioPlayer');
            if (!this.audioPlayer || isNaN(this.audioPlayer.duration)) return;

            // 获取进度条宽度(在拖动开始时获取)
            const progressBar = document.querySelector('.progress-bar');
            if (!progressBar) return;
            this.progressBarWidth = progressBar.offsetWidth;
            this.moveMinX = progressBar.getBoundingClientRect().left;
            this.moveMaxX = progressBar.getBoundingClientRect().width + this.moveMinX;

            this.dragStartX = event.touches[0].clientX;
            this.isDragging = true;

            // 阻止默认行为(防止选中文本)
            event.preventDefault();
            
            // 添加全局事件监听
            document.addEventListener('touchmove', this.dragging);
            document.addEventListener('touchend', this.stopDrag);
        },

        dragging(event) {
            if (!this.isDragging) return;

            // 计算新的进度百分比
            const mouseX = event.touches[0].clientX;
            // console.log(mouseX, 'mousex', event);

            if (mouseX < this.moveMinX) {
                this.progress = 0;
                this.dotProgress = 0 - 5;
            } else if (mouseX > this.moveMaxX) {
                this.progress = 100;
                this.dotProgress = 100 - 5;
            } else {
                const newProgress = ((mouseX - this.moveMinX) / this.progressBarWidth) * 100;
                this.progress = newProgress;
                this.dotProgress = newProgress - 5;
            }
            this.audioCurrentTime = this.realFormatSecond((this.audioPlayer.duration * (this.progress / 100)).toFixed(2));
            this.drawCircle(this.progress)
        },

        stopDrag() {
            this.isDragging = false;
            document.removeEventListener('touchmove', this.dragging);
            document.removeEventListener('touchend', this.stopDrag);

            // 设置音频实际播放位置
            if (this.audioPlayer && this.audioPlayer.duration > 0) {
                this.audioPlayer.currentTime = (this.audioPlayer.duration * (this.progress / 100)).toFixed(2);
            }
        },
        showTip() {
            let isFirst = getCookie("audio-isFirst");
            if (!isFirst) {
                this.isAudioTip = true; //首次才显示tip
                setTimeout(() => {
                    this.isAudioTip = false;
                }, 4000);
                setCookie("audio-isFirst", true, '365');
            } else {
                this.isAudioTip = false;
            }
        },
        //设置定时检测
        setAudioInterval() {
            let audioPlayer = document.getElementById('detailAudioPlayer');
            /*语音播放结束*/
            audioPlayer.addEventListener("ended",
                () => {
                    this.dotProgress = -5;
                    this.progress = 0;
                    this.isPlay = false;
                    this.isCirclePlay = false;
                     this.drawCircle(0)
                    this.audioCurrentTime = "00:00";
                }
            );
            this.timer = setInterval(() => {
                const n = audioPlayer.currentTime / audioPlayer.duration;
                let i = (n * 100).toFixed(2);
                if (i >= 100) {
                    clearInterval(this.timer);
                }

                if (!this.isDragging) {
                    this.drawCircle(i);
                    this.progress = i;
                    this.dotProgress = i - 5;
                    this.audioCurrentTime = this.realFormatSecond(audioPlayer.currentTime);
                    this.audioAllTime = this.realFormatSecond(audioPlayer.duration);
                }

            }, 30);
        },
        //播放
        playAudio() {
            this.isPlay = !this.isPlay;
            this.isCirclePlay = !this.isCirclePlay;
            this.isDot = true;
            let audioPlayer = document.getElementById('detailAudioPlayer');
            this.isShowCircle = true; //点击播放按钮才显示悬浮按钮
            if (this.isPlay) {
                this.setAudioInterval();
                audioPlayer.play();
            } else {
                //暂停
                clearInterval(this.timer);
                audioPlayer.pause();
            }
        },
        //获取播放时间
        changeProgress() {
            let audioPlayer = document.getElementById('detailAudioPlayer');
            this.audioAllTime = this.realFormatSecond(audioPlayer.duration);
            this.audioCurrentTime = this.realFormatSecond(audioPlayer.currentTime);
        },
        //格式化秒
        realFormatSecond(time) {
            //分钟
            if (isNaN(time)) {
                return "";
            }
            let minute = parseInt(time / 60);
            if (minute < 10) {
                minute = "0" + minute;
            }
            //秒
            let second = Math.round(time % 60);
            if (second < 10) {
                second = "0" + second;
            }
            return minute + ":" + second;
        },
        //绘制圆环
        drawCircle(i) {
            let canvas = document.getElementById('audioCanvas');
            //起始一条路径
            this.ctx = canvas.getContext('2d');
            canvas.width = 100;
            canvas.height = 100;
            this.ctx.beginPath();
            //连接处样式
            this.ctx.lineCap = 'round';
            //设置当前线条的宽度
            this.ctx.lineWidth = 10; //10px
            //设置笔触的颜色
            this.ctx.strokeStyle = '#ff4343';
            //arc(圆心x,圆心y,半径,开始角度,结束角度)
            //设置开始处为0点钟方向(-90 * Math.PI / 180)
            //n为百分比值(0-100)
            this.ctx.arc(50, 50, 42, -90 * Math.PI / 180, (i * 3.6 - 90) * Math.PI / 180);
            //绘制已定义的路径
            this.ctx.stroke(); //绘制
            this.ctx.closePath(); //路径结束
            this.ctx.restore();
        },
        clearFill() {
            this.circleTimer = null;
            const canvas = document.getElementById('audioCanvas');
            canvas.width = 0;
            canvas.height = 0;
        }
    }
}
</script>

<style scoped>
.audio-circle {
    position: fixed;
    bottom: 8rem;
    right: 4vw;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4.2vw;
    width: 23.644vw;
    height: 12.8vw;
    background-color: #ffffff;
    border-radius: 1.067vw;
    box-shadow: 0vw 0vw 1.778vw 0vw rgba(0, 0, 0, 0.2);
    z-index: 99;
}

.audio-circle .close-icon {
    width: 5.333vw;
    height: 5.333vw;
    display: flex;
    align-items: center;
    justify-content: center;
}

.audio-circle .close-icon img {
    width: 3.733vw;
    height: auto;
}

.audio-circle .circle-box {
    width: 7.111vw;
    height: 7.111vw;
    background: url("./images/news-pause1.png") no-repeat center;
    background-size: 100%;
}

.audio-circle.active .circle-box {
    background: url("./images/news-play1.png") no-repeat center;
    background-size: 100%;
}

.audio-tip {
    width: 11.1rem;
    height: 2.66rem;
    border-radius: .33rem;
    background: rgba(0, 0, 0, .8);
    position: absolute;
    left: 2%;
    bottom: -2.8rem;
    font-size: 0.933rem;
    color: #fff;
    text-align: center;
    line-height: 2.66rem;
    z-index: 99;
}

.audio-tip::after {
    position: absolute;
    top: -0.4rem;
    left: 10%;
    border-left: .4rem solid transparent;
    border-right: .4rem solid transparent;
    border-bottom: .4rem solid rgba(0, 0, 0, .8);
    content: " ";
    display: block;
    width: 0;
    height: 0;
}

.audio-progress {
    position: relative;
    width: 92vw;
    height: 10.667vw;
    margin-left: 4vw;
    background-color: #ffffff;
    border-radius: 5.333vw;
    border: solid 1px #e5e5e5;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 5vw;

}

.play-icon-box {
    width: 6.489vw;
    height: 6.489vw;
    margin-right: 4.3vw;
}

.play-icon-box .play-icon {
    display: block;
    width: 6.489vw;
    height: 6.489vw;
    background: url("./images/news-pause.png") no-repeat center;
    background-size: 100%;
    flex-shrink: 0;
    margin: 0 auto 0.13rem;
    cursor: pointer;
}


.play-icon.active {
    background: url("./images/news-play.png") no-repeat center;
    background-size: 100%;
}

.currentTime,
.allTime {
    font-size: 2.667vw;
    color: #333333;
}

.progress-box {
    width: 56.089vw;
    height: 0.8vw;
    margin: 0 2.6vw;
}

.progress-bar {
    width: 100%;
    position: relative;
    height: 0.8vw;

}

.blue-progress {
    position: absolute;
    width: 0;
    height: 0.8vw;
    border-radius: 0.8vw;
    background-color: #ff4343;
    z-index: 99;
}

.dot {
    position: absolute;
    left: -2%;
    top: 50%;
    width: 8vw;
    height: 8vw;
    background: url("./images/news-dot.png") no-repeat center center;
    background-size: 50%;
    margin-top: -4vw;
    z-index: 100;
}

.progress {
    position: absolute;
    width: 100%;
    height: 0.8vw;
    border-radius: 0.8vw;
    background-color: #f7e3e5;
    box-shadow: 0px 0px 0.13rem 0px rgba(0, 0, 0, .3);
    z-index: 98;
}

.progress-text {
    display: flex;
    display: -webkit-flex;
    align-items: center;
    justify-content: space-between;
    margin-top: .4rem;
}

.progress-text span {
    font-size: .6rem;
    color: #000;
}
</style>


底部圆环是联动的进度

相关推荐
简鹿办公4 小时前
用 MP3 Converter 提取 M4A 音频?超详细使用指南
音视频·手机视频转音频·手机视频转m4a
音视频牛哥11 小时前
AI时代底层技术链:GPU、云原生与大模型的协同进化全解析
大数据·云原生·kubernetes·音视频·transformer·gpu算力·云原生cloud native
梯度下降不了班12 小时前
【mmodel/xDit】Cross-Attention 深度解析:文生图/文生视频的核心桥梁
人工智能·深度学习·ai作画·stable diffusion·音视频·transformer
ACP广源盛1392462567314 小时前
GSV2125D@ACP#GSV6125#HDMI 2.0 转 DisplayPort 1.4 转换器(带嵌入式 MCU)
嵌入式硬件·计算机外设·音视频
赖small强15 小时前
【音视频开发】 ISP核心技术解析:3A算法(AE/AWB/AF)原理与实现
音视频·ae·af·awb·3a算法
ACP广源盛1392462567318 小时前
GSV2202D@ACP#DisplayPort 1.4 到 HDMI 2.0 转换器(带嵌入式 MCU)
单片机·嵌入式硬件·计算机外设·音视频
你好音视频18 小时前
RTSP拉流:RTP包解析流程详解
ffmpeg·音视频
赖small强19 小时前
【音视频开发】视频中运动模糊与拖影现象深度解析技术文档
音视频·快门·运动模糊·拖影
Dev7z20 小时前
基于MATLAB小波变换的音频水印算法研究与实现
开发语言·matlab·音视频