自造简易版音频进度条
最近在做音乐播放器页面, 积累了很多有趣的经验, 今天先分享播放进度条的开发过程.
效果
话不多说,先看效果

支持点击修改进度,拖拽修改进度,当然大家肯定都知道ui库里面有现成的,为何要自己造一个
首先著名的ui库中确实都要这样的滑动输入条,比如antd-mobile中的slider
官网:https://mobile.ant.design/zh/components/slider/
效果很多

比我自己造的肯定功能丰富的多,但是亲自试过之后,发现效果不太友好,下面可以看看使用antd-mobile中的slider效果如下:
对比
这是antd-mobile的效果

代码如下:
其实把value属性去掉,这个组件就会丝滑很多,但是音乐播放器,需要随着音频播放,更改进度条,这是必须要的功能,不能去掉。
<div className={styles.process}><div className={styles.processTime}>{currentTime ? formatTime(currentTime) : '00:00'}</div><SliderclassName={styles.songSlider}defaultValue={0}onAfterChange={changeProgressValue}value={currentTime && duration ? currentTime / duration * 100 : 0}icon={<div className={styles.sliderDot} />}/>{/* <MusicSliderclassName={styles.songSlider}defaultValue={currentTime && duration ? currentTime / duration * 100 : 0}onAfterChange={change}value={currentTime && duration ? currentTime / duration * 100 : 0}/> */}<div className={styles.processTime}>{duration ? formatTime(duration) : '00:00'}</div>
</div>
changeProgressValue事件就是修改音频的currentTime
除此之外,我也试用了其他的依赖库,比如react-slider
https://github.com/zillow/react-slider
但是效果依旧不好,就是因为有这种两三点的滑动,所以导致逻辑复杂,滑动效果就不太丝滑

所以,我就觉得自己造一个,slider组件
点击修改进度
点击事件比较简单,就需要给这个区域绑定点击事件

灰色区域是父元素,红色元素是子元素,红色元素的宽度就是歌曲当前播放进度比分比 * 父元素宽度
首先,需要理清楚几个坐标,如何确定点击这里是音频进度所占比分比
点击当前点的坐标,点击的时候,能够拿到;父元素的宽度,通过getBoundingClientRect().width也能拿到
父元素左边距离最左边的距离,也能拿到getBoundingClientRect().left,也就是下面这段距离。

所以最终的点击函数如下:
// 点击事件const barClick = (e: React.MouseEvent) => {// @ts-ignoreconst rect = mmProgress.current.getBoundingClientRect()const activeWidthVal = Math.min(rect.width, Math.max(0, e.clientX - rect.left))// @ts-ignoreconst progress = Math.floor(activeWidthVal / mmProgress.current.clientWidth * 100)setActiveWidth(progress)if (onAfterChange) {onAfterChange(progress)}}
拖拽修改进度
在电脑上,需要监听的是鼠标的mouseup和mouseMove事件
在移动端,需要监听的是touchend和touchmove事件
鼠标移动/触屏移动:需要更新进度条的百分比
鼠标弹起/触屏结束:需要更新歌曲的进度
开始事件能够直接绑定在进度小圆点上
开始时,需要获取到开始的坐标,并且存起来,方便移动事件计算
mouseDown
// 触摸开始事件const barDown = (e: React.TouchEvent) => {startX.current = e.touches[0].pageX// @ts-ignoreleftVal.current = mmProgressInner.current.clientWidthisDrag.current = true}
// 鼠标开始移动const barDown1 = (e: React.MouseEvent) => {startX.current = e.clientX// @ts-ignoreleftVal.current = mmProgressInner.current.clientWidthisDrag.current = true}// tsx
<div className={styles.sliderDot}onMouseDown={barDown1}onTouchStart={barDown}></div>
由于我直接绑定在了tsx元素上,为了防止ts报错,我就写了两个函数,因为两者的类型不同
类型错误如下:

mouseMove
鼠标移动,需要及时更新进度条的样式,也就是红色条的宽度
所以需要计算当前点击的坐标,和上面函数保持的开始移动坐标
然后就是计算百分比,更新样式
// 鼠标/触摸移动事件const barMove = (e: React.TouchEvent & React.MouseEvent) => {if (isDrag.current) {const endX = e.clientX || e.touches[0].pageXconst dist = endX - startX.current// @ts-ignoreconst activeWidthVal = Math.min(mmProgress.current.clientWidth, Math.max(0, leftVal.current + dist))// @ts-ignoreconst progress = Math.floor(activeWidthVal / mmProgress.current.clientWidth * 100)setActiveWidth(progress)dynamicState.current = progress}}
mouseUp
鼠标抬起,这个函数需要说一下,首先需要判断一下是否已经在鼠标抬起时完成了鼠标放下事件mouseDown
为什么呢?防止这两种情况


这两种情况,也会触发mouseMove和mouseUp事件,但是这两种情况都不可以修改进度
所以需要一个变量来判断是否是在小圆点处发生了mouseDown事件
// 鼠标/触摸释放事件const barUp = () => {// 避免打开Playing组件时触发if (isDrag.current && onAfterChange) {// @ts-ignoreonAfterChange(dynamicState.current)}}
销毁事件
到这里已经接近尾声了,但是注意挂载了事件,需要销毁
useMount(() => {bindEvents()
})useUnmount(()=> {unbindEvents()})// 添加绑定事件const bindEvents = () => {// @ts-ignoremmProgress.current.addEventListener('mousemove', barMove)// @ts-ignoremmProgress.current.addEventListener('mouseup', barUp)// @ts-ignoremmProgress.current.addEventListener('touchmove', barMove)// @ts-ignoremmProgress.current.addEventListener('touchend', barUp)}// 移除绑定事件const unbindEvents = () => {if (mmProgress.current) {// @ts-ignoremmProgress.current.removeEventListener('mousemove', barMove)// @ts-ignoremmProgress.current.removeEventListener('mouseup', barUp)// @ts-ignoremmProgress.current.removeEventListener('touchmove', barMove)// @ts-ignoremmProgress.current.removeEventListener('touchend', barUp)}}
最后全部代码如下:
import classNames from 'classnames'
import { useEffect, useRef, useState } from 'react'
import styles from './index.module.scss'
import { useMount, useUnmount } from 'ahooks';export default function MusicSlider(props: any) {const { className, defaultValue, onAfterChange, value } = propsconst [activeWidth, setActiveWidth] = useState(defaultValue)const dynamicState = useRef(0)const startX = useRef(0) // 记录最开始点击的x坐标const leftVal = useRef(0) // 记录当前已经移动的距离const isDrag = useRef(false) // 是否可以拖拽const mmProgress = useRef(null)const mmProgressInner = useRef(null)useMount(() => {bindEvents()})useEffect(() => {const progress = Math.floor(value)// @ts-ignoresetActiveWidth(progress)}, [value])useUnmount(()=> {unbindEvents()})// 添加绑定事件const bindEvents = () => {// @ts-ignoremmProgress.current.addEventListener('mousemove', barMove)// @ts-ignoremmProgress.current.addEventListener('mouseup', barUp)// @ts-ignoremmProgress.current.addEventListener('touchmove', barMove)// @ts-ignoremmProgress.current.addEventListener('touchend', barUp)}// 移除绑定事件const unbindEvents = () => {if (mmProgress.current) {// @ts-ignoremmProgress.current.removeEventListener('mousemove', barMove)// @ts-ignoremmProgress.current.removeEventListener('mouseup', barUp)// @ts-ignoremmProgress.current.removeEventListener('touchmove', barMove)// @ts-ignoremmProgress.current.removeEventListener('touchend', barUp)}}// 点击事件const barClick = (e: React.MouseEvent) => {// @ts-ignoreconst rect = mmProgress.current.getBoundingClientRect()const activeWidthVal = Math.min(rect.width, Math.max(0, e.clientX - rect.left))// @ts-ignoreconst progress = Math.floor(activeWidthVal / mmProgress.current.clientWidth * 100)setActiveWidth(progress)if (onAfterChange) {onAfterChange(progress)}}// 触摸开始事件const barDown = (e: React.TouchEvent) => {startX.current = e.touches[0].pageX// @ts-ignoreleftVal.current = mmProgressInner.current.clientWidthisDrag.current = true}
// 鼠标开始移动const barDown1 = (e: React.MouseEvent) => {startX.current = e.clientX// @ts-ignoreleftVal.current = mmProgressInner.current.clientWidthisDrag.current = true}// 鼠标/触摸移动事件const barMove = (e: React.TouchEvent & React.MouseEvent) => {if (isDrag.current) {const endX = e.clientX || e.touches[0].pageXconst dist = endX - startX.current// @ts-ignoreconst activeWidthVal = Math.min(mmProgress.current.clientWidth, Math.max(0, leftVal.current + dist))// @ts-ignoreconst progress = Math.floor(activeWidthVal / mmProgress.current.clientWidth * 100)setActiveWidth(progress)dynamicState.current = progress}}// 鼠标/触摸释放事件const barUp = () => {// 避免打开Playing组件时触发if (isDrag.current && onAfterChange) {// @ts-ignoreonAfterChange(dynamicState.current)}}return (<div className={classNames(className, styles.progress)} ref={mmProgress} onClick={barClick}><div className={styles.bar}></div><div className={styles.outer}></div><div className={styles.inner} ref={mmProgressInner} style={{width: `${activeWidth}%`}}><div className={styles.sliderDot}onMouseDown={barDown1}onTouchStart={barDown}></div></div></div>)
}
相关文章:
自造简易版音频进度条
最近在做音乐播放器页面, 积累了很多有趣的经验, 今天先分享播放进度条的开发过程. 效果 话不多说,先看效果 支持点击修改进度,拖拽修改进度,当然大家肯定都知道ui库里面有现成的,为何要自己造一个 首先著名的ui库中确实都要这…...
433MHz芯片在遥控应用市场中的优点
当涉及到简单的无线射频通信,433MHz芯片成为一种经济实惠且广泛应用的选择。以下是关于433MHz芯片的重点信息: 工作原理:433MHz芯片的工作原理是将数字信号转化为射频信号,并通过无线信道进行传输。在接收端,射频信号再…...
基于Bert+Attention+LSTM智能校园知识图谱问答推荐系统——NLP自然语言处理算法应用(含Python全部工程源码及训练模型)+数据集
目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境服务器环境 模块实现1. 构造数据集2. 识别网络3. 命名实体纠错4. 检索问题类别5. 查询结果 系统测试1. 命名实体识别网络测试2. 知识图谱问答系统整体测试 工程源代码下载其它资料下载 前言 这个项目充分利用了…...
慕尼黑主题活动!亚马逊云科技生成式AI全新解决方案,引领未来移动出行领域
IAA作为世界五大车展之一,一直对全球汽车产业的发展起着关键作用!2023年9月5日在慕尼黑开幕的IAA MOBILITY 2023以“体验联动智慧出行”为主题,紧跟移动出行领域的前沿变化,将汇集整车企业、开发者、供应商、科技公司、服务提供商…...
android 离线语言合成(文字转语音)
1、基于开源MaryTTS https://github.com/AndroidMaryTTS/AndroidMaryTTS 目前查到的资料,不支持中文,只针对西方语种。 2、基于TensorFlowTTS 官方个地址:为 Android 构建 TensorFlow Lite 库 (google.cn) 所依赖包下载地址:Maven Centr…...
使用Fastchat部署vicuna大模型
FastChat是一个用于训练、提供服务和评估基于大型语言模型的聊天机器人的开放平台。其核心特点包括: 最先进模型(例如 Vicuna)的权重、训练代码和评估代码。一个分布式的多模型提供服务系统,配备 Web 用户界面和与 OpenAI 兼容的…...
【2023高教社杯】C题 蔬菜类商品的自动定价与补货决策 问题分析、数学模型及python代码实现
【2023高教社杯】C题 蔬菜类商品的自动定价与补货决策 1 题目 C题蔬菜类商品的自动定价与补货决策 在生鲜商超中,一般蔬菜类商品的保鲜期都比较短,且品相随销售时间的增加而变差, 大部分品种如当日未售出,隔日就无法再售。因此&…...
华为云云耀云服务器L实例评测|华为云云耀云服务器L实例评测使用
作者简介: 辭七七,目前大一,正在学习C/C,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: 七七的闲谈 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖…...
【DS思想+堆贪心】CF595div3 D2
Problem - D2 - Codeforces 题意: 思路: 大家都说这是典,但是我不懂怎么个典法,可能堆贪心都是这样做的吗,不懂 首先肯定要贪心,对于一个坏点,优先删除覆盖别的点多的 考虑nlogn做法&#x…...
2023-09-08 LeetCode每日一题(计算列车到站时间)
2023-09-08每日一题 一、题目编号 2651. 计算列车到站时间二、题目链接 点击跳转到题目位置 三、题目描述 给你一个正整数 arrivalTime 表示列车正点到站的时间(单位:小时),另给你一个正整数 delayedTime 表示列车延误的小时…...
软考-高级-信息系统项目管理第四版(完整24章全笔记)
《信息系统项目管理师教程》(第4版)是由全国计算机专业技术资格考试办公室组织编写的考试用书,根据2022年审定通过的《信息系统项目管理师考试大纲》编写,对信息系统项目管理师岗位所要求的主要知识及应用技术进行了阐述。 《信息…...
华为Mate 60和iPhone 15选哪个?
最近也有很多朋友问我这个问题来着,首先两款手机定位都是高端机,性能和体验各有千秋,各自有自己的铁杆粉。 但是让人意想不到的是华为mate60近日在海外越来越受欢迎和追捧,甚至是引起了不少人的抢购,外观设计和…...
嵌入式Linux驱动开发(同步与互斥专题)(二)
一、自旋锁spinlock的实现 自旋锁,顾名思义:自己在原地打转,等待资源可用,一旦可用就上锁霸占它。 ① 原地打转的是CPU x,以后CPU y会解锁:这涉及多个CPU,适用于SMP系统; ② 对于单…...
Docker安装部署Nexus3作为内网镜像代理缓存容器镜像
Docker安装部署Nexus3作为内网镜像代理 一、背景描述 基础镜像比较小,仓库使用阿里云或者腾讯云拉取速度挺快,但是时光飞逝几年时间过去,再加上AI加持的情况下,有些镜像的大小已经接近20G! 这种情况下不管是测试环境…...
SpringBoot工具库:解决SpringBoot2.*版本跨域问题
1.解决问题:When allowCredentials is true, xxxxxxx , using “allowedOriginPatterns“ instead 2.3版本跨域配置如下 /*** 跨域问题解决*/ Configuration public class CorsConfig implements WebMvcConfigurer {Overridepublic void addCorsMappings(CorsRegi…...
docker安装开发常用软件MySQL,Redis,rabbitMQ
Docker安装 docker官网:Docker: Accelerated Container Application Development docker镜像仓库:https://hub.docker.com/search?qnginx 官网的安装教程:Install Docker Engine on CentOS | Docker Docs 安装步骤 1、卸载以前安装的doc…...
C# Unity FSM 状态机
C# Unity FSM 状态机 使用状态机可以降低代码耦合性,并且可以优化代码可读性,方便团队协作等。 对于游戏开发内容来讲游戏开发的流程控制玩家动画都可以使用FSM有限状态机来实现。 1.FsmState 每个状态的基类,泛型参数表示所拥有者 publi…...
pytorch搭建squeezenet网络的整套工程,及其转tensorrt进行cuda加速
本来,前辈们用caffe搭建了一个squeezenet的工程,用起来也还行,但考虑到caffe的停更后续转trt应用在工程上时可能会有版本的问题所以搭建了一个pytorch版本的。 以下的环境搭建不再细说,主要就是pyorch,其余的需要什么p…...
【精读Uboot】SPL阶段的board_init_r详细分析
对于i.MX平台上的SPL来说,其不会直接跳转到Uboot,而是在SPL阶段借助BOOTROM跳转到ATF,然后再通过ATF跳转到Uboot。 board_init_f会初始化设备相关的硬件,最后进入board_init_r为镜像跳转做准备。下面是board_init_r调用的核心函数…...
canvas绘制渐变色三角形金字塔
项目需求:需要绘制渐变色三角形金字塔,并用折线添加标识 (其实所有直接用图片放上去也行,但是ui没切图,我也懒得找她要,正好也没啥事,直接自己用代码绘制算了,总结一句就是闲的) 最终效果如下图: (以上没用任何图片,都是代码绘制的) 在网上找了,有用canvas绘…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
