435 lines
12 KiB
Plaintext
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
|
|
}
|
|
|
|
} |