版本发布

This commit is contained in:
icssoa
2025-07-21 16:47:04 +08:00
parent 1abed7a2e1
commit 6d8193880a
307 changed files with 41718 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
<template>
<cl-text
:color="pt.color"
:pt="{
className: parseClass(['cl-rolling-number', pt.className])
}"
>{{ displayNumber }}</cl-text
>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, computed } from "vue";
import { parseClass, parsePt } from "@/cool";
const props = defineProps({
// 透传样式对象
pt: {
type: Object,
default: () => ({})
},
// 目标数字
value: {
type: Number,
default: 0
},
// 动画持续时间(毫秒)
duration: {
type: Number,
default: 1000
},
// 显示的小数位数
decimals: {
type: Number,
default: 0
}
});
// 定义透传类型仅支持className
type PassThrough = {
className?: string;
color?: string;
};
// 计算pt样式便于组件内使用
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 当前动画显示的数字
const currentNumber = ref<number>(0);
// 当前格式化后显示的字符串
const displayNumber = ref<string>("0");
// requestAnimationFrame动画ID用于取消动画
let animationId: number = 0;
// setTimeout定时器ID用于兼容模式
let timerId: number = 0;
// 动画起始值
let startValue: number = 0;
// 动画目标值
let targetValue: number = 0;
// 动画起始时间戳
let startTime: number = 0;
// 缓动函数ease out cubic动画更自然
function easeOut(t: number): number {
return 1 - Math.pow(1 - t, 3);
}
// 格式化数字,保留指定小数位
function formatNumber(num: number): string {
if (props.decimals == 0) {
return Math.round(num).toString();
}
return num.toFixed(props.decimals);
}
// 动画主循环每帧更新currentNumber和displayNumber
function animate(timestamp: number): void {
// 首帧记录动画起始时间
if (startTime == 0) {
startTime = timestamp;
}
// 计算已用时间
const elapsed = timestamp - startTime;
// 计算动画进度最大为1
const progress = Math.min(elapsed / props.duration, 1);
// 应用缓动函数
const easedProgress = easeOut(progress);
// 计算当前动画值
const currentValue = startValue + (targetValue - startValue) * easedProgress;
currentNumber.value = currentValue;
displayNumber.value = formatNumber(currentValue);
// 动画未结束,继续下一帧
if (progress < 1) {
animationId = requestAnimationFrame((t) => animate(t));
} else {
// 动画结束,确保显示最终值
currentNumber.value = targetValue;
displayNumber.value = formatNumber(targetValue);
animationId = 0;
}
}
/**
* 基于setTimeout的兼容动画实现
* 适用于不支持requestAnimationFrame的环境
*/
function animateWithTimeout(): void {
const frameRate = 60; // 60fps
const frameDuration = 1000 / frameRate; // 每帧时间间隔
const totalFrames = Math.ceil(props.duration / frameDuration); // 总帧数
let currentFrame = 0;
function loop(): void {
currentFrame++;
// 计算动画进度最大为1
const progress = Math.min(currentFrame / totalFrames, 1);
// 应用缓动函数
const easedProgress = easeOut(progress);
// 计算当前动画值
const currentValue = startValue + (targetValue - startValue) * easedProgress;
currentNumber.value = currentValue;
displayNumber.value = formatNumber(currentValue);
// 动画未结束,继续下一帧
if (progress < 1) {
// @ts-ignore
timerId = setTimeout(() => loop(), frameDuration);
} else {
// 动画结束,确保显示最终值
currentNumber.value = targetValue;
displayNumber.value = formatNumber(targetValue);
timerId = 0;
}
}
loop();
}
// 外部调用,停止动画
function stop() {
if (animationId != 0) {
cancelAnimationFrame(animationId);
animationId = 0;
}
if (timerId != 0) {
clearTimeout(timerId);
timerId = 0;
}
}
/**
* 启动动画从from到to
* @param from 起始值
* @param to 目标值
*/
function startAnimation(from: number, to: number): void {
// 若有未完成动画,先取消
stop();
startValue = from;
targetValue = to;
startTime = 0;
// #ifdef MP
animateWithTimeout();
// #endif
// #ifndef MP
// 启动动画
animationId = requestAnimationFrame(animate);
// #endif
}
// 外部调用,重头开始动画
function start() {
startAnimation(0, props.value);
}
// 监听value变化自动启动动画
watch(
computed(() => props.value),
(newValue: number, oldValue: number) => {
// 只有值变化时才启动动画
if (newValue != oldValue) {
startAnimation(currentNumber.value, newValue);
}
},
{ immediate: false }
);
// 组件挂载时,初始化动画
onMounted(() => {
start();
});
defineExpose({
start,
stop
});
</script>
<style lang="scss" scoped>
.cl-rolling-number {
}
</style>

View File

@@ -0,0 +1,21 @@
export type ClRollingNumberProps = {
/**
* 绑定值
*/
modelValue: number
/**
* 动画持续时间,单位毫秒
* @default 1000
*/
duration?: number
/**
* 保留小数位数
* @default 0
*/
decimals?: number
/**
* 是否自动开始动画
* @default true
*/
autoStart?: boolean
}