自造简易版音频进度条

最近在做音乐播放器页面, 积累了很多有趣的经验, 今天先分享播放进度条的开发过程.

效果

话不多说,先看效果

支持点击修改进度,拖拽修改进度,当然大家肯定都知道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>)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/72707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端实习第七周周记

前言 第六周没写&#xff0c;是因为第六周的前两天在处理第五周的样本库部分。问题解决一个是嵌套问题&#xff08;因为我用到了递归&#xff09;&#xff0c;还有一个问题在于本机没有问题&#xff0c;打包上线接口404。这个问题我会在这周的总结中说。 第六周第三天才谈好新…

《TCP/IP网络编程》阅读笔记--地址族和数据序列

目录 1--IP地址和端口号 2--地址信息的表示 3--网络字节序与地址变换 4--网络地址的初始化与分配 5--Windows部分代码案例 1--IP地址和端口号 IP 地址分为两类&#xff1a; ① IPv4 表示 4 字节地址族&#xff1b; ② IPv6 表示 16 字节地址族&#xff1b; IPv4 标准的 4 …

Meta 验证徽章:为何大家都想在 FB 和 IG 上获得元验证

随着 Meta&#xff08;前身为 Facebook&#xff09;和 Instagram 统治数字领域&#xff0c;Meta Verified 徽章已成为真实性的终极象征。无论您是公众人物还是品牌&#xff0c;在Facebook和Instagram上获得此徽章都会对您的在线形象产生深远的影响。Facebook验证于2013年首次推…

【文字到语音的论文总结】

1.文字到语音的整个过程 文字到语音的一般整体结构 主要是下面这个流程&#xff0c;每个网络可能会把其中两者或是三者融合在一起来&#xff1b; 长度不同的问题 生成的语音可能和文字的长度并不一样&#xff0c;因此需要解决这个问题 Tactron使用的是交叉注意力的方式解…

Vue + Element UI 前端篇(十):动态加载菜单

Vue Element UI 实现权限管理系统 前端篇&#xff08;十&#xff09;&#xff1a;动态加载菜单 动态加载菜单 之前我们的导航树都是写死在页面里的&#xff0c;而实际应用中是需要从后台服务器获取菜单数据之后动态生成的。 我们在这里就用上一篇准备好的数据格式Mock出模…

vue3项目,点击分页器,列表接口请求两次的问题

接手别人做的项目&#xff0c;出现了一个分页器bug&#xff0c;vue3element plus&#xff0c;记录一下。 点击分页器&#xff0c;却出现了调用两次列表接口的情况&#xff0c;并且第二次请求&#xff0c;分页器的pageNum自动变成1&#xff0c;这样就导致了分页器bug&#xff0…

k8s部署redis 3主3从

k8s部署redis6节点&#xff0c;组成3主3从集群模式 一般来说&#xff0c;redis部署有三种模式。 单实例模式&#xff0c;一般用于测试环境。 哨兵模式 集群模式后两者用于生产部署 哨兵模式 在redis3.0以前&#xff0c;要实现集群一般是借助哨兵sentinel工具来监控master节点…

1.4 空间中的曲线和曲面

空间中的曲线与曲面 知识点1 曲面方程定义 定义1 如果曲面 S 与方程F (x,y,z ) 0 有下述关系&#xff1a; &#xff08;1&#xff09; 曲面 S 上的任意点的坐标都满足此方程 &#xff08;2&#xff09;不在曲面S上的点的坐标不满足此方程 则F&#xff08;x,y,z&#xff0…

Redis 集群

1. 是什么 1.1 定义 由于数据量过大&#xff0c;单个Master复制集难以承担&#xff0c;因此需要对多个复制集进行集群&#xff0c;形成水平扩展每个复制集只负责存储整个数据集 的一部分&#xff0c;这就是Redis的集群&#xff0c;其作用是提供在多个Redis节点间共享数据的程序…

K8S原理架构与实战教程

文章目录 一、背景1.1 物理机时代、虚拟机时代、容器化时代1.2 容器编排的需要 二、K8S架构2.2 Worker节点 三、核心概念3.1 Pod3.2 Deployment3.3 Service3.4 Volume3.5 Namespace 四、K8S安装五、kubectl常用命令六、K8S实战6.1 水平扩容6.2 自动装箱6.2.1 节点污点6.2.2 Pod…

pico学习进程记录已经开发项目

Pico pin脚定义 Pico 运行准备 下载uf2文件 https://pico.org.cn/ &#xff08;注意运行micropython的文件和运行c/c的不一样&#xff09; 装载uf2文件&#xff1a;按住pico的按键&#xff0c;然后通过micro usb连接电脑&#xff08;注意&#xff1a;如果用的线材&#xff0c…

LeetCode刷题笔记【27】:贪心算法专题-5(无重叠区间、划分字母区间、合并区间)

文章目录 前置知识435. 无重叠区间题目描述参考<452. 用最少数量的箭引爆气球>, 间接求解直接求"重叠区间数量" 763.划分字母区间题目描述贪心 - 建立"最后一个当前字母"数组优化marker创建的过程 56. 合并区间题目描述解题思路代码① 如果有重合就合…

打造西南交通感知新范式,闪马智能携手首讯科技落地创新中心

9月4日&#xff0c;2023年中国国际智能产业博览会&#xff08;以下简称“智博会”&#xff09;在重庆拉开帷幕。大会期间&#xff0c;由上海闪马智能科技有限公司&#xff08;以下简称“闪马智能”&#xff09;与重庆首讯科技股份有限公司&#xff08;以下简称“首讯科技”&…

后端/DFT/ATPG/PCB/SignOff设计常用工具/操作/流程及一些文件类型

目录 1.PD/DFT常用工具及流程 1.1 FC和ICC2 1.2 LC (Library compiler) 1.3 PrimeTime 1.4 Redhawk与PA 1.5 Calibre和物理验证PV 1.6 芯片设计流程 2.后端、DFT、ATPG的一些常见文件 2.1 LEF和DEF 2.2 ATPG的CTL和STIL 2.3 BSDL 2.4 IPXCT 3.PCB设计的一些工作和工…

数学建模:模糊综合评价分析

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 数学建模&#xff1a;模糊综合评价分析 文章目录 数学建模&#xff1a;模糊综合评价分析综合评价分析常用评价方法一级模糊综合评价综合代码 多级模糊综合评价总结 综合评价分析 构成综合评价类问题的五个…

【SpringMVC]获取参数的六种方式

目录 1.通过ServletAPI获取 2.通过控制器方法的形参获取 3.RequestParam&#xff1a;将请求参数和控制器方法的形参绑定 4.RequestHeader&#xff1a;将请求头信息与控制器方法的形参的值进行绑定 5. CookieValue&#xff1a;将cookie数据和控制器方法的形参绑定 Cookie&…

gitlab 点击Integrations出现500错误

背景&#xff1a;在新服务器重新搭建了gitlab&#xff0c;并导入原来gitlab的备份&#xff0c;在项目中点击点击Integrations出现500错误。 解决方法&#xff1a;1.进入新服务器&#xff0c;将 /etc/gitlab/gitlab-secrets.json重命名为 /etc/gitlab/gitlab-secrets.json.bak …

yo!这里是进程控制

目录 前言 进程创建 fork()函数 写时拷贝 进程终止 退出场景 退出方法 进程等待 等待原因 等待方法 1.wait函数 2.waitpid函数 等待结果&#xff08;status介绍&#xff09; 进程替换 替换原理 替换函数 进程替换例子 shell简易实现 后记 前言 学习完操作…

Springboot 实践(14)spring config 配置与运用--手动刷新

前文讲解Spring Cloud zuul 实现了SpringbootAction-One和SpringbootAction-two两个项目的路由切换&#xff0c;正确访问到项目中的资源。这两个项目各自拥有一份application.yml项目配置文件&#xff0c;配置文件中有一部分相同的配置参数&#xff0c;如果涉及到修改&#xf…

【前端】CSS-Grid网格布局

目录 一、grid布局是什么二、grid布局的属性三、容器属性1、display①、语句②、属性值 2、grid-template-columns属性、grid-template-rows属性①、定义②、属性值1&#xff09;、固定的列宽和行高2&#xff09;、repeat()函数3&#xff09;、auto-fill关键字4&#xff09;、f…