Files
hospital-rescue-app-v2/uni_modules/tmx-ui/core/util/xTween.uts
T
2026-09-24 16:25:22 +08:00

435 lines
12 KiB
Plaintext

/**
* 动画过渡函数
* @version 1.0.0
* @author tmzdy
* @host https://xui.tmui.design
* @description 异常简便,没有繁杂的配置选项,只有快速的开发部署。
*/
import bezier from "./bezier.uts"
import { xTweenStatus, xTweenCallbackFunType, xTweenAnimate, xTweenEventCallFunType, xTweenEventCall } from "../../interface.uts"
import { getUid } from "./xCoreUtil.uts";
type FrameRequestCallback = (time:number)=>void;
export class xTween {
private frameId : null | number = null;
//当前全局动画是否在执行中,只要有一个动画在执行就会是true
isRuning : boolean = false;
//所有动画是否结束
isStoping : boolean = true;
//刷新率,不会立即得出结题,需要有至少执行16ms才可能计算出帖率
frmae = 0
// 舞台是否在执行中
isRendering = false;
private _callListFun : xTweenEventCallFunType[] = []
private _frema_starttimes = 0
private enters : (listAni : xTweenEventCallFunType[], tims : number) => void = (listAni : xTweenEventCallFunType[], tims : number) => { }
private complete : () => void = () => { }
private lastTime = 0
private nextHandle = 0
// 存储回调函数的映射
private callbacks : Map<number, FrameRequestCallback> = new Map();
private easingList = new Map<string, number[]>([
["linear", [0.250, 0.250, 0.750, 0.750]],
["ease", [0.250, 0.100, 0.250, 1.000]],
["easeIn", [0.420, 0.000, 1.000, 1.000]],
["easeOut", [0.000, 0.000, 0.580, 1.000]],
["easeInOut", [0.420, 0.000, 0.580, 1.000]],
["easeInQuad", [0.550, 0.085, 0.680, 0.530]],
["easeOutQuad", [0.250, 0.460, 0.450, 0.940]],
["easeInOutQuad", [0.455, 0.030, 0.515, 0.955]],
["easeInCubic", [0.550, 0.055, 0.675, 0.190]],
["easeOutCubic", [0.215, 0.610, 0.355, 1.000]],
["easeInOutCubic", [0.645, 0.045, 0.355, 1.000]],
["easeInQuart", [0.895, 0.030, 0.685, 0.220]],
["easeOutQuart", [0.165, 0.840, 0.440, 1.000]],
["easeInOutQuart", [0.770, 0.000, 0.175, 1.000]],
["easeInQuint", [0.755, 0.050, 0.855, 0.060]],
["easeOutQuint", [0.230, 1.000, 0.320, 1.000]],
["easeInOutQuint", [0.860, 0.000, 0.070, 1.000]],
["easeInSine", [0.470, 0.000, 0.745, 0.715]],
["easeOutSine", [0.390, 0.575, 0.565, 1.000]],
["easeInOutSine", [0.445, 0.050, 0.550, 0.950]],
["easeInExpo", [0.950, 0.050, 0.795, 0.035]],
["easeOutExpo", [0.190, 1.000, 0.220, 1.000]],
["easeInOutExpo", [1.000, 0.000, 0.000, 1.000]],
["easeInCirc", [0.600, 0.040, 0.980, 0.335]],
["easeOutCirc", [0.075, 0.820, 0.165, 1.000]],
["easeInOutBack", [0.680, -0.550, 0.265, 1.550]],
["tmxEase", [0.42, 0.38, 0.15, 0.93]]
]);
constructor() {
}
private customRequestAnimationFrame(callback : FrameRequestCallback) : number {
const currTime : number = Date.now();
const timeToCall : number = Math.max(0, 16 - (currTime - this.lastTime));
const handle : number = this.nextHandle++;
this.callbacks.set(handle, callback);
const id = setTimeout(() => {
callback(currTime + timeToCall);
}, timeToCall);
this.lastTime = currTime + timeToCall;
return handle;
}
private customCancelAnimationFrame(handle : number) {
if (this.callbacks.has(handle)) {
this.callbacks.delete(handle);
clearTimeout(handle);
}
}
/**
* 启动渲染,并非执行动画,执行动画需要使用play
* 但如果不启动渲染,play中的动画都无法执行.
*/
startRender() : xTween {
this.isRendering = true;
if (this.frameId == null) {
this._run(this)
}
return this;
}
/**
* 销毁渲染.
*/
destroy() {
if (this.frameId != null) {
// #ifdef APP||WEB
cancelAnimationFrame(this.frameId!)
// #endif
// #ifdef MP
this.customCancelAnimationFrame(this.frameId!)
// #endif
}
this.frameId = null;
this.isRendering = false;
}
getFrame() : number {
return this.frmae
}
/**
* 所有事件动画执行完毕
*/
setComplete(call : () => void) : xTween {
this.complete = call
return this;
}
/**
* 设置帖动画,舞台渲染执行帖函数
* 它不是animate动画,没有进度,只会不停的执行.直接舞台渲染被注销.
*/
setEnter(call : (listAni : xTweenEventCallFunType[], tims : number) => void) : xTween {
this.enters = call
return this;
}
/**
* 添加一个动画,可以重复加达到多个动画联合执行的效果
* 并且 每一个动画都是独立存在,这样可以细微的控制每个动画
* 来达到联合处理的效果.
*/
addAnimate(opts : xTweenAnimate) : string {
let uid = getUid();
let call : xTweenEventCall = (item : xTweenEventCallFunType) => { };
this._callListFun.push({
id: uid,
ease: this._getEasing(opts?.ease ?? 'linear'),
duration: opts.duration,
status: 1,
progress: 0,
oldProgeress: 0,
startTime: 0,
autoRemove: opts?.autoRemove != null ? (opts!.autoRemove as boolean) : true,
complete: opts?.complete != null ? (opts!.complete!) : call,
enter: opts?.enter != null ? (opts!.enter!) : call,
start: opts?.start != null ? (opts!.start!) : call,
pause: opts?.pause != null ? (opts!.pause!) : call,
loop: opts?.loop != null ? (opts!.loop!) : 1,
tyty: opts?.tyty != null ? (opts!.tyty!) : false,
step: opts?.step != null ? (opts!.step!) : 1,
_finishLoop: 0,
reverse: false
} as xTweenEventCallFunType)
return uid;
}
/**
* 删除一个事件.
* @param {string} uid 如果为null表示删除所有.
*/
removeAnimate(uid : string | null = null) : xTween {
if (uid == null) {
this._callListFun = [] as xTweenEventCallFunType[]
} else {
let index = this._getCallIndex(uid as string);
if (index > -1) {
this._callListFun.splice(index, 1)
}
}
return this;
}
/**
* 播放动画。
* @param {string|null} uid 动画id,如果不填写或者为null表示播放所有动画
*/
play(uid : string | null = null) {
if (uid != null && uid != '') {
let index = this._getCallIndex(uid! as string)
if (index > -1) {
let item = this._callListFun[index]
if (item.status == 1) {
this._by_run_set_status(item, 1)
this._by_run_item_callFun(item, 0)
}
// 马上要执行运行
this._by_run_set_status(item, 4)
}
} else {
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
if (item.status == 1) {
this._by_run_set_status(item, 1)
this._by_run_item_callFun(item, 0)
}
this._by_run_set_status(item, 4)
}
}
this._setGlobaleStatus(1)
}
getAnimationListLen() : number {
return this._callListFun.length;
}
/**
* 暂停动画。
* @param {string|null} uid 动画id,如果不填写或者为null表示暂停所有动画
*/
pause(uid : string | null = null) : xTween {
if (uid != null && uid != '') {
let index = this._getCallIndex(uid! as string)
if (index > -1) {
let item = this._callListFun[index]
this._by_run_set_status(item, this._isLoopPauseing(item) ? 6 : 3)
this._by_run_item_callFun(item, item.progress)
}
} else {
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
this._by_run_set_status(item, this._isLoopPauseing(item) ? 6 : 3)
this._by_run_item_callFun(item, item.progress)
}
this._setGlobaleStatus(2)
}
return this;
}
/**
* 结束动画。
* @param {string|null} uid 动画id,如果不填写或者为null表示结束所有动画
*/
stop(uid : string | null = null) : xTween {
if (uid != null && uid != '') {
let index = this._getCallIndex(uid! as string)
if (index > -1) {
let item = this._callListFun[index]
this._by_run_set_status(item, 2)
this._by_run_item_callFun(item, 1)
}
} else {
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
this._by_run_set_status(item, 2)
this._by_run_item_callFun(item, 1)
}
this._callListFun = this._callListFun.filter((el : xTweenEventCallFunType) : boolean => !el.autoRemove)
this.enters(this._callListFun, 0)
}
return this;
}
private _getEasing(args : any) : xTweenCallbackFunType | null {
let fun : xTweenCallbackFunType | null = (x : number) : number => x;
if (typeof args == 'string') {
let animateNumber = this.easingList.get(args as string)
if (animateNumber != null) {
let ease = animateNumber! as number[]
fun = bezier(ease[0], ease[1], ease[2], ease[3])
}
} else if (Array.isArray(args)) {
let argsar = args as number[]
if (argsar.length == 4) {
fun = bezier(argsar[0], argsar[1], argsar[2], argsar[3])
}
} else {
let animateNumber = this.easingList.get('linear')
if (animateNumber != null) {
let ease = animateNumber! as number[]
fun = bezier(ease[0], ease[1], ease[2], ease[3])
}
}
return fun;
}
private _getCallIndex(uid : string) : number {
let index = -1;
for (let i = 0; i < this._callListFun.length; i++) {
let item = this._callListFun[i]
if (item.id == uid) {
index = i;
break;
}
}
return index;
}
private _isLoopPauseing(item : xTweenEventCallFunType) : boolean {
if (item.loop == -1) return true;
if (item._finishLoop < item.loop) return true;
return false;
}
private _by_run_set_status(item : xTweenEventCallFunType, status : xTweenStatus) {
if (status == 1) {
item.progress = 0
item.startTime = 0
item._finishLoop = 0
} else if (status == 2) {
item.progress = 1;
item.oldProgeress = 0;
item.startTime = 0
item._finishLoop = item.loop
} else if (status == 3) {
item.startTime = 0
item.oldProgeress = item.progress
} else if (status == 5) {
item.progress = 0
item.oldProgeress = 0
item.startTime = 0
} else if (status == 6) {
item.oldProgeress = item.progress
item.startTime = 0
}
item.status = status;
}
private _by_run_item_callFun(item : xTweenEventCallFunType, progress : number) {
if (item.status == 1) {
item.start(item)
}
if (item.status == 2) {
item.enter(item)
item.complete(item)
if (item.autoRemove) {
let index = this._getCallIndex(item.id)
if (index > -1) {
this._callListFun.splice(index, 1)
}
}
}
if (item.status == 3) {
item.pause(item)
}
if (item.status == 4 || item.status == 5) {
item.enter(item)
}
}
/**
* 1:执行中
* 2:未在执行动画。
*/
private _setGlobaleStatus(type : number) {
if (type == 1) {
this.isRuning = true;
this.isStoping = false
} else if (type == 2) {
this.isRuning = false;
this.isStoping = true
}
}
private _run(_this : xTween) {
if (!_this.isRendering) {
return;
}
function actions(times:number){
_this.enters(_this._callListFun, times)
if (_this._frema_starttimes == 0) {
_this._frema_starttimes = times
}
let isAllCompelted = true;
for (let i = 0; i < _this._callListFun.length; i++) {
let item = _this._callListFun[i]
let isFinishStatus = 1
if (item.status == 4 || item.status == 5) {
if (item.startTime == 0) {
item.startTime = times
}
// 计算当前进度
if (_this.frmae > 0) {
let progress = Math.min((times - (item.startTime)) / item.duration + item.oldProgeress, 1);
let eas = item.ease!
item.progress = eas(progress)
if (progress == isFinishStatus) {
item._finishLoop += 1
if (item.loop > 0) {
if (item.loop == item._finishLoop) {
_this._by_run_set_status(item, 2)
_this._by_run_item_callFun(item, isFinishStatus)
} else {
_this._by_run_set_status(item, 5)
}
} else if (item.loop == -1) {
_this._by_run_set_status(item, 5)
}
if (item.tyty) {
item.reverse = !item.reverse
}
}
_this._by_run_item_callFun(item, item.progress)
}
}
if (item.progress != 2) {
isAllCompelted = false;
}
}
if (isAllCompelted && _this.isRuning) {
_this.complete()
_this._setGlobaleStatus(2)
}
_this.frmae = Math.ceil(1000 / (times - _this._frema_starttimes))
_this._frema_starttimes = times
_this._run(_this)
}
// #ifdef APP||WEB
_this.frameId = requestAnimationFrame((times : number) => {
actions(times)
})
// #endif
// #ifdef MP
_this.frameId = this.customRequestAnimationFrame((times : number) => {
actions(times)
})
// #endif
}
}