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

支持点击修改进度,拖拽修改进度,当然大家肯定都知道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绘…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
