当前位置: 首页 > news >正文

[React] 手动实现CountTo 数字滚动效果

这个CountTo组件npmjs里当然有大把的依赖存在,不过今天我们不需要借助任何三方依赖,造个轮子来手动实现这个组件。

通过研究其他count to插件我们可以发现,数字滚动效果主要依赖于requestAnimationFrame 通过js帧来让数字动起来,数字变化则是依赖于内部的easingFn函数来每次计算。

首先声明组件props类型

interface Props {/*** 动画开始的值*/start?: number;/*** 目标值*/end: number;/*** 持续时间*/duration?: number;/*** 是否自动播放*/autoPlay?: boolean;/*** 精度*/decimals?: number;/*** 小数点*/decimal?: string;/*** 千分位分隔符*/separator?: string;/*** 数字前 额外信息*/prefix?: string;/*** 数字后 额外信息*/suffix?: string;/*** 是否使用变速函数*/useEasing?: boolean;/*** 计算函数*/easingFn?: (t: number, b: number, c: number, d: number) => number;/*** 动画开始后传给父组件的回调*/started?: () => void;/*** 动画结束传递给父组件的回调*/ended?: () => void;
}

除了end 是必要的,其他都是可选参数。
所以我们需要给组件默认值,防止没有参数时会报错。
同时写几个工具函数便于后面使用

export default function Index({end,start = 0,duration = 3000,autoPlay = true,decimals = 0,decimal = '.',separator = ',',prefix = '',suffix = '',useEasing = true,easingFn = (t, b, c, d) => (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b,started = () => {},ended = () => {},
}: Props) {const isNumber = (val: string) => {return !isNaN(parseFloat(val));};// 格式化数据,返回想要展示的数据格式const formatNumber = (n: number) => {let val = '';if (n % 1 !== 0) val = n.toFixed(decimals);const x = val.split('.');let x1 = x[0];const x2 = x.length > 1 ? decimal + x[1] : '';const rgx = /(\d+)(\d{3})/;if (separator && !isNumber(separator)) {while (rgx.test(x1)) {x1 = x1.replace(rgx, '$1' + separator + '$2');}}return prefix + x1 + x2 + suffix;};...
}

初始化数据

  const [state, setState] = useState<State>({start: 0,paused: false,duration,});const startTime = useRef(0);const _timestamp = useRef(0);const remaining = useRef(0);const printVal = useRef(0);const rAf = useRef(0);const endRef = useRef(end);const endedCallback = useRef(ended);const [displayValue, setValue] = useState(formatNumber(start));// 定义一个计算属性,当开始数字大于结束数字时返回trueconst stopCount = useMemo(() => start > end, [start, end]);

动画的关键函数

  const count = (timestamp: number) => {if (!startTime.current) startTime.current = timestamp;_timestamp.current = timestamp;const progress = timestamp - startTime.current;remaining.current = state.duration - progress;// 是否使用速度变化曲线if (useEasing) {if (stopCount) {printVal.current = state.start - easingFn(progress, 0, state.start - end, state.duration);} else {printVal.current = easingFn(progress, state.start, end - state.start, state.duration);}} else {if (stopCount) {printVal.current = state.start - (state.start - endRef.current) * (progress / state.duration);} else {printVal.current = state.start + (endRef.current - state.start) * (progress / state.duration);}}if (stopCount) {printVal.current = printVal.current < endRef.current ? endRef.current : printVal.current;} else {printVal.current = printVal.current > endRef.current ? endRef.current : printVal.current;}setValue(formatNumber(printVal.current));if (progress < state.duration) {rAf.current = requestAnimationFrame(count);} else {endedCallback.current?.();}};

执行动画的函数

  const startCount = () => {setState({ ...state, start, duration, paused: false });rAf.current = requestAnimationFrame(count);startTime.current = 0;};

挂载时监听是否有autoPlay 来选择是否开始动画,同时组件销毁后清除requestAnimationFrame动画;

  useEffect(() => {if (autoPlay) {startCount();started?.();}return () => {cancelAnimationFrame(rAf.current);};}, []);

一些相关依赖的监听及处理

useEffect(() => {if (!autoPlay) {cancelAnimationFrame(rAf.current);setState({ ...state, paused: true });}}, [autoPlay]);useEffect(() => {if (!state.paused) {cancelAnimationFrame(rAf.current);startCount();}}, [start]);

最后返回displayValue就可以了;

好了 我要开启五一假期了!
最后附上完整代码 –

'use client';import { useEffect, useMemo, useRef, useState } from 'react';interface Props {/*** 动画开始的值*/start?: number;/*** 目标值*/end: number;/*** 持续时间*/duration?: number;/*** 是否自动播放*/autoPlay?: boolean;/*** 精度*/decimals?: number;/*** 小数点*/decimal?: string;/*** 千分位分隔符*/separator?: string;/*** 数字前 额外信息*/prefix?: string;/*** 数字后 额外信息*/suffix?: string;/*** 是否使用变速函数*/useEasing?: boolean;/*** 计算函数*/easingFn?: (t: number, b: number, c: number, d: number) => number;/*** 动画开始后传给父组件的回调*/started?: () => void;/*** 动画结束传递给父组件的回调*/ended?: () => void;
}
interface State {start: number;paused: boolean;duration: number;
}
export default function Index({end,start = 0,duration = 3000,autoPlay = true,decimals = 0,decimal = '.',separator = ',',prefix = '',suffix = '',useEasing = true,easingFn = (t, b, c, d) => (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b,started = () => {},ended = () => {},
}: Props) {const isNumber = (val: string) => {return !isNaN(parseFloat(val));};// 格式化数据,返回想要展示的数据格式const formatNumber = (n: number) => {let val = '';if (n % 1 !== 0) val = n.toFixed(decimals);const x = val.split('.');let x1 = x[0];const x2 = x.length > 1 ? decimal + x[1] : '';const rgx = /(\d+)(\d{3})/;if (separator && !isNumber(separator)) {while (rgx.test(x1)) {x1 = x1.replace(rgx, '$1' + separator + '$2');}}return prefix + x1 + x2 + suffix;};const [state, setState] = useState<State>({start: 0,paused: false,duration,});const startTime = useRef(0);const _timestamp = useRef(0);const remaining = useRef(0);const printVal = useRef(0);const rAf = useRef(0);const endRef = useRef(end);const endedCallback = useRef(ended);const [displayValue, setValue] = useState(formatNumber(start));// 定义一个计算属性,当开始数字大于结束数字时返回trueconst stopCount = useMemo(() => start > end, [start, end]);const count = (timestamp: number) => {if (!startTime.current) startTime.current = timestamp;_timestamp.current = timestamp;const progress = timestamp - startTime.current;remaining.current = state.duration - progress;// 是否使用速度变化曲线if (useEasing) {if (stopCount) {printVal.current = state.start - easingFn(progress, 0, state.start - end, state.duration);} else {printVal.current = easingFn(progress, state.start, end - state.start, state.duration);}} else {if (stopCount) {printVal.current = state.start - (state.start - endRef.current) * (progress / state.duration);} else {printVal.current = state.start + (endRef.current - state.start) * (progress / state.duration);}}if (stopCount) {printVal.current = printVal.current < endRef.current ? endRef.current : printVal.current;} else {printVal.current = printVal.current > endRef.current ? endRef.current : printVal.current;}setValue(formatNumber(printVal.current));if (progress < state.duration) {rAf.current = requestAnimationFrame(count);} else {endedCallback.current?.();}};const startCount = () => {setState({ ...state, start, duration, paused: false });rAf.current = requestAnimationFrame(count);startTime.current = 0;};useEffect(() => {if (!autoPlay) {cancelAnimationFrame(rAf.current);setState({ ...state, paused: true });}}, [autoPlay]);useEffect(() => {if (!state.paused) {cancelAnimationFrame(rAf.current);startCount();}}, [start]);useEffect(() => {if (autoPlay) {startCount();started?.();}return () => {cancelAnimationFrame(rAf.current);};}, []);return displayValue;
}

相关文章:

[React] 手动实现CountTo 数字滚动效果

这个CountTo组件npmjs里当然有大把的依赖存在&#xff0c;不过今天我们不需要借助任何三方依赖&#xff0c;造个轮子来手动实现这个组件。 通过研究其他count to插件我们可以发现&#xff0c;数字滚动效果主要依赖于requestAnimationFrame 通过js帧来让数字动起来&#xff0c;…...

9.Admin后台系统

9. Admin后台系统 Admin后台系统也称为网站后台管理系统, 主要对网站的信息进行管理, 如文字, 图片, 影音和其他日常使用的文件的发布, 更新, 删除等操作, 也包括功能信息的统计和管理, 如用户信息, 订单信息和访客信息等. 简单来说, 它是对网站数据库和文件进行快速操作和管…...

redis之集群

一.redis主从模式和redis集群模式的区别 redis主从模式:所有节点上的数据一致&#xff0c;但是key过多会影响性能 redis集群模式:将数据分散到多个redis节点&#xff0c;数据分片存储&#xff0c;提高了redis的吞吐量 二.redis cluster集群的特点 数据分片 多个存储入…...

#9松桑前端后花园周刊-React19beta、TS5.5beta、Node22.1.0、const滥用、jsDelivr、douyin-vue

行业动态 Mozilla 提供 Firefox 的 ARM64 Linux二进制文件 此前一直由发行版开发者或其他第三方提供&#xff0c;目前Mozilla提供了nightly版本&#xff0c;正式版仍需要全面测试后再推出。 发布 React 19 Beta 此测试版用于为 React 19 做准备的库。React团队概述React 19…...

STM32中UART通信的完整C语言代码范例

UART&#xff08;通用异步收发器&#xff09;是STM32微控制器中常用的外设&#xff0c;用于与其他设备进行串行通信。本文将提供一个完整的C语言代码范例&#xff0c;演示如何在STM32中使用UART进行数据传输。 硬件配置 在开始编写代码之前&#xff0c;需要确保以下硬件配置&…...

【ITK统计】第一期 分类器

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享ITK中的分类器及其使用情况,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 在统计分…...

51单片机两个中断及中断嵌套

文章目录 前言一、中断嵌套是什么&#xff1f;二、两个同级别中断2.1 中断运行关系2.2 测试程序 三、两个不同级别中断实现中断嵌套3.1 中断运行关系3.2 测试程序 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 课程需要&#xff1a; 提示&#x…...

VUE 监视数据原理

1、如何监测对象中的数据&#xff1f; 通过setter实现监视&#xff0c;且要在new vue时就传入监测的数据 &#xff08;1&#xff09;对象中后加的属性&#xff0c;vue默认不做响应式处理 &#xff08;2&#xff09;如需给后添加的属性做响应式&#xff0c;请使用如下API&#x…...

Thinkphp使用dd()函数

用过Laravel框架的同学都知道在调试代码的时候使用dd()函数打印变量非常方便&#xff0c;在ThinkPHP6及以上的版本框架中也默认加上了这个函数。但是在ThinkPHP5或更低版本的框架中&#xff0c;dd 并不是一个内置的方法&#xff0c;不过我们可以手动添加这个函数&#xff0c;步…...

Git使用指北

目录 创建一个Git仓库本地仓库添加文件文件提交到本地仓库缓冲区添加远程仓库地址本地仓库推送到远程仓库创建新的分支拉取代码同步删除缓冲区的文件&#xff0c;远程仓库的文件.gitignore文件 创建一个Git仓库 Git仓库分为远程和本地两种&#xff0c;远程仓库如Githu上创建的…...

STM32G030F6P6TR 芯片TSSOP20 MCU单片机微控制器芯片

STM32G030F6P6TR 在物联网&#xff08;IoT&#xff09;设备中的典型应用案例包括但不限于以下几个方面&#xff1a; 1. 环境监测系统&#xff1a; 使用传感器来监测温度、湿度、气压等环境因素&#xff0c;并通过无线通信模块将数据发送到中央服务器或云端平台进行分析和监控。…...

零基础入门学习Python第二阶01生成式(推导式),数据结构

Python语言进阶 重要知识点 生成式&#xff08;推导式&#xff09;的用法 prices {AAPL: 191.88,GOOG: 1186.96,IBM: 149.24,ORCL: 48.44,ACN: 166.89,FB: 208.09,SYMC: 21.29}# 用股票价格大于100元的股票构造一个新的字典prices2 {key: value for key, value in prices.i…...

Java面试题:多线程3

CAS Compare and Swap(比较再交换) 体现了一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性. 线程A和线程B对主内存中的变量c同时进行修改 在线程A中存在预期值a,修改后的更新值a1 在线程B中存在预期值b,修改后的更新值b1 当且仅当预期值和主内存中的变量值相等…...

【QEMU系统分析之实例篇(十八)】

系列文章目录 第十八章 QEMU系统仿真的机器创建分析实例 文章目录 系列文章目录第十八章 QEMU系统仿真的机器创建分析实例 前言一、QEMU是什么&#xff1f;二、QEMU系统仿真的机器创建分析实例1.系统仿真的命令行参数2.创建后期后端驱动qemu_create_late_backends()qtest_serv…...

pyside6的调色板QPalette的简单应用

使用调色板需要先导入:from PySide6.QtGui import QPalette 调色板QPalette的源代码&#xff1a; class QPalette(Shiboken.Object):class ColorGroup(enum.Enum):Active : QPalette.ColorGroup ... # 0x0Normal : QPalette.ColorGrou…...

苍穹外卖项目

Day01 收获 补习git Git学习之路-CSDN博客 nginx 作用&#xff1a;反向代理和负载均衡 swagger Swagger 与 Yapi Swagger&#xff1a; 可以自动的帮助开发人员生成接口文档&#xff0c;并对接口进行测试。 项目接口文档网址&#xff1a; http://localhost:8080/doc.html Da…...

error: Execution was interrupted, reason: signal SIGABRT

c json解析时&#xff0c; error: Execution was interrupted, reason: signal SIGABRT const Json::Value points root["shapes"]; if (points.isArray()) { for (unsigned int i 0; i < points.size(); i) { std::cout << " - [" <<…...

HarmaonyOS鸿蒙应用科普课

一、什么是鸿蒙OS&#xff1f; 1.概念&#xff1a; 先给大家讲讲今天讲课的主题&#xff0c;鸿蒙OS是什么&#xff1f;鸿蒙系统大家都知道&#xff0c;就是一个操作系统&#xff0c;我们未来是为的成为鸿蒙程序员。所以我们不要将鸿蒙os完全等同于手机操作系统&#xff0c;太…...

数码管的显示

静态数码管显示 数码管有两种一种的负电压促发,一种是正电压促发,上图是单数码管的引脚 上图是数码管模组的引脚,采用了引脚复用技术 咱们这个单片机由8个单数码管,所以要用上38译码器,如下图 74138使能端,单片机上电直接就默认接通了 74HC245的作用是稳定输入输出,数据缓冲作…...

关于海康相机和镜头参数的记录

对比MV-CS020-10UC和大家用的最多的MV-CS016-10UC 其实前者适合雷达站使用&#xff0c;后者适合自瞄使用 一&#xff1a;MV-CS020-10UC的参数 二&#xff1a;对比 三&#xff1a;海康镜头选型工具...

AI系统性挑战:从可解释性到思想体系构建的深度剖析

1. 项目概述&#xff1a;从“可解释”到“可理解”的鸿沟最近和几位做AI落地的朋友聊天&#xff0c;大家不约而同地提到了同一个痛点&#xff1a;模型输出看起来头头是道&#xff0c;逻辑清晰&#xff0c;但一旦深究&#xff0c;或者把不同场景下的回答放在一起对比&#xff0c…...

Azure VM SSH被锁死?别慌,用Serial Console这招救活你的服务器(亲测有效)

Azure VM SSH被锁死&#xff1f;Serial Console终极救援指南 当你在Azure VM上误操作sshd_config导致SSH被完全锁死时&#xff0c;那种绝望感就像被困在数字孤岛。常规的RDP、Bastion甚至重建VM都无济于事——直到发现Serial Console这个隐藏的救命通道。作为经历过同样噩梦的运…...

杰理之RX 端 长按软关机后无法唤醒【篇】

问题描述&#xff1a;RX端长按进入软关机后&#xff0c;无法唤醒问题&#xff0c;...

Illustrator脚本合集:设计师的10倍效率提升神器

Illustrator脚本合集&#xff1a;设计师的10倍效率提升神器 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts 你是否厌倦了在Adobe Illustrator中重复繁琐的操作&#xff1f;是否渴望…...

MATLAB程序打包成exe后,发给没有MATLAB的同事/客户怎么用?完整部署指南

MATLAB程序打包成EXE后的完整部署指南&#xff1a;让无MATLAB环境的用户也能顺畅运行 当你花费数周时间在MATLAB中打磨出一个完美的算法工具&#xff0c;最终通过mcc命令将其打包成.exe文件时&#xff0c;那种成就感无与伦比。但现实往往给你当头一棒——同事或客户双击这个exe…...

HoRain云--PHP操作MySQL:三种创建数据库方法详解

&#x1f3ac; HoRain 云小助手&#xff1a;个人主页 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个超棒的服务器购买网站&#xff0c;性价比超高&#xff0c;大内存超划算&#xff01;忍不住分享一下给大家。点击跳转到网站。 目录 ⛳️ 推荐 …...

2026测绘、遥感、地信三大专业就业现状对比

01测绘测绘目前的情况是易就业&#xff0c;劳动密集但薪酬不高&#xff0c;且比较辛苦。招聘网站上测绘的岗位一搜一大把&#xff1a;测绘实习岗位也非常多&#xff1a;但是大部分测绘岗位没有递进式积累。很多岗位会呈现一个类似下面公式的发展路线图&#xff1a;”助理--XX师…...

告别重复图片困扰:AntiDupl.NET 智能图片去重工具完全指南

告别重复图片困扰&#xff1a;AntiDupl.NET 智能图片去重工具完全指南 【免费下载链接】AntiDupl A program to search similar and defect pictures on the disk 项目地址: https://gitcode.com/gh_mirrors/an/AntiDupl 你是否曾因电脑中堆积如山的重复图片而感到困扰&…...

第八部分-企业级实践——37. 容器编排选型

37. 容器编排选型 1. 容器编排概述 容器编排平台负责管理容器的整个生命周期&#xff0c;包括部署、扩缩容、负载均衡、服务发现、滚动更新等。Docker Swarm 和 Kubernetes 是目前主流的容器编排方案。 ┌──────────────────────────────────…...

Umi-CUT:如何用一款免费工具实现批量图片去黑边与智能裁剪

Umi-CUT&#xff1a;如何用一款免费工具实现批量图片去黑边与智能裁剪 【免费下载链接】Umi-CUT 图片批量去黑边/裁剪/压缩工具&#xff0c;带界面。可排除图片边缘的色块干扰&#xff0c;将黑边删除干净。基于 Opencv 。 项目地址: https://gitcode.com/gh_mirrors/um/Umi-C…...