自造简易版音频进度条

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

效果

话不多说,先看效果

支持点击修改进度,拖拽修改进度,当然大家肯定都知道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.rhkb.cn/news/129917.html

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

相关文章

C#学习 - 方法的定义、调用、调试

方法 方法&#xff08;Method&#xff09;是由C/C中的函数&#xff08;Function&#xff09;发展而来的 //C语言 #include <stdio.h> int Add(int x, int y) {return x y; }//函数 int main(void) {int a 4;int b 2;int c Add(a, b);printf("%d %d %d\n&quo…

初识MyBatis(一)基于配置文件下的一些增删改查

MyBatis可以使用简单的XML或注解用于配置和原始映射&#xff0c;将接口和Java的POJO&#xff08;Plain Old Java Objects&#xff0c;普通的Java对象&#xff09;映射成数据库中的记录 MyBatis 是一个 半自动的ORM&#xff08;Object Relation Mapping&#xff09;框架 创建好m…

《计算机网络安全》DNS与它的具体作用解析

初步了解DNS 个人介绍DNS定义DNS作用DNS的生活加速作用科普 个人介绍 &#x1f338;一名大四备考考研学子&#xff0c;喜欢前端&#xff0c;还有Android和JAVA开发 &#x1f332;爱看书和打游戏还有唱歌 &#x1f352;热爱编程和读古今中外名著 &#x1f33a;座右铭&#xff1…

el-form表单中不同数据类型对应的时间格式化和校验规则

1. 在表单中, 当选择不同的数据类型时, 需要在下面选择时间时和数据类型对应上, 通过监听数据类型的变化, 给时间做格式化, 2. 但是当不按顺序选择数据类型后, 再选时间可能会报错, 所以需要在dom更新后, 再清空表单. 3. 校验规则, 结束时间需要大于开始时间, 但是不能选当前的…

Jmeter引入外部jar包以满足加密数据的Post请求

目录 一、把项目打成jar包 1、创建一个Maven项目&#xff0c;并保证可以正常运行。 2、把工具类放置项目中&#xff0c;确保无报错且能够正常使用。 3、打包 4、验证 jar包是否有效 5、你想打多个工具类的包 二、在jmeter中使用 1、把jar包放到jmeter仓库下&#xff0c;…

Mixin从理论到实践

mixin从理论到实践 mixin从理论到实践一、什么是mixin二、使用mixin三、mixin的合并策略四、mixin辨析五、个人实践 mixin从理论到实践 一、什么是mixin mixin混入 — Vue.js (vuejs.org) 官方解释&#xff1a; 混入 (mixin) 提供了一种非常灵活的方式&#xff0c;来分发 Vue …

安全生产:CVE-2020-11022/CVE-2020-11023漏洞解析

文章目录 一、前言二、漏洞原理三、修复方案3.1 升级jQuery3.2 1.x 升级至 3.x 需要考虑的问题3.2.1 table表格元素自动添加tbody3.2.2 方法变更 3.3 jquery migrate是什么 四、拓展阅读 一、前言 代码安全扫描阶段&#xff0c;前端资源审计发现jQuery版本过低导致生产系统存在…

96. 不同的二叉搜索树

class Solution { public:int numTrees(int n) {if (n0) {return 1;}vector<int> dp(n1, 0);dp[0] 1;dp[1] 0;for (int i 1; i < n; i) {for (int j 0; j < i; j) {dp[i] dp[j] * dp[i - 1 - j];}}return dp[n];} };

grpc多语言通信之GO和DART

都是一个吗生的,找下例子 上一篇文章说到go实现的grpc方法已经实现了一个grpc的server端, 注意: 这两个项目的.proto文件应当是完全一致的,只是方法用各自的语言实现罢了 报错了: Caught error: gRPC Error (code: 12, codeName: UNIMPLEMENTED, message: grpc: Decompresso…

网络传输方式

1. 单播 1.1. 定义 单播是指一种向单个目标地址传送数据的方式&#xff0c;即单独的一对一通讯方式。 1.2. 可使用协议 UDP、TCP等协议 1.3. 常见的场景 发送电子邮件传输文件 2. 广播 2.1. 定义 一种向本地网络中所有设备发送数据的方式。 2.2. 常见的场景 电视和电…

SpringBoot+Vue 整合websocket实现简单聊天窗口

效果图 1 输入临时名字充当账号使用 2 进入聊天窗口 3 发送消息 &#xff08;复制一个页面&#xff0c;输入其他名字&#xff0c;方便展示效果&#xff09; 4 其他窗口效果 代码实现 后端SpringBoot项目&#xff0c;自行创建 pom依赖 <dependency><groupId…

有效回文字符串(Valid palindrome)

题目描述 思路分析 代码实践 java: public class Solutation1 {//定义一个方法&#xff0c;判断是否是有效数字或者字母private static boolean isValid(char c) {//如果不是字母或者数字&#xff0c;那就返回一个flase//这里调用了Character类里面的方法return Character.i…

Python - PyQt6、QDesigner、pyuic5-tool 安装使用

Python 开发可视化界面可以使用原生的 tkinter&#xff0c;但是原生框架使用起来颇为不方便&#xff0c;所以最流行的还是QT UI框架&#xff0c;QT是使用C语言开发&#xff0c;Python 想使用需要对其进行封装&#xff0c;所以就出现了PyQt框架&#xff0c;这个框架使用极其方便…

element的el-select给下拉框添加背景

第一步 :popper-append-to-body"false" <el-selectv-model"value"placeholder"请选择":popper-append-to-body"false"><el-optionv-for"item in options":key"item.value":label"item.label&quo…

【算法专题突破】双指针 - 最大连续1的个数 III(11)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;1004. 最大连续1的个数 III - 力扣&#xff08;Leetcode&#xff09; 这道题不难理解&#xff0c;其实就是求出最长的连续是1的子数组&#xff0c; 但是&#xff0c;他支…

Spring Reactive:响应式编程与WebFlux的深度探索

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

2023-简单点-什么是protobuf?

protobuf mother: 谷歌 作用 序列化 人话&#xff1a; 存储数据的一种结构 优势在&#xff1f; 类型安全 易用性好 序列化/反序列性能好 兼容性好 不仅可以定义结构体&#xff0c;还可以定义rpc服务接口 劣势在&#xff1f; 可读性较差&#xff1a;没有schema的情况下&a…

Linux--进程--进程-父进程退出

1.进程退出函数 进程退出分为正常退出&异常退出 正常退出&#xff1a; 1、main函数调用return 2、进程调用exit(),标准c库 3、进程调用_exit()或者_Exit(),属于系统调用 补充 1、进程最后一个线程返回 2、最后一个线程调用pthread_exit 异常退出&#xff1a; 1、调用abo…

【开箱即用】开发了一个基于环信IM聊天室的Vue3插件,从而快速实现仿直播间聊天窗功能

前言 由于看到有部分的需求为在页面层&#xff0c;快速的引入一个包&#xff0c;并且以简单的配置&#xff0c;就可以快速实现一个聊天窗口&#xff0c;因此尝试以 Vue3 插件的形式开发一个轻量的聊天窗口。 这次简单分享一下此插件的实现思路&#xff0c;以及实现过程&#xf…

OpenCV(二十二):均值滤波、方框滤波和高斯滤波

目录 1.均值滤波 2.方框滤波 3.高斯滤波 1.均值滤波 OpenCV中的均值滤波&#xff08;Mean Filter&#xff09;是一种简单的滤波技术&#xff0c;用于平滑图像并减少噪声。它的原理非常简单&#xff1a;对于每个像素&#xff0c;将其与其周围邻域内像素的平均值作为新的像素值…