Commit f7723ae1 authored by wjf's avatar wjf

2.0.28

parent e1714ace
declare namespace FYGE{export const VERSION = "2.0.27";
declare namespace FYGE{export const VERSION = "2.0.28";
export const osType: "ios" | "android" | "pc";
......@@ -6167,7 +6167,7 @@ export class Container extends DisplayObject {
*/
protected _renderCanvas(renderer: CanvasRenderer): void;
/**
* 更新方法,注意别覆盖,子类可继承修改
* 节点每帧更新方法,注意别覆盖,否则每帧监听将失效,子类可继承修改
*/
update(): void;
/**
......@@ -8614,6 +8614,10 @@ export class AnimationClip extends EventDispatcher {
* 记录时间
*/
protected curTime: number;
/**
* 当前时间
*/
get currentTime(): number;
private _endMark;
/**
* 需要挂在循环里的方法,传时间间隔
......@@ -8661,62 +8665,250 @@ export class AnimationClip extends EventDispatcher {
resetState(): void;
}
export const getBezierEasing: (a: any, b: any, c: any, d: any, nm?: any) => any;
export function buildBezierProps(pt1: any, pt2: any, pt3: any, pt4: any, startIndex: any, endIndex: any, points: any, fnc: any, limit?: number): void;
export function buildBezierProp(pt1: any, pt2: any, pt3: any, pt4: any, startIndex: any, endIndex: any, time: any, fnc: any): any[];
export class Lottie extends Container {
export abstract class AnimationNode extends Container {
/**
* 原始数据,尽量只获取,不修改
*/
private rawData;
protected rawData: any;
/**
* 总时间,秒计
*/
get totalTime(): number;
abstract get totalTime(): number;
/**
* 总帧数
*/
get totalFrames(): number;
abstract get totalFrames(): number;
/**
* 动画显示宽度
*/
get videoWidth(): number;
abstract get videoWidth(): number;
/**
* 动画显示高度
*/
get videoHeight(): number;
private _fps;
abstract get videoHeight(): number;
/**
* 帧率
*/
protected _fps: number;
/**
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps(): number;
/**
* 设置每秒刷新率,可能值30,60等等
* 子类重写get方法时必须重写set方法,否则执行不到父类的set方法
*/
set fps(v: number);
private lottieContainer;
/**
* 用于控制动画,这里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
* 用于控制动画,
* Lottie和SvgaAni里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
*/
animationClip: AnimationClip;
constructor(data: LottieData);
init(data: LottieData): void;
protected animationClip: AnimationClip;
constructor(data: any);
/**
* 抽象的初始化类
* @param data
*/
abstract init(data: any): void;
private lastTime;
/**
* 更新方法,直接写了,后续继承的Lottie和SvgaAni基本不用改,有需要自行重写
*/
update(): void;
/**
* 从头播放,需要自定义的用this.animationClip
* @param loop 播放测试,默认0,一直循环播放
* @param callback 回调
* 播放loop次后回调
* @param loop 播放次数
* @param callback 播放次数完后回调
*/
play(loop?: number, callback?: () => void): void;
/**
* 开始播放
* @param isFront 是否正向,默认是
*/
play(isFront?: boolean): void;
/**
* 停止,需要自定义的用this.animationClip
* @param isReset 是否重置,默认false,不重置
*/
stop(isReset?: boolean): void;
/**
* 当前帧,从0开始
* @property currentFrame
* @public
* @since 1.0.0
* @type {number}
* @default 1
* @readonly
*/
get currentFrame(): number;
/**
* 当前动画是否处于播放状态
* @property isPlaying
* @readOnly
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
get isPlaying(): boolean;
/**
* 动画的播放方向,是顺着播还是在倒着播
* @property isFront
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
get isFront(): boolean;
/**
* 停在指定时间
* @param time
* @param force 是否强制更新,默认false,如果发现没stop在指定位置,可以试试设置true
*/
gotoAndStop: (time: number, force?: boolean) => void;
/**
* 从某时刻开始播放
* @param time
* @param isFront 默认true,正向播放
*/
gotoAndPlay: (time: number, isFront?: boolean) => void;
/**
* 在一个区间范围内播放
* @param beginTime 开始时间,秒计,默认0,
* @param endTime 结束时间,秒计,默认_totalTime
* @param loops 循环次数,默认1,0表示无限循环
* @param callback 参数loop表示还剩几次
*/
startAniRange: (beginTime: number, endTime: number, loops: number, callback?: (loop: number) => void) => void;
}
export class SvgaAni extends AnimationNode {
/**
* 原始数据,接口在解析类上,不搞先,尽量只获取,不修改
*/
protected rawData: VideoEntity;
/**
* 总时间,秒计
*/
get totalTime(): number;
/**
* 总帧数
*/
get totalFrames(): number;
/**
* 动画显示宽度
*/
get videoWidth(): number;
/**
* 动画显示高度
*/
get videoHeight(): number;
/**
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps(): number;
set fps(v: number);
/**
* 用于控制动画,这里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
*/
animationClip: AnimationClip;
constructor(data: VideoEntity);
/**
* 初始化方法
* @param data
* @returns
*/
init(data: VideoEntity): void;
/**
* 添加一个动画部件,主要用于换装添加部件
* @param imageKey 用于查找图层对应动画的key值,只找首个用key的图层,所以建议视觉保证使用唯一
* @param child 需要设置动画的显示对象
* @param index 需要显示的层级
* @param x 调整位置x
* @param y 调整位置y
* @param scaleX
* @param scaleY
* @param rotation
* @param anchorX
* @param anchorY
* @returns
*/
addAniPart(imageKey: string, child: Container, index: number, x?: number, y?: number, scaleX?: number, scaleY?: number, rotation?: number, anchorX?: number, anchorY?: number): void;
/**
* 给图层修改图片,一般用于出奖动效的奖品图片的替换,尺寸不一致时会做居中适配
* @param imagekey 会查找所有用了imagekey的图层
* @param imageUrl 图片路径
*/
setImage(imagekey: string, imageUrl: string): void;
/**
* 给对应图层修改纹理,直接替换,所以建议原纹理和替换纹理尺寸一致
* @param imagekey 会查找所有用了imagekey的图层
* @param texture 纹理
*/
setTexture(imagekey: string, texture: Texture): void;
/**
* 根据imagekey获取所有用到过的图层
* @param imagekey
* @returns
*/
getSpritesByImageKey(imagekey: string): Sprite[];
/**
* 用源数据拷贝一份,用相应参数,并未拷贝遮罩或矢量数据
* @param frames 源数据
* @param x 偏移x,默认0
* @param y 偏移y,默认0
* @param scaleX 相对缩放x,默认1
* @param scaleY 相对缩放y,默认1
* @param rotation 相对旋转,角度制,默认0
* @param anchorX 相对锚点x,默认0
* @param anchorY 相对锚点y,默认0
*/
static deepCopyFrames(frames: FrameEntity[], x?: number, y?: number, scaleX?: number, scaleY?: number, rotation?: number, anchorX?: number, anchorY?: number): any[];
}
export {};
export const getBezierEasing: (a: any, b: any, c: any, d: any, nm?: any) => any;
export function buildBezierProp(pt1: any, pt2: any, pt3: any, pt4: any, startIndex: any, endIndex: any, time: any, fnc: any): any[];
export class Lottie extends AnimationNode {
/**
* 原始数据,尽量只获取,不修改
*/
protected rawData: LottieData;
/**
* 总时间,秒计
*/
get totalTime(): number;
/**
* 总帧数
*/
get totalFrames(): number;
/**
* 动画显示宽度
*/
get videoWidth(): number;
/**
* 动画显示高度
*/
get videoHeight(): number;
/**
* 重写
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps(): number;
set fps(v: number);
private lottieContainer;
constructor(data: LottieData);
/**
* 初始化方法
* @param data
* @returns
*/
init(data: LottieData): void;
}
export {};
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
export const VERSION = "2.0.27";
export const VERSION = "2.0.28";
export const osType: "ios" | "android" | "pc";
......@@ -6167,7 +6167,7 @@ export class Container extends DisplayObject {
*/
protected _renderCanvas(renderer: CanvasRenderer): void;
/**
* 更新方法,注意别覆盖,子类可继承修改
* 节点每帧更新方法,注意别覆盖,否则每帧监听将失效,子类可继承修改
*/
update(): void;
/**
......@@ -8614,6 +8614,10 @@ export class AnimationClip extends EventDispatcher {
* 记录时间
*/
protected curTime: number;
/**
* 当前时间
*/
get currentTime(): number;
private _endMark;
/**
* 需要挂在循环里的方法,传时间间隔
......@@ -8661,62 +8665,250 @@ export class AnimationClip extends EventDispatcher {
resetState(): void;
}
export const getBezierEasing: (a: any, b: any, c: any, d: any, nm?: any) => any;
export function buildBezierProps(pt1: any, pt2: any, pt3: any, pt4: any, startIndex: any, endIndex: any, points: any, fnc: any, limit?: number): void;
export function buildBezierProp(pt1: any, pt2: any, pt3: any, pt4: any, startIndex: any, endIndex: any, time: any, fnc: any): any[];
export class Lottie extends Container {
export abstract class AnimationNode extends Container {
/**
* 原始数据,尽量只获取,不修改
*/
private rawData;
protected rawData: any;
/**
* 总时间,秒计
*/
get totalTime(): number;
abstract get totalTime(): number;
/**
* 总帧数
*/
get totalFrames(): number;
abstract get totalFrames(): number;
/**
* 动画显示宽度
*/
get videoWidth(): number;
abstract get videoWidth(): number;
/**
* 动画显示高度
*/
get videoHeight(): number;
private _fps;
abstract get videoHeight(): number;
/**
* 帧率
*/
protected _fps: number;
/**
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps(): number;
/**
* 设置每秒刷新率,可能值30,60等等
* 子类重写get方法时必须重写set方法,否则执行不到父类的set方法
*/
set fps(v: number);
private lottieContainer;
/**
* 用于控制动画,这里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
* 用于控制动画,
* Lottie和SvgaAni里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
*/
animationClip: AnimationClip;
constructor(data: LottieData);
init(data: LottieData): void;
protected animationClip: AnimationClip;
constructor(data: any);
/**
* 抽象的初始化类
* @param data
*/
abstract init(data: any): void;
private lastTime;
/**
* 更新方法,直接写了,后续继承的Lottie和SvgaAni基本不用改,有需要自行重写
*/
update(): void;
/**
* 从头播放,需要自定义的用this.animationClip
* @param loop 播放测试,默认0,一直循环播放
* @param callback 回调
* 播放loop次后回调
* @param loop 播放次数
* @param callback 播放次数完后回调
*/
play(loop?: number, callback?: () => void): void;
/**
* 开始播放
* @param isFront 是否正向,默认是
*/
play(isFront?: boolean): void;
/**
* 停止,需要自定义的用this.animationClip
* @param isReset 是否重置,默认false,不重置
*/
stop(isReset?: boolean): void;
/**
* 当前帧,从0开始
* @property currentFrame
* @public
* @since 1.0.0
* @type {number}
* @default 1
* @readonly
*/
get currentFrame(): number;
/**
* 当前动画是否处于播放状态
* @property isPlaying
* @readOnly
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
get isPlaying(): boolean;
/**
* 动画的播放方向,是顺着播还是在倒着播
* @property isFront
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
get isFront(): boolean;
/**
* 停在指定时间
* @param time
* @param force 是否强制更新,默认false,如果发现没stop在指定位置,可以试试设置true
*/
gotoAndStop: (time: number, force?: boolean) => void;
/**
* 从某时刻开始播放
* @param time
* @param isFront 默认true,正向播放
*/
gotoAndPlay: (time: number, isFront?: boolean) => void;
/**
* 在一个区间范围内播放
* @param beginTime 开始时间,秒计,默认0,
* @param endTime 结束时间,秒计,默认_totalTime
* @param loops 循环次数,默认1,0表示无限循环
* @param callback 参数loop表示还剩几次
*/
startAniRange: (beginTime: number, endTime: number, loops: number, callback?: (loop: number) => void) => void;
}
export class SvgaAni extends AnimationNode {
/**
* 原始数据,接口在解析类上,不搞先,尽量只获取,不修改
*/
protected rawData: VideoEntity;
/**
* 总时间,秒计
*/
get totalTime(): number;
/**
* 总帧数
*/
get totalFrames(): number;
/**
* 动画显示宽度
*/
get videoWidth(): number;
/**
* 动画显示高度
*/
get videoHeight(): number;
/**
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps(): number;
set fps(v: number);
/**
* 用于控制动画,这里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
*/
animationClip: AnimationClip;
constructor(data: VideoEntity);
/**
* 初始化方法
* @param data
* @returns
*/
init(data: VideoEntity): void;
/**
* 添加一个动画部件,主要用于换装添加部件
* @param imageKey 用于查找图层对应动画的key值,只找首个用key的图层,所以建议视觉保证使用唯一
* @param child 需要设置动画的显示对象
* @param index 需要显示的层级
* @param x 调整位置x
* @param y 调整位置y
* @param scaleX
* @param scaleY
* @param rotation
* @param anchorX
* @param anchorY
* @returns
*/
addAniPart(imageKey: string, child: Container, index: number, x?: number, y?: number, scaleX?: number, scaleY?: number, rotation?: number, anchorX?: number, anchorY?: number): void;
/**
* 给图层修改图片,一般用于出奖动效的奖品图片的替换,尺寸不一致时会做居中适配
* @param imagekey 会查找所有用了imagekey的图层
* @param imageUrl 图片路径
*/
setImage(imagekey: string, imageUrl: string): void;
/**
* 给对应图层修改纹理,直接替换,所以建议原纹理和替换纹理尺寸一致
* @param imagekey 会查找所有用了imagekey的图层
* @param texture 纹理
*/
setTexture(imagekey: string, texture: Texture): void;
/**
* 根据imagekey获取所有用到过的图层
* @param imagekey
* @returns
*/
getSpritesByImageKey(imagekey: string): Sprite[];
/**
* 用源数据拷贝一份,用相应参数,并未拷贝遮罩或矢量数据
* @param frames 源数据
* @param x 偏移x,默认0
* @param y 偏移y,默认0
* @param scaleX 相对缩放x,默认1
* @param scaleY 相对缩放y,默认1
* @param rotation 相对旋转,角度制,默认0
* @param anchorX 相对锚点x,默认0
* @param anchorY 相对锚点y,默认0
*/
static deepCopyFrames(frames: FrameEntity[], x?: number, y?: number, scaleX?: number, scaleY?: number, rotation?: number, anchorX?: number, anchorY?: number): any[];
}
export {};
export const getBezierEasing: (a: any, b: any, c: any, d: any, nm?: any) => any;
export function buildBezierProp(pt1: any, pt2: any, pt3: any, pt4: any, startIndex: any, endIndex: any, time: any, fnc: any): any[];
export class Lottie extends AnimationNode {
/**
* 原始数据,尽量只获取,不修改
*/
protected rawData: LottieData;
/**
* 总时间,秒计
*/
get totalTime(): number;
/**
* 总帧数
*/
get totalFrames(): number;
/**
* 动画显示宽度
*/
get videoWidth(): number;
/**
* 动画显示高度
*/
get videoHeight(): number;
/**
* 重写
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps(): number;
set fps(v: number);
private lottieContainer;
constructor(data: LottieData);
/**
* 初始化方法
* @param data
* @returns
*/
init(data: LottieData): void;
}
export {};
......
......@@ -201,7 +201,7 @@
//svga动画
SvgaParser.loadSvga("./res/小贱成年常态.svga", (v) => {
stage.addChild(new FYGE.MovieClip(v)).y -= 600
stage.addChild(new FYGE.SvgaAni(v)).y -= 600
})
//lottie动画(文件全)
FYGE.GlobalLoader.loadJson((s, json) => {
......@@ -248,7 +248,7 @@
//测试svga蒙版
SvgaParser.loadSvga("./res/测蒙版.svga", (v) => {
stage.addChild(new FYGE.MovieClip(v)).y -= 100
stage.addChild(new FYGE.SvgaAni(v)).y -= 100
})
// var ss = stage.addChild(new FYGE.EditableText())
......
......@@ -75,7 +75,7 @@
if (name.indexOf(".svga") >= 0) {
SvgaParser.loadSvga(src, (v) => {
console.log(v)
stage.addChild(new FYGE.MovieClip(v))
window.asd= stage.addChild(new FYGE.SvgaAni(v))
// .position.set(
// (750 - v.videoSize.width) / 2,
// (stage.viewRect.height - v.videoSize.height) / 2,
......@@ -93,6 +93,14 @@
}
})
</script>
<!-- <script src="http://172.16.228.98:8090/dist/index.js"></script>
<script>
agent.start()
setTimeout(()=>{
sssss
},1000)
</script> -->
</body>
</html>
\ No newline at end of file
{
"name": "fyge",
"version": "2.0.27",
"version": "2.0.28",
"description": "canvas渲染引擎",
"main": "./build/fyge.min.js",
"types": "./build/types.d.ts",
......
......@@ -363,6 +363,16 @@
Spine添加遮罩(tempClipContainers,ClippingAttachment,readAttachment添加SkinType.clipping,createGraphics,updateGraphics)
SpineAniManager的showAni方法loops参数默认值修改为0,默认无限循环,applyMixingFrom修改为私有方法
2.0.28 AnimationClip加了个currentTime的get方法
AnimationClip的gotoAndPlay、gotoAndStop、startAniRange方法的time修正改用clamp方法
AnimationClip的startAniRange放的循环监听里callback后加上rectify。否则下一帧生效,会导致部分动效会闪一帧
添加抽象类AnimationNode,用于SvgaAni和Lottie的抽象
删除原先的Lottie,LottieNew作为Lottie
导出SvgaAni
MovieClip构造函数添加提醒替换类
删除buildBezierEaserProps.ts文件
buildBezierProps文件改成buildBezierProp,去掉原先的buildBezierProps方法
现在不改,索引数据过大时得用Uint32Array,同时开扩展gl.getExtension( "OES_element_index_uint" )和drawElements改参数类型为gl.UNSIGNED_INT
......
import { EventDispatcher, Event } from "./events";
import { clamp } from "./utils";
/**
* 就是个时间轴,setValue和resetValue方法自行实现
......@@ -83,6 +84,12 @@ export class AnimationClip extends EventDispatcher {
* 记录时间
*/
protected curTime: number = 0;
/**
* 当前时间
*/
get currentTime() {
return this.curTime;
}
private _endMark: boolean = false;
/**
......@@ -148,8 +155,9 @@ export class AnimationClip extends EventDispatcher {
let s = this;
s._isFront = isFront;
s._isPlaying = true;
if (time > s._totalTime) time = s._totalTime;
if (time < 1) time = 0;
// if (time > s._totalTime) time = s._totalTime;
// if (time < 0) time = 0;
time = clamp(time, 0, s._totalTime);//改成用clamp
s.curTime = time;
}
/**
......@@ -159,8 +167,9 @@ export class AnimationClip extends EventDispatcher {
*/
public gotoAndStop(time: number, force: boolean = false): void {
this._isPlaying = false;
if (time > this.totalTime) time = this.totalTime;
if (time < 0) time = 0;
// if (time > this.totalTime) time = this.totalTime;
// if (time < 0) time = 0;
time = clamp(time, 0, this._totalTime);//改成用clamp
this.curTime = time;
//这样会强制更新
if (force) this.lastTime = null
......@@ -185,10 +194,12 @@ export class AnimationClip extends EventDispatcher {
loops = loops || 0;//去掉null等等
loops = Math.max(0, loops);//去掉负数
if (beginTime <= 0) beginTime = 0;
if (beginTime > this._totalTime) beginTime = this._totalTime;
if (endTime <= 0) endTime = 0;
if (endTime > this._totalTime) endTime = this._totalTime;
// if (beginTime <= 0) beginTime = 0;
// if (beginTime > this._totalTime) beginTime = this._totalTime;
// if (endTime <= 0) endTime = 0;
// if (endTime > this._totalTime) endTime = this._totalTime;
beginTime = clamp(beginTime, 0, this._totalTime);
endTime = clamp(endTime, 0, this._totalTime);
if (beginTime === endTime) {
this.gotoAndStop(beginTime)
//如果相等
......@@ -212,10 +223,12 @@ export class AnimationClip extends EventDispatcher {
if (s.curTime >= endTime || s._endMark) {//
loopCount--;
if (loopCount <= 0) {
s.gotoAndStop(endTime);
s.gotoAndStop(endTime);//这个在下一帧才生效,所以加一个矫正rectify
// s.rectify();
s.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this);
this.startAniRangeFun = null;
callback && callback(loopCount);
s.rectify();//放在最后吧,callback里也可能干了啥
} else {
s.gotoAndPlay(beginTime);
}
......@@ -224,10 +237,12 @@ export class AnimationClip extends EventDispatcher {
if (s.curTime <= beginTime || s._endMark) {
loopCount--
if (loopCount <= 0) {
s.gotoAndStop(beginTime);
s.gotoAndStop(beginTime);//这个在下一帧才生效,所以加一个矫正rectify
// s.rectify();
s.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this);
this.startAniRangeFun = null;
callback && callback(loopCount);
s.rectify();//放在最后吧,callback里也可能干了啥
} else {
s.gotoAndPlay(endTime, false);
}
......
......@@ -7,7 +7,7 @@
* @name VERSION
* @type {string}
*/
export const VERSION = "2.0.27";
export const VERSION = "2.0.28";
/**
......
......@@ -534,7 +534,7 @@ export default class Container extends DisplayObject {
}
/**
* 更新方法,注意别覆盖,子类可继承修改
* 节点每帧更新方法,注意别覆盖,否则每帧监听将失效,子类可继承修改
*/
update() {
if (!this.visible) return;
......
import { AnimationClip } from "../AnimationClip";
import { Container } from "../display";
/**
* 抽象动画类,暂时用于SvgaAni和Lottie的基类
*/
export abstract class AnimationNode extends Container {
/**
* 原始数据,尽量只获取,不修改
*/
protected rawData: any;
/**
* 总时间,秒计
*/
abstract get totalTime(): number;
/**
* 总帧数
*/
abstract get totalFrames(): number;
/**
* 动画显示宽度
*/
abstract get videoWidth(): number;
/**
* 动画显示高度
*/
abstract get videoHeight(): number;
/**
* 帧率
*/
protected _fps: number;
/**
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps(): number {
return this._fps || 0
}
/**
* 设置每秒刷新率,可能值30,60等等
* 子类重写get方法时必须重写set方法,否则执行不到父类的set方法
*/
set fps(v: number) {
this._fps = v;
}
/**
* 用于控制动画,
* Lottie和SvgaAni里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
*/
protected animationClip: AnimationClip;
constructor(data: any) {
super();
this._instanceType = "AnimationNode";
this.init(data)
}
/**
* 抽象的初始化类
* @param data
*/
abstract init(data: any): void;
private lastTime: number;
/**
* 更新方法,直接写了,后续继承的Lottie和SvgaAni基本不用改,有需要自行重写
*/
update() {
if (this.animationClip) {
var now = Date.now()
var delta = this.lastTime ? (now - this.lastTime) * 0.001 : 0.01667
this.lastTime = now;
//时间需要转换下帧小数,animationClip及各个track里的都是帧数
this.animationClip.update(delta * this.fps);
}
super.update();
}
/**
* 播放loop次后回调
* @param loop 播放次数
* @param callback 播放次数完后回调
*/
play(loop?: number, callback?: () => void): void;
/**
* 开始播放
* @param isFront 是否正向,默认是
*/
play(isFront?: boolean): void;
/**
* 从头播放,需要自定义的用this.animationClip
* @param {number|boolean} loop 播放测试,默认0,一直循环播放
* @param callback 回调
*/
play(loop?: any, callback?: () => void): void {
if (!this.animationClip) return;
//如果没有参数,或者第一个参数是布尔的走animationClip.play
if (!arguments.length || typeof arguments[0] == "boolean") {
this.animationClip.play(arguments[0]);
return;
}
//其他走播放次数
loop = loop || 0;
this.animationClip.startAniRange(0, this.animationClip.totalTime, loop, callback)
}
/**
* 停止,需要自定义的用this.animationClip
* @param isReset 是否重置,默认false,不重置
*/
stop(isReset: boolean = false) {
if (!this.animationClip) return;
isReset ? this.animationClip.gotoAndStop(0, true) : this.animationClip.stop();
}
/**
* 当前帧,从0开始
* @property currentFrame
* @public
* @since 1.0.0
* @type {number}
* @default 1
* @readonly
*/
get currentFrame(): number {
return this.animationClip ? Math.round(this.animationClip.currentTime) : 0;
}
/**
* 当前动画是否处于播放状态
* @property isPlaying
* @readOnly
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
get isPlaying() {
return this.animationClip ? this.animationClip.isPlaying : false
}
/**
* 动画的播放方向,是顺着播还是在倒着播
* @property isFront
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
get isFront(): boolean {
return this.animationClip ? this.animationClip.isFront : true;
}
/**
* 停在指定时间
* @param time
* @param force 是否强制更新,默认false,如果发现没stop在指定位置,可以试试设置true
*/
gotoAndStop: (time: number, force?: boolean) => void;
/**
* 从某时刻开始播放
* @param time
* @param isFront 默认true,正向播放
*/
gotoAndPlay: (time: number, isFront?: boolean) => void;
/**
* 在一个区间范围内播放
* @param beginTime 开始时间,秒计,默认0,
* @param endTime 结束时间,秒计,默认_totalTime
* @param loops 循环次数,默认1,0表示无限循环
* @param callback 参数loop表示还剩几次
*/
startAniRange: (beginTime: number, endTime: number, loops: number, callback?: (loop: number) => void) => void;
};
//这三个直接用clip的
["gotoAndStop", "gotoAndPlay", "startAniRange"].forEach((v) => {
Object.defineProperty(AnimationNode.prototype, v, {
value: function () {
if (!this.animationClip) return;
//直接用
this.animationClip[v](...Array.prototype.slice.call(arguments));
},
writable: true,
enumerable: true,
})
})
\ No newline at end of file
import { TextureCache, createImage } from "../utils";
import { Tween } from "../../tween";
import { Texture, BaseTexture } from "../texture";
import { Container, Sprite, DisplayObject } from "../display";
import { getBezierEasing } from "./forLottie/BezierEaser";
import { buildBezierProps } from "./forLottie/buildBezierProps";
import { buildBezierEaserProps } from "./forLottie/buildBezierEaserProps";
import { AnimationClip, IAnimationTrack } from "../AnimationClip";
import { HashObject } from "../HashObject";
import { Container, Sprite } from "../display";
import { BaseTexture, Texture } from "../texture";
import { getBezierEasing } from "./forLottie/BezierEaser"
import { buildBezierProp } from "./forLottie/buildBezierProp";
import { createImage, TextureCache } from "../utils";
import { Shape } from "../graphics";
import { AnimationNode } from "./AnimationNode";
export class Lottie extends AnimationNode {
/**
* 原始数据,尽量只获取,不修改
*/
protected rawData: LottieData;
/**
* 总时间,秒计
*/
get totalTime(): number {
if (!this.rawData) return 0;
const { op, ip } = this.rawData;
return (op - ip) * (1 / this.fps);
};
/**
* 总帧数
*/
get totalFrames(): number {
if (!this.rawData) return 0;
const { op, ip } = this.rawData;
return op - ip;
};
/**
* 动画显示宽度
*/
get videoWidth(): number {
return this.rawData && this.rawData.w || 0;
};
/**
* 动画显示高度
*/
get videoHeight(): number {
return this.rawData && this.rawData.h || 0;
};
/**
* 重写
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps() {
//没设置过就用数据里的
return this._fps || this.rawData && this.rawData.fr || null;
}
set fps(v: number) {
this._fps = v;
}
//lottie图层主容器
private lottieContainer: LottieContainer;
constructor(data: LottieData) {
super(data);//里面执行了init
this._instanceType = "Lottie";
}
/**
* 初始化方法
* @param data
* @returns
*/
public init(data: LottieData) {
if (!data || data == this.rawData) return;
this.rawData = data;
//名字
this.name = data.nm;
//初始化图片 有assets但无textures
if (data.assets && !data.textures) {
data.textures = {};
data.assets.forEach((a) => {
if (a.layers) {//合成嵌套的情况
data.compositions = data.compositions || {};
data.compositions[a.id] = a.layers;
return;
}
let imgTag = createImage();
imgTag.src = a.p;
data.textures[a.id] = new Texture(new BaseTexture(imgTag));
})
}
//原先有的,先销毁
this.lottieContainer && this.lottieContainer.destroy();
//初始化一个容器;
this.lottieContainer = this.addChild(new LottieContainer());
//生成时间轴
var tracks = createLottieTracks(
this.lottieContainer,
data.layers,
data.ip,
data.textures,
data.compositions
);
//合成所有时间轴
if (!this.animationClip) {
this.animationClip = new AnimationClip(tracks, this.totalFrames);
} else {
this.animationClip.init(tracks, this.totalFrames)
}
}
}
interface LottieData {
"fr": number,//珍露 30 60等
"fr": number,//帧率 30 60等
"ip": number,//开始帧
"op": number,//结束帧
"w": number,//宽度
......@@ -20,23 +117,26 @@ interface LottieData {
"w": number,
"h": number,
"p": string,//base64数据
"layers": LayerData[],//合成嵌套的
}[],
"textures"?: { [key: string]: Texture }//缓存的贴图,为了上面的assets里的图片数据,不进全局缓存,
svgaData?: any //数据生成后存入
// "components"?: { [key: string]: LottieData }
compositions?: { [key: string]: LayerData[] }
}
interface LayerData {
"ind": number,//id唯一
"ty": number,//类型,暂时只有2
"ty": number,//类型,2是图片,0是合成
"nm": string//"owl_sleep.png",//暂时就是图片
"refId": string,
"parent"?: number,//父级id
"ks": KsData;
"ip": number,//开始帧
"op": number,//结束帧
transformDatas?: TansformData[];
"st": number,
"w"?: number,//合成的会有
"h"?: number,//合成的会有
hasMask?: boolean,
masksProperties?: ILottieMaskData[],//遮罩数据
}
interface KsData {
o: KeyData //透明度
......@@ -46,12 +146,9 @@ interface KsData {
s: KeyData //缩放
}
interface KeyData {
a: number,
a: number,//貌似没用
k: KeyAniData[] | number[] | number,
x: string,//可能有表达式
// frames?: KeyFrameInt[],//注意根据表达式还要添加
props?: number[][],//记录所有的属性,每一帧的
}
interface KeyAniData {
......@@ -64,577 +161,316 @@ interface KeyAniData {
to: number[],
ti: number[],
h: number
}
h: number,//暂时用是否存在判断。有标识瞬切,不缓动,待测试
/**
* 记录的Tween数据
*/
interface TweenData {
/**
* 属性对象
*/
obj: any,
deltaTime: number,
deltaT: number,
isSet?: boolean,
/**
* 原始帧数据,算pingpong的时间差值用
*/
timeOri?: number,
}
/**
* 每一个属性在总帧数上每个关键点的数据
* 挂在每个层级的的ks的各个属性上,到时直接计算出所有的x,y,sx,sy,r,alpha, ax和ay固定,注意xy都得减axy
* 第一步只处理ks里面的数据
*/
interface KeyFrameInt {
/**
* 数值
*/
value: number[],
/**
* 第几帧
*/
frame: number,
//后面记录的东西
bezierData?,//to存在时的贝塞尔数据
fnc?,//贝塞尔函数,可能是数组,
// type: "stay" | "tween",//stay停留不变,到下一个状态时硬切 ,
/**
* 为了计算贝塞尔补间数据 ,有io或toti才有缓动
*/
i?: { x: number | number[], y: number | number[] };
o?: { x: number | number[], y: number | number[] };
/**
* 为了计算贝塞尔补间数据
*/
to?: number[],
ti?: number[],
//不知道干嘛用的,但是有用
h?: boolean,
}
interface TansformData {
alpha: number;
transform?: {
a: number,
b: number,
c: number,
d: number,
tx: number,
ty: number,
};
interface ILottieMaskData {
inv: boolean,//true需要处理矩形wh的矩形遮罩
mode: "n" | "a",//遮罩模式,一般是a
pt: {
"a": number,
"k": {
"i": [number, number][],
"o": [number, number][],
"v": [number, number][],
"c": boolean
},
"ix": number
},
"o": {
"a": number,
"k": number,
"ix": number
},
"x": {
"a": number,
"k": number,
"ix": number
},
"nm": string
}
enum LoopType {
pingpong = "pingpong",
cycle = "cycle",
}
interface LoopData {
loopInOrOut: 0 | 1 | 2,//0表示没有,1表示in前面循环,2表示out后续循环
type: LoopType,
duration: number,//为0表示全循环
}
/**
* 用Tween拼,后续计算帧数据记录
* 临时写的,真尼玛乱得很,以后再说
* 内部使用,不对外导出,这里track的时间统统是帧时间,所以对于AnimationClip的update传的时间做换算
* 注意lottie的透明度是不影响子级的
*/
export class Lottie extends Container {
/**
* 原始数据,尽量只获取,不修改
*/
private rawData: LottieData;
/**
* 总帧数
*/
get totalFrames(): number {
return this.rawData && (this.rawData.op - this.rawData.ip) || 0;
};
/**
* 锁步的时间间隔,按fps定,毫秒
*/
private timeInterval;
/**
* 按帧率计算,60为1,30为2,
*/
private deltaFrame: number = 1;
get videoWidth(): number {
return this.rawData && this.rawData.w || 0;
};
get videoHeight(): number {
return this.rawData && this.rawData.h || 0;
};
class LottieBaseTrack extends HashObject implements IAnimationTrack {
constructor(
protected obj: Sprite,
private type?: "r" | "o" | "s" | "p",
private times?: KeyAniData[],
private loop?: LoopData,
private ip: number = 0,//偏移
) {
super();
this._instanceType = "LottieBaseTrack";
}
/**
* 供循环用
* 子类重写
* @param time
*/
private loops: number
private callback: () => void
constructor(data) {
super()
this._instanceType = "Lottie";
//初始化
if (data) {
this.init(data);
setValue(time: number) {
time -= this.ip;
// if (!this.obj.visible) return
var value = this.cacValue(time);
switch (this.type) {
case "r":
this.obj.rotation = value[0];
break;
case "o":
this.obj.alpha = value[0] / 100;
break;
case "s":
this.obj.scale.set(value[0] / 100, value[1] / 100);
break;
case "p":
this.obj.position.set(value[0] - this.obj.anchorX, value[1] - this.obj.anchorY);
break;
}
// else {
// this.totalFrames = 0;
// }
}
/**
* 暂时不考虑重复init
* @param data
*
* @param time
* @returns
*/
init(data: LottieData) {
if (!data) return
this.rawData = data;
this.timeInterval = 1000 / data.fr;
// this.totalFrames = data.op - data.ip
//间隔帧数,
this.deltaFrame = 60 / data.fr;
this.name = data.nm;
//初始化图片 有assets但无textures
if (data.assets && !data.textures) {//带图片数据的待测试
data.textures = {};
data.assets.forEach((a) => {
let imgTag = createImage();
imgTag.src = a.p;
data.textures[a.id] = new Texture(new BaseTexture(imgTag));
})
protected cacValue(time: number) {
//计算真实时间,因为部分又表达式的
if (this.loop && this.loop.loopInOrOut) {
time = (this.loop.loopInOrOut == 1 ? loopIn : loopOut)(this.loop.type, this.loop.duration, this.times, time)
}
this.initChildren();
}
private initChildren() {
//初始化内容吧,假设所有资源已经加载好额
var layers = this.rawData.layers.slice();
//先筛选出所有不带parents,说明是顶级容器
for (var i = layers.length - 1; i >= 0; i--) {
let layer = layers[i];
// if (!layer.refId) console.log(layer)
let c = this.addChild(new Sprite(
// RES.getRes(layer.nm) ||
layer.refId ?
this.rawData.textures ?
this.rawData.textures[layer.refId] :
TextureCache[layer.refId] ||
TextureCache[layer.refId + ".png"] : null
));
//记录一下数据
c["layerData"] = layer;
if (time <= this.times[0].t) return this.times[0].s;
if (time >= this.times[this.times.length - 1].t) return this.times[this.times.length - 1].s;
//其他的计算补间了要,找前后两个索引
var after = this.findIndex(time);
var before = after - 1;
var da = this.times[after];
var db = this.times[before];
var beforeValue = db.s, afterValue = da.s;
var value: number[]
//有路径的
if (db.to) {
db.fnc = db.fnc || getBezierEasing(db.o.x, db.o.y, db.i.x, db.i.y).get;
value = buildBezierProp(
beforeValue,
afterValue,
db.to,
db.ti,
db.t,
da.t,
time,
db.fnc
)
}
this.initState()
}
private initState(con = this.children) {
for (var i = 0; i < con.length; i++) {
var c = con[i] as Sprite;
if (c["layerData"]) {
//取第一个数据
let data: LayerData = c["layerData"];
//@ts-ignore 透明度
c.alpha = data.ks.o.k[0] ? data.ks.o.k[0].s[0] / 100 : data.ks.o.k / 100;
// c.alpha = c.alpha === 0.01 ? 1 : c.alpha;
//@ts-ignore 选转
c.rotation = data.ks.r.k[0] ? data.ks.r.k[0].s[0] : data.ks.r.k;
//锚点,用贴图锚点
var ad = typeof data.ks.a.k[0] == "number" ? data.ks.a.k : data.ks.a.k[0].s;
c.anchor.set(ad[0], ad[1])
//位置
var ad = typeof data.ks.p.k[0] == "number" ? data.ks.p.k : data.ks.p.k[0].s;
c.position.set(ad[0] - c.anchorX, ad[1] - c.anchorY)
//缩放
var ad = typeof data.ks.s.k[0] == "number" ? data.ks.s.k : data.ks.s.k[0].s;
c.scale.set(ad[0] / 100, ad[1] / 100)
//如果入场不在的
c.visible = data.ip <= 0
// if (data.ip > 0) {
// c.visible = false
// } else {
// c.visible = true
// }
//有h的
else if (db.h === 1) {
value = beforeValue
} else {
db.fnc = db.fnc || generateFuncs(db.o, db.i, beforeValue.length);
var newValue = [], perc: number, delta = da.t - db.t;
for (var i = 0; i < beforeValue.length; i += 1) {
if (time >= da.t) {
perc = 1;
} else if (time < db.t) {
perc = 0;
} else {
if (!db.fnc) {//线性
perc = (time - db.t) / delta;
}
else if (db.fnc.constructor === Array) {
perc = db.fnc[i]((time - db.t) / delta);
} else {
perc = db.fnc((time - db.t) / delta);
}
}
newValue[i] = beforeValue[i] + (afterValue[i] - beforeValue[i]) * perc;;
}
if (c.children.length) this.initState(c.children)
value = newValue
}
return value;
}
/**
* 为了那啥 修改 loop默认0
* 首尾已判断,得到后一位索引
* @param times
* @param time
* @returns
*/
play(loop: number = 0, callback?: () => void) {
// this.initState();
this.stop(true);//需要回到初始状态再开始
this.loops = loop;
this.callback = callback;
this.addTweens();
protected findIndex(time: number): number {
var low = 0;
var high = this.times.length - 2;
if (high == 0) return 1;
var current = high >>> 1;
while (true) {
if (this.times[current + 1].t <= time)
low = current + 1;
else
high = current;
if (low == high)
return low + 1;
current = (low + high) >>> 1;
}
}
resetValue() {
this.setValue(0)
}
destroy() {
this.obj = null;
}
}
/**
* 部件的出场时间,就是个范围,
* 每个对象都有,优先判断
*/
class LottieVisibleTrack extends HashObject implements IAnimationTrack {
constructor(
protected obj: Container,
private inTime: number,
private outTime: number,
private startTime: number,
private ip: number = 0,//偏移
) {
super();
this._instanceType = "LottieVisibleTrack";
}
/**
* 移除所有的Tween,临时方法
* @param isReset 是否回到初始状态,默认否
* @param time
*/
stop(isReset: boolean = false) {
//tween要去掉
Tween.removeTweens(this)
this.children.forEach((c) => { Tween.removeTweens(c) })
isReset && this.initState();
setValue(time: number) {
time -= this.ip;
this.obj.visible = this.inTime <= time && this.outTime >= time
}
resetValue() {
private addTweens(con = this.children) {
for (var i = 0; i < con.length; i++) {
let c = con[i] as Sprite;
if (c["layerData"]) {
//取第一个数据
let data: LayerData = c["layerData"];
//@ts-ignore 透明度,如果k是数组,肯定有帧数据
if (data.ks.o.k.length) this.addTween(c, "o");
//@ts-ignore 旋转
if (data.ks.r.k.length) this.addTween(c, "r");
//位置,得是对象
if (typeof data.ks.p.k[0] != "number") this.addTween(c, "p");
//缩放
if (typeof data.ks.s.k[0] != "number") this.addTween(c, "s");
//显示隐藏统一这里处理,还有个循环的,如何计算显示隐藏再说
var t = Tween.get(c, { loop: true })
if (data.ip > 0 || data.op < this.rawData.op) {
var aa = data.ip < 0 ? 0 : data.ip;
var bb = data.op > this.rawData.op ? this.rawData.op : data.op
t.wait(aa * this.timeInterval)
.call(() => { c.visible = true; })
.wait((bb - aa) * this.timeInterval)
.call(() => { c.visible = false; })
.wait((this.rawData.op - bb) * this.timeInterval)
}
}
//其实不会有
if (c.children.length) this.addTweens(c.children)
}
//考虑回调
Tween.get(this, { loop: true })
.wait((this.rawData.op - this.rawData.ip) * this.timeInterval)
.call(() => {
if (--this.loops == 0) {
this.stop();
this.callback && this.callback();
}
})
}
destroy() {
this.obj = null
}
}
/**
* 来吧重写,,。专干loopOut和loopIn
* @param dis
* @param type
*/
private addTween(dis: DisplayObject, type: "r" | "o" | "s" | "p") {
const data: { "t": number, "s": number[], "h": number }[] = dis["layerData"].ks[type].k
let tween = Tween.get(dis, { loop: true })
let countTime = 0;
//有表达式的,先随便写,到时整理
if (dis["layerData"].ks[type].x) {
var xs = dis["layerData"].ks[type].x;
//取数字
var rr = +xs.replace(/[^0-9]/ig, "");
//loopOut后续循环,补齐从最后一个数据的t到dis["layerData"].op的间隔,不足一个的情况需要单独计算
if (xs.indexOf("loopOut") >= 0) {
//先走完一次整的,反正补后面的
let objArr: { obj: any, deltaTime: number, deltaT: number, isSet?: boolean }[] = [];
let curT = 0;
for (let i = 0; i < data.length; i++) {
let d = data[i];
//如果超过op的
if (d.t > dis["layerData"].op) break;
let deltaT = d.t - curT;
let deltaTime = deltaT * this.timeInterval;
let obj = getTweenObj(d);
//如果第一帧不是0,需要等待
if (i == 0 && d.t > 0) {
tween.wait(deltaTime)
countTime += deltaTime
// console.log("asdffff",countALL,d.t)
//需加set,但是时间0,暂时别加吧,tween会自行记录初始值
// objArr.push({ obj, deltaT:0, deltaTime:0, isSet: true })
}
//从0开始,但是t为0,用set
else if (i == 0 && d.t == 0) { //考虑下是否需要,deltaTime也是0
tween.set(obj);
objArr.push({ obj, deltaT, deltaTime, isSet: true })
} else {
//一帧当作set
if (d.t - curT == 1 || d.h === 1) {//1.5.0新增或者h==1
tween.wait(deltaTime)
.set(obj);
objArr.push({ obj, deltaT, deltaTime, isSet: true })
countTime += deltaTime
// console.log("asdff",countALL)
} else {
tween.to(obj, deltaTime);
objArr.push({ obj, deltaT, deltaTime })
countTime += deltaTime
// console.log("asdff",countALL)
}
}
//赋值
curT = d.t;
}
// if (dis["layerData"].ind == "1") console.log(45445456, objArr)
// console.log("asdf",countALL)
//pingpong先不考虑次数,还没遇到
if (xs.indexOf("pingpong") >= 0 && data[data.length - 1].t < dis["layerData"].op) {
// Math.floor((dis["layerData"].op - data[0].t))
var round = Math.round(
(dis["layerData"].op - data[data.length - 1].t) / //其实op可能超过rawData的op所以这样有问题
(data[data.length - 1].t - data[0].t) //不一定所有
)
curT += round * (data[data.length - 1].t - data[0].t) //不一定所有
var dir = false;
while (--round) {//居然没判断set的,遇到问题再说TODO
if (dir) {
for (var o = 0; o < objArr.length; o++) {
tween.to(objArr[o].obj, objArr[o].deltaTime);
countTime += objArr[o].deltaTime;
}
} else {
for (var o = objArr.length - 1; o >= 1; o--) {
tween.to(objArr[o - 1].obj, objArr[o].deltaTime);
countTime += objArr[o].deltaTime;
}
}
dir = !dir;
}
}
//循环,先floor,多余的重走一次,估计不能用dis["layerData"].op,得用this.rawData.op,loopOut到时也得单独计算
else if (xs.indexOf("cycle") >= 0 && data[data.length - 1].t < dis["layerData"].op) {
var lastIndex = data.length - 1;
var num = Math.floor(
(dis["layerData"].op - data[lastIndex].t) / //其实op可能超过rawData的op所以这样有问题
(data[lastIndex].t - data[lastIndex - (rr || lastIndex)].t)
);
// console.log("asd",num,data[lastIndex].t - data[lastIndex - (rr || lastIndex)].t)
//取一部分
let objArrC = objArr.slice(-rr);
while (num) {
num--;
//补满
for (var o = 0; o < objArrC.length; o++) {
if (objArrC[o].isSet) {
tween.wait(objArrC[o].deltaTime)
.set(objArrC[o].obj);
} else {
tween.to(objArrC[o].obj, objArrC[o].deltaTime);
}
countTime += objArrC[o].deltaTime
}
}
// console.log("asd",countALL)
//补剩下的,跑一部分
var left = (dis["layerData"].op - data[lastIndex].t) % (data[lastIndex].t - data[lastIndex - (rr || lastIndex)].t);
// if(dis["layerData"].ind=="1") console.log(45445456,left)
for (var o = 0; o < objArrC.length; o++) {
if (objArrC[o].deltaT <= left) {
if (objArrC[o].isSet) {
tween.wait(objArrC[o].deltaTime)
.set(objArrC[o].obj);
} else {
tween.to(objArrC[o].obj, objArrC[o].deltaTime);
}
left -= objArrC[o].deltaT
countTime += objArrC[o].deltaTime
} else {
if (left > 0) {//这种情况不会是set,再调吧,这样算补间有问题
// console.log(o,left, objArrC.length)
// if(o == 0)console.log(left, objArrC[o].deltaT)
var ooo = o == 0 ?
calculateInterpolation(
/*objArrC[0].obj,*/ copyProps(objArrC[o].obj, tween["_initQueueProps"]),//初始值用tween记录的
objArrC[o].obj,
left / objArrC[o].deltaT
) : calculateInterpolation(
objArrC[o - 1].obj,
objArrC[o].obj,
left / objArrC[o].deltaT
)
tween.to(
ooo,
left * this.timeInterval
);
countTime += left * this.timeInterval
function loopOut(type: LoopType, duration: number, keyframes: KeyAniData[], currentFrame: number) {
var lastKeyFrame = keyframes[keyframes.length - 1].t;
if (currentFrame <= lastKeyFrame) return currentFrame;
// console.log(countALL)
}
break;
}
}
if (!duration || duration > keyframes.length - 1) {
duration = keyframes.length - 1;
}
var firstKeyFrame = keyframes[keyframes.length - 1 - duration].t;
var cycleDuration = lastKeyFrame - firstKeyFrame;
}
// var i, len, ret;
if (type === 'pingpong') {
var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration);
if (iterations % 2 !== 0) {
return cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame;
}
}
//其他的再说
// else if(){
}
//前面循环,先取所有的tween序列,初始状态要改
else if (xs.indexOf("loopIn") >= 0) {
let objArr: TweenData[] = [];
let curT = 0;
for (let i = 0; i < data.length; i++) {
let d = data[i];
//不能去掉,有可能需要用到
// if (d.t > dis["layerData"].op) break;
let deltaT = d.t - curT;
let deltaTime = deltaT * this.timeInterval;
let obj = getTweenObj(d);
//一帧当作set
if (d.t - curT == 1 || d.h === 1) {//1.5.0新增或者h==1
objArr.push({ obj, deltaT, deltaTime, isSet: true, timeOri: d.t })
} else {
objArr.push({ obj, deltaT, deltaTime, timeOri: d.t })
}
//赋值
curT = d.t;
}
//pingpong再loopIn暂时没有,用到时再写,还真尼玛下个就是
if (xs.indexOf("pingpong") >= 0 && data[0].t > 0) {
objArr = getLoopInPingpongTween(objArr, rr, dis["layerData"].op)
// if (dis["layerData"].ind == 8) console.log("asdf", objArr);
for (var o = 0; o < objArr.length; o++) {
// if (dis["layerData"].ind == 8) console.log("asdf", objArrC[o]);
if (objArr[o].isSet) {
tween.wait(objArr[o].deltaTime)
.set(objArr[o].obj);
} else {
tween.to(objArr[o].obj, objArr[o].deltaTime);
}
countTime += objArr[o].deltaTime
}
}
//循环,其实应该用dis["layerData"].ip判断
else if (xs.indexOf("cycle") >= 0 && data[0].t > 0) {//不考虑不整的,直接从0开始,算出整的,然后自然过度到最后一个
//可能入场的时间不能算
// if (dis["layerData"].ip > 0) tween.wait(dis["layerData"].ip * this.timeInterval)
// }
return (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame;
}
objArr = getLoopInCycleTween(objArr, rr, dis["layerData"].op, dis["layerData"].ip);
if (dis["layerData"].ip) {
// console.log(5464,dis["layerData"].ip,countTime)
tween.wait(dis["layerData"].ip * this.timeInterval)//以后改
countTime += dis["layerData"].ip * this.timeInterval
}
for (var o = 0; o < objArr.length; o++) {
if (objArr[o].isSet) {
tween.wait(objArr[o].deltaTime)
.set(objArr[o].obj);
} else {
tween.to(objArr[o].obj, objArr[o].deltaTime);
}
countTime += objArr[o].deltaTime
}
}
// if (dis["layerData"].ind == 1) console.log("asd", countTime);
//多余的时间
var op = Math.min(dis["layerData"].op, this.rawData.op)
if (countTime < op * this.timeInterval) {
var dd = op * this.timeInterval - countTime;
tween.wait(dd)
countTime += dd
}
// console.log(countTime)
// if (dis["layerData"].ind == 1) console.log("asde", countTime);
}
//还有一部分 dis["layerData"].op 到 this.rawData.op
if (dis["layerData"].op < this.rawData.op) {
tween.wait((this.rawData.op - dis["layerData"].op) * this.timeInterval)
countTime += (this.rawData.op - dis["layerData"].op) * this.timeInterval
}
//查看所有时间
// console.log(countTime)
}
//没表达式的,用wait补满前面的和后面的
else {
let curT = 0;
for (let i = 0; i < data.length; i++) {
let d = data[i],
obj = getTweenObj(d),
deltaTime;
//判断是否小于0,小于0需要和下一帧算补间
//给出时间就行,自行判断是否计算,根据
function loopIn(type: LoopType, duration: number, keyframes: KeyAniData[], currentFrame: number) {
var firstKeyFrame = keyframes[0].t;
if (currentFrame >= firstKeyFrame) return currentFrame;
//0时取全部
if (!duration || duration > keyframes.length - 1) {
duration = keyframes.length - 1;
}
var lastKeyFrame = keyframes[duration].t;
var cycleDuration = lastKeyFrame - firstKeyFrame;
if (d.t < 0) {
//下一帧不存在或也小于0
if (!data[i + 1] || data[i + 1].t < 0) break;
obj = calculateInterpolation(
obj,
getTweenObj(data[i + 1]),
-d.t / (data[i + 1].t - d.t)
);
// if (d.t == -35) console.log(123)
tween.set(obj);
curT = 0;
}
//如果超过op的,和上一帧算补间
else if (d.t > this.rawData.op) {
if (!data[i - 1] || data[i - 1].t > this.rawData.op) break;
let dt = this.rawData.op - data[i - 1].t;
deltaTime = dt * this.timeInterval;
obj = calculateInterpolation(
getTweenObj(data[i - 1]),
obj,
dt / (d.t - data[i - 1].t)
);
// if (dis["layerData"].ind == 7) console.log(999, d.t, data[i - 1].t, getTweenObj(data[i - 1]), obj)
tween.to(obj, deltaTime);
//累计时间
countTime += deltaTime;
//没必要记了。最后一帧了
curT = this.rawData.op;
}
else {
deltaTime = (d.t - curT) * this.timeInterval
//如果第一帧不是0,需要等待
if (i == 0 && d.t > 0) {
tween.wait(deltaTime)
}
//从0开始,但是t为0,用set
else if (i == 0 && d.t == 0) {
tween.set(obj);
} else {
tween.to(obj, deltaTime);
}
countTime += deltaTime
//赋值
curT = d.t;
}
}
//考虑还有部分时间,等待
if (this.rawData.op > curT) {
tween.wait((this.rawData.op - curT) * this.timeInterval)
}
// var i, len, ret;
if (type === LoopType.pingpong) {
var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration);
if (iterations % 2 === 0) {
return (firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame;
}
// console.log(countTime)
//结束的操作
// tween.call(() => {
// if (--this.loops == 0) {
// this.stop();
// this.callback && this.callback();
// }
// })
}
//其他的再说
// else if(){
/**
* type和dis主参数里取
* @param d 循环里取
*/
function getTweenObj(d: { "t": number; "s": number[] }) {
let obj;
switch (type) {
case "r":
obj = { rotation: d.s[0] }
break;
case "o":
obj = { alpha: d.s[0] / 100 }
break;
case "s":
obj = { scaleX: d.s[0] / 100, scaleY: d.s[1] / 100 }
break;
case "p":
obj = { x: d.s[0] - dis.anchorX, y: d.s[1] - dis.anchorY }
break;
}
return obj;
// }
return cycleDuration - (firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame;
}
function generateFuncs(outV, inV, len) {
//无参数时直接返回null
if (!outV || !inV) return null;
var outX, outY, inX, inY
if (outV.x.constructor === Array) {
var fncts = [];
for (var i = 0; i < len; i++) {
outX = (typeof outV.x[i] === 'undefined') ? outV.x[0] : outV.x[i];
outY = (typeof outV.y[i] === 'undefined') ? outV.y[0] : outV.y[i];
inX = (typeof inV.x[i] === 'undefined') ? inV.x[0] : inV.x[i];
inY = (typeof inV.y[i] === 'undefined') ? inV.y[0] : inV.y[i];
fncts[i] = getBezierEasing(outX, outY, inX, inY).get;
}
return fncts;
} else {
outX = outV.x;
outY = outV.y;
inX = inV.x;
inY = inV.y;
return getBezierEasing(outX, outY, inX, inY).get;
}
}
function getLoopData(x: string): LoopData {
if (!x) return null;
//取数字
var rr = +x.replace(/[^0-9]/ig, "");
var loopInOrOut: 0 | 1 | 2 = 0;
if (x.indexOf("loopOut") >= 0) {
loopInOrOut = 2
} else if (x.indexOf("loopIn") >= 0) {
loopInOrOut = 1
}
var type: LoopType;
if (x.indexOf("pingpong") >= 0) {
type = LoopType.pingpong
} else if (x.indexOf("cycle") >= 0) {
type = LoopType.cycle
}
return { loopInOrOut, type, duration: rr }
}
/**
*
*/
interface ILottieLater {
_mark: boolean;
_isLottieLayer: boolean;
_ind: number
_parentId: number
}
/**
* 一个子级完全平铺,但是子级却存在父级id记录的容器类,lottie会继承自他,还有合成的也继承自他
*/
class LottieContainer extends Container {
/**
* 是图片,但有额外属性
*/
children: ((Sprite | LottieContainer) & ILottieLater)[];
/**
* 对所有的进行刷新,,根据cParent进行迭代刷新
* 层级有问题,只能平铺,手动计算矩阵
......@@ -648,623 +484,199 @@ export class Lottie extends Container {
this._recursivePostUpdateTransformAA(c);
})
this.children.forEach((c) => {
c["mark"] = false;
c._mark = false;
})
}
private findChildByInd(ind: number) {
for (var i = 0; i < this.children.length; i++) {
if (this.children[i]["layerData"] &&
this.children[i]["layerData"].ind === ind
) return this.children[i]
if (this.children[i]._ind === ind) return this.children[i]
}
return null
}
private _recursivePostUpdateTransformAA(c) {
if (c.layerData && c.layerData.parent) {
private _recursivePostUpdateTransformAA(c: (Sprite | LottieContainer) & ILottieLater) {
if (c._isLottieLayer && c._parentId) {
//ind从1开始,所以不用考虑0,且不应该存在 p没有的情况
var p = this.findChildByInd(c.layerData.parent)
var p = this.findChildByInd(c._parentId)
this._recursivePostUpdateTransformAA(p);
if (!c.mark) {
c.mark = true;
c.transform.updateWorldMatrix(p.transform);
//透明度单独计算,不跟cParent保持
if (!c._mark) {
c._mark = true;
// c.transform.updateWorldMatrix(p.transform);
//用临时父级的方式,否则合成嵌套的子级不会更新
const cacheParent = c.parent;
c.parent = p;
c.updateTransform();
c.parent = cacheParent;
//透明度单独计算,不跟p保持,这个到时要测试下,可以需要向上递归取透明度才是对的
c._worldAlpha = c.alpha * c.parent._worldAlpha;
}
}
//直接进行tans
else if (!c.mark) {
c.updateTransform();//alpha跟父级有关
c.mark = true
else if (!c._mark) {
c.updateTransform();//alpha跟父级有关,这里透明度也一样
c._mark = true
}
}
/**
* 加个方法,前两个参数都没用,为了一头牛
* @param beginFrame
* @param endFrame
* @param loops
* @param callback
*/
public startAniRange(
beginFrame: number = 1,
endFrame: number = this.totalFrames,
loops: number = 1,
callback?: () => void
) {
this.play(loops, callback)
}
destroy() {
//tween要去掉
Tween.removeTweens(this);
this.children.forEach((c) => { Tween.removeTweens(c) })
super.destroy();
}
}
function calculateInterpolation(
d1: any,
d2: any,
scale: number //时间比例 t/(t2-t1)
) {
let obj = {};
// for (let key in d1) obj[key] = Math.abs(d1[key] - d2[key]) * scale + d1[key]
//之前为何要加绝对值
for (let key in d1) obj[key] = (d2[key] - d1[key]) * scale + d1[key]
return obj
}
/**
* 返回一个带obj里所有的key的对象,但是key值为sObj里的
* @param obj 取里面的key
* @param sObj 取里面的key值
* 递归创建lottie时间轴,
* @param root
* @param layers
* @param offset
* @param textures
* @param compositions
* @param tracks
* @returns
*/
function copyProps(obj, sObj) {
var o = {};
if (!obj) return o;
for (let key in obj) o[key] = sObj[key];
return o;
}
/**
* 以后可能还会改,以后整理吧,以后可能要计算补间,现在先不管
* @param objArr
* @param time 第一帧的时间间隔
* @param round 循环索引
*/
function getLoopInCycleTween(objArr: TweenData[], round: number, op: number, ip: number = 0) {
var time = objArr[0].deltaT - ip;//以后算插值时再说
//这样是否合理,也可能是objArrC的length下标,超出一个
if (round >= objArr.length) round = 0;
var objArrC = objArr.slice(0, round ? round + 1 : objArr.length)
// var lastDeltaT = objArrC[objArrC.length - 1].deltaT;
// if (objArrC.length > 2) {//如果长度大于2,首帧和尾帧是一致的,默认,所以不取最后一帧
// //去掉最后一帧
// objArrC.pop();
// }
// console.log(objArrC.length, objArr.length)
var tweenArr: TweenData[] = [];
var curIndex = 0;
while (time > 0) {
curIndex--;
//超出就是最后一帧
if (curIndex < 0) curIndex = objArrC.length - 1;
//第0帧是set
if (!curIndex) {
tweenArr.unshift({
obj: objArrC[0].obj,
deltaT: 0,
deltaTime: 0,
timeOri: objArrC[0].timeOri,
isSet: true
})
function createLottieTracks(
root: LottieContainer,
layers: LayerData[],
offset: number,
textures: { [key: string]: Texture },
compositions?: { [key: string]: LayerData[] },
tracks: (LottieVisibleTrack | LottieBaseTrack)[] = [],
) {
for (var i = layers.length - 1; i >= 0; i--) {
let layer = layers[i];
const { ty, refId, ks, parent, ind, ip, op, st, hasMask, masksProperties } = layer;
let c: (Sprite | LottieContainer) & ILottieLater;
//如果存在组件嵌套
if (ty == 0 && compositions) {
c = root.addChild(new LottieContainer()) as LottieContainer & ILottieLater;
//递归创建,宽高和名字暂时不需要,
createLottieTracks(
c,
compositions[refId],
offset + ip,
textures,
compositions,
tracks
);
}
//其他的都是终点
//图片图层,不能判断2.因为有部分是Container
else {
tweenArr.unshift(objArrC[curIndex])
time -= objArrC[curIndex].deltaT
}
}
//第一帧加上
tweenArr.unshift({
obj: objArrC[0].obj,
deltaT: 0,
deltaTime: 0,
timeOri: objArrC[0].timeOri,
isSet: true
})
//把剩下的,第一项要变成set,再加后面的所有
tweenArr.push({
obj: objArrC[0].obj,
deltaT: 0,
deltaTime: 0,
isSet: true
})
for (var i = 1; i < objArr.length; i++) {
if (objArr[i].timeOri > op) break;
tweenArr.push(objArr[i]);
}
// console.log(tweenArr)
return tweenArr
}
function getLoopInCycleTween11(objArr: TweenData[], time: number, round: number) {
//这样是否合理,也可能是objArrC的length下标,超出一个
if (round >= objArr.length) round = 0;
var lastDeltaT = objArr[round || objArr.length - 1].deltaT;
var lastDeltaTime = objArr[round || objArr.length - 1].deltaTime;
objArr[0].deltaT = lastDeltaT;
objArr[0].deltaTime = lastDeltaTime;
var objArrC = objArr.slice(0, round || objArr.length)
var tweenArr: TweenData[] = [];
var curT = 0;
//从0开始,一般就是初始未知,不做任何处理,不考虑插值
for (var i = 1; i < objArrC.length; i++) {
tweenArr.push(objArrC[i]);//不做拷贝应该也没事
curT += objArrC[i].deltaT;
}
time -= curT;
while (time > lastDeltaT) {
for (var i = 0; i < objArrC.length; i++) {
tweenArr.push(objArrC[i]);//不做拷贝应该也没事
time -= objArrC[i].deltaT
}
}
//接上最后一组
tweenArr = tweenArr.concat(objArr)
return tweenArr
}
/**
* pingpong的,取首尾相接的循环
* @param objArr
* @param time
* @param round
*/
function getLoopInPingpongTween(objArr: TweenData[], round: number, op: number) {
var time = objArr[0].deltaT;
if (round >= objArr.length) round = 0;
var objArrC = objArr.slice(0, round ? round + 1 : objArr.length)
var timeInterval = objArr[0].deltaTime / objArr[0].deltaT;
// var allTime = 0;
// objArrC.forEach((o, i) => { if (i != 0) allTime += o.deltaT })
var tweenArr: TweenData[] = [];
var dir: boolean = true// = (time / allTime) % 2 == 0;
var curIndex = 0;
var lastIndex = 0;
while (time > 0) {
dir ? curIndex++ : curIndex--;
//如果超了,就反向
if (curIndex > objArrC.length - 1) {
dir = false;
curIndex -= 2
}
else if (curIndex < 0) {
dir = true;
curIndex += 2
}
var deltaT = Math.abs(objArrC[lastIndex].timeOri - objArrC[curIndex].timeOri);
tweenArr.unshift({
obj: objArrC[lastIndex].obj,
deltaT,
deltaTime: deltaT * timeInterval,
timeOri: objArrC[lastIndex].timeOri,
isSet: objArrC[lastIndex].isSet
})
lastIndex = curIndex;
time -= deltaT
}
//塞入
tweenArr.unshift({
obj: objArrC[lastIndex].obj,
deltaT: 0,
deltaTime: 0,
timeOri: objArrC[lastIndex].timeOri,
isSet: true
})
//把剩下的,除了第一项不要
//得考虑超出的op
for (var i = 1; i < objArr.length; i++) {
if (objArr[i].timeOri > op) break;
tweenArr.push(objArr[i]);
}
// objArr.forEach((o, i) => {
// if (i) tweenArr.push(o)
// })
return tweenArr
}
//动画总ip必须为0,否则修改
let totalOp: number = 0;
let lottieData: LottieData
export function lottieToSvgaData(data: LottieData) {
//已经计算过了就直接返回了
if (data.svgaData) return data.svgaData;
//存一下,全局用
totalOp = data.op;//最后一帧估计不算
lottieData = data;
//是否需要深拷贝
// data = JSON.parse(JSON.stringify(data))
//最终返回的数据
var videoEntity: any = {};
//一些简单的先设置了
videoEntity.videoSize = {
width: data.w,
height: data.h
}
videoEntity.FPS = data.fr;
videoEntity.frames = data.op - data.ip;
//图片还在的时
if (data.assets) {//注意这时不能把素材干进全局。单独存在自己的数据中
videoEntity.images = {};
data.assets.forEach((a) => {
videoEntity.images[a.id] = a.p.replace("data:image/png;base64,", "")//去掉相同的前缀
})
}
for (var i = 0; i < data.layers.length; i++) {
var layerData = data.layers[i];
var ks = layerData.ks;
//锚点是不变的,先写
var ad: [number, number] = typeof layerData.ks.a.k[0] == "number" ? layerData.ks.a.k : layerData.ks.a.k[0].s;
// ks.a.frames = [{ value: ad, frame: 0 }];
//@ts-ignore
ks.a.props = fillUp(ad);
//其他的
addFrames(layerData);
}
//开始计算每帧矩阵,注意父级,循环每帧,必须上面的先执行完毕
data.layers.forEach((l) => {
l.transformDatas = fillUp({
alpha: 0,
// transform: {
// a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0
// }
});
})
for (var i = 0; i < totalOp; i++) {
data.layers.forEach((l) => {
var ks = l.ks;
var ip = l.ip;
var op = l.op;
if (i >= ip && i <= op) {
// if()
// console.log(ks.o.props[i], l.nm, ks.o.props.length, i)
l.transformDatas[i].alpha = ks.o.props[i][0];
}
recursiveUpdateTransform(l, i);
})
}
//sprites最重要的东西,里面有imageKey和frames,TODO
videoEntity.sprites = [];
for (var i = 0; i < data.layers.length; i++) {
var layerData = data.layers[i];
if (layerData.refId)//有图片的才存下,因为全部已经转换成世界矩阵。容器已经没有意义
videoEntity.sprites.push({
imageKey: layerData.refId,
frames: layerData.transformDatas
})
}
videoEntity.sprites.reverse()
return lottieData.svgaData = videoEntity
}
function addFrames(layerData: LayerData) {
var ks = layerData.ks;
var ip = layerData.ip;
var op = layerData.op;
//@ts-ignore透明度
if (ks.o.k.length) {
addFrame(layerData, "o");
} else {//没有直接取首诊数据,且根据ip op处理显隐 具体怎么处理好点
//@ts-ignore 透明度决定显隐,最后根据ip和op来重新修正alpha 注意ip有可能小于0,op可能大于totalOp,所以注意判断 TODO
var alpha = ks.o.k / 100;
// ks.o.frames = [{ value: alpha, frame: 0 }]
//@ts-ignore
ks.o.props = fillUp([alpha]);
}
//@ts-ignore 旋转
if (ks.r.k.length) {
addFrame(layerData, "r");
} else {
//@ts-ignore
// ks.r.frames = [{ value: ks.r.k, frame: 0 }]
//@ts-ignore
ks.r.props = fillUp([ks.r.k]);
}
//位置,得是对象
if (typeof ks.p.k[0] != "number") {
addFrame(layerData, "p");
} else {
var anchor: [number, number] = typeof ks.a.k[0] == "number" ? ks.a.k : ks.a.k[0].s;
//@ts-ignore
var p = ks.p.k;
// ks.p.frames = [{ value: [p[0] - anchor[0], p[1] - anchor[1]], frame: 0 }]
//@ts-ignore
ks.p.props = fillUp([p[0] - anchor[0], p[1] - anchor[1]]);
}
//缩放
if (typeof ks.s.k[0] != "number") {
addFrame(layerData, "s");
} else {
//@ts-ignore
var s = ks.s.k;
// ks.s.frames = [{ value: [s[0] / 100, s[1] / 100], frame: 0 }]
//@ts-ignore
ks.s.props = fillUp([s[0] / 100, s[1] / 100]);
}
}
function addFrame(layerData: LayerData, type: "o" | "r" | "p" | "s") {
//@ts-ignore
var data: KeyAniData[] = layerData.ks[type].k;
var ip = layerData.ip; //有小于0的情况
var op = layerData.op; //有大于totalOp的情况
var ks = layerData.ks;
var anchor: [number, number] = typeof ks.a.k[0] == "number" ? ks.a.k : ks.a.k[0].s;
var frames: KeyFrameInt[] = [];
var props: number[][] = ks[type].props = [];
props.length = totalOp + 1;
for (let i = 0; i < data.length; i++) {
let d = data[i];
let value: number[];
switch (type) {
case "r":
value = [d.s[0]];
break;
case "o":
value = [d.s[0] / 100];
break;
case "s":
value = [d.s[0] / 100, d.s[1] / 100, 0]
break;
case "p":
value = [d.s[0] - anchor[0], d.s[1] - anchor[1], 0]
break;
c = root.addChild(new Sprite(
refId ?
textures ?
textures[refId] :
TextureCache[refId] ||
TextureCache[refId + ".png"] : null
) as Sprite & ILottieLater);
}
frames.push({
i: d.i, o: d.o, ti: d.ti, to: d.to,
h: d.h === 1,
value,
frame: d.t
})
}
//把已有的干一边
for (var i = 0; i < frames.length - 1; i++) {//最后一个数据不用
let f = frames[i], nextF = frames[i + 1];
if (f.to) {
//把函数也传进去吧,用来处理
var fnc = getBezierEasing(f.o.x, f.o.y, f.i.x, f.i.y).get;
buildBezierProps(
f.value,
nextF.value,
f.to,//.map((e, index) => e - anchor[index] || 0),
f.ti,//.map((e, index) => e - anchor[index] || 0),
f.frame,
nextF.frame,
props,
fnc,
ks[type].x ? undefined : totalOp //没有表达式时直接加限制
);
//遮罩数据,遮罩矩阵和c一致,所以加在c里面
if (hasMask && masksProperties && masksProperties.length) {
c.mask = c.addChild(createLottieMask(masksProperties, layer.w, layer.h));
}
//没有路径,只有缓动
else {
buildBezierEaserProps(
f.value,
nextF.value,
f.o,
f.i,
f.frame,
nextF.frame,
props,
f.h,
ks[type].x ? undefined : totalOp,
//标记下
c._isLottieLayer = true;
//记录一下数据,查找父级的时候有用
c._ind = ind, c._parentId = parent;
//处理下锚点,基本不会有锚点动画
var ad = typeof ks.a.k[0] == "number" ? ks.a.k : ks.a.k[0].s;
c.anchor.set(ad[0], ad[1]);
//加显示隐藏的
tracks.push(new LottieVisibleTrack(c, ip, op, st, offset))
//@ts-ignore透明度
if (ks.o.k.length) {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "o", ks.o.k, getLoopData(ks.o.x), offset)
)
} else {
//@ts-ignore
c.alpha = ks.o.k / 100
}
}
//如果是h==1的,把最后的数据加上
if (frames[frames.length - 1].h) {
props[frames[frames.length - 1].frame] = frames[frames.length - 1].value.slice();
}
//有表达式的,把最后一帧数据加上,循环有用
if (ks[type].x && (ks[type].x.indexOf("loopOut") >= 0 || ks[type].x.indexOf("loopIn") >= 0)) {
var xs = ks[type].x;
var loopOut = xs.indexOf("loopOut") >= 0;
var loopIn = xs.indexOf("loopIn") >= 0;
//取数字
var rr = +xs.replace(/[^0-9]/ig, "");
if (rr > frames.length - 1) rr = 0;
var propsClone = props.slice();
var isCycle = xs.indexOf("cycle") >= 0;
var isPingpong = xs.indexOf("pingpong") >= 0;
var dir = false;
var lastIndex = frames.length - 1;
var startIndex = 0;
var count = loopOut ? frames[lastIndex].frame ://开始接的那一帧
frames[0].frame;//往前的从0开始
var stepOffset = loopOut ? frames[lastIndex - (rr || lastIndex)].frame :
// frames[0 + (rr || lastIndex)].frame;//循环帧数起点索引,往后补和往前补的
frames[0].frame
// frames[lastIndex - (rr || lastIndex)].frame
var stepAll = loopOut ? frames[lastIndex].frame - stepOffset :
// stepOffset - frames[0].frame; //总循环数每次,拐点只算一次,多一帧应该也计算了
// frames[lastIndex].frame - stepOffset
frames[0 + (rr || lastIndex)].frame - stepOffset;
while (loopOut ?
(/*count <= op &&*/ count <= totalOp) ://op也可能大于totalOp;
(/*count >= ip &&*/ count >= 0)//ip他妈有可能是负的
) {//到op为止,其他的不算
if (isPingpong) {
if (dir) {//正向,第一帧不算
funLoop((index) => {
if (loopOut) {
props[++count] = propsClone[index + 1 + stepOffset].slice();
} else {
props[--count] = propsClone[stepAll - 1 - index + stepOffset].slice();
}
}, stepAll)
} else {//反向,从最后一帧(不算)开始往前加剩下的
funLoop((index) => {
if (loopOut) {
props[++count] = propsClone[stepAll - 1 - index + stepOffset].slice();
} else {//从起始点参考其实是正向
// props[--count] = propsClone[index + 1 + stepOffset].slice();
props[--count] = propsClone[index + 1 + stepOffset].slice();
}
}, stepAll)
}
dir = !dir;
} else if (isCycle) {
funLoop((index) => {
if (loopOut) {
props[++count] = propsClone[index + 1 + stepOffset].slice()
} else {
props[--count] = propsClone[stepAll - 1 - index + stepOffset].slice();
}
}, stepAll)
} else {//其他情况暂时不管
break;
}
//@ts-ignore 旋转
if (ks.r.k.length) {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "r", ks.r.k, getLoopData(ks.r.x), offset)
)
} else {
//@ts-ignore
c.rotation = ks.r.k
}
}
var arr = returnEmpty(props);
if (arr[0] !== null) {//前面补齐
var countFront = arr[0]
var startValue = props[countFront];
while (countFront--) {
props[countFront] = startValue.slice();
}
}
// console.log(layerData.nm, arr)
if (arr[1] !== null) {
// console.log(layerData.nm, arr, arr[1])
var countAfter = arr[1];
var endValue = props[countAfter];
while (countAfter++ <= totalOp) {
props[countAfter] = endValue.slice();
//位置
if (typeof ks.p.k[0] != "number") {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "p", ks.p.k, getLoopData(ks.p.x), offset)
)
} else {
//@ts-ignore
var p = ks.p.k;
c.position.set(p[0] - ad[0], p[1] - ad[1])
}
}
}
function findLayerByInd(ind: number): LayerData {
for (var i = 0; i < lottieData.layers.length; i++) {
if (lottieData.layers[i].ind === ind) return lottieData.layers[i]
}
//貌似layer是按顺序的,TMD有些中间的ind是没有的,还是找吧
// return lottieData.layers[ind - 1];
}
function recursiveUpdateTransform(l: LayerData, index: number) {
if (l.parent !== undefined) {
//ind从1开始,所以不用考虑0,且不应该存在 p没有的情况
var p = findLayerByInd(l.parent)
recursiveUpdateTransform(p, index);
if (!l.transformDatas[index].transform) {
//计算自己的先
var lt = l.transformDatas[index];
lt.transform = createWorldMatrix(
createLocalMatrix(l, index),
p.transformDatas[index].transform
);
//缩放
if (typeof ks.s.k[0] != "number") {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "s", ks.s.k, getLoopData(ks.s.x), offset)
)
} else {
//@ts-ignore
var s = ks.s.k;
c.scale.set(s[0] / 100, s[1] / 100)
}
}
//直接进行tans
else if (!l.transformDatas[index].transform) {
//计算顶级的自己的世界矩阵
var lt = l.transformDatas[index];
lt.transform = createLocalMatrix(l, index);
}
}
//不用Matrix类,担心属性太多,直接用函数
function createLocalMatrix(l: LayerData, index: number) {
var ks = l.ks;
var x = 0, y = 0, scaleX = 1, scaleY = 1, rotation = 0;
var anchorX = ks.a.props[0][0], anchorY = ks.a.props[0][1];
if (ks.p.props[index]) {
x = ks.p.props[index][0];
y = ks.p.props[index][1];
}
if (ks.s.props[index]) {
scaleX = ks.s.props[index][0];
scaleY = ks.s.props[index][1];
}
if (ks.r.props[index]) {
rotation = ks.r.props[index][0];
}
var lt: any = {};
rotation *= Math.PI / 180;
lt.a = Math.cos(rotation) * scaleX;
lt.b = Math.sin(rotation) * scaleX;
lt.c = -Math.sin(rotation) * scaleY;
lt.d = Math.cos(rotation) * scaleY;
lt.tx = x + anchorX - ((anchorX * lt.a) + (anchorY * lt.c));
lt.ty = y + anchorY - ((anchorX * lt.b) + (anchorY * lt.d));
return lt;
return tracks;
}
//不用Matrix类,担心属性太多,直接用函数
function createWorldMatrix(lt, pt) {
return {
a: (lt.a * pt.a) + (lt.b * pt.c),
b: (lt.a * pt.b) + (lt.b * pt.d),
c: (lt.c * pt.a) + (lt.d * pt.c),
d: (lt.c * pt.b) + (lt.d * pt.d),
tx: (lt.tx * pt.a) + (lt.ty * pt.c) + pt.tx,
ty: (lt.tx * pt.b) + (lt.ty * pt.d) + pt.ty,
}
}
function returnEmpty(arr: any[]): [number, number] {
var front = null, after = null;
var last = !!arr[0];
for (var i = 1; i < arr.length; i++) {
if (!last) {
if (arr[i] !== undefined) {
front = i;
/**
* 生成lottie的遮罩矢量
* @param masksProperties
* @param w
* @param h
* @returns
*/
function createLottieMask(masksProperties: ILottieMaskData[], w: number, h: number): Shape {
var mask = new Shape();
mask.beginFill();
masksProperties.forEach((m) => {
if (m.mode !== 'n') {
if (m.inv) {
mask.moveTo(0, 0);
mask.lineTo(w, 0);
mask.lineTo(w, h);
mask.lineTo(0, h);
mask.lineTo(0, 0);
}
} else {
if (arr[i] === undefined) {
after = i - 1;
break
var data = m.pt.k;
mask.moveTo(data.v[0][0], data.v[0][1]);
var j, jLen = data.v.length;
for (j = 1; j < jLen; j++) {
mask.bezierCurveTo(
data.o[j - 1][0] + data.v[j - 1][0],
data.o[j - 1][1] + data.v[j - 1][1],
data.i[j][0] + data.v[j][0],
data.i[j][1] + data.v[j][1],
data.v[j][0],
data.v[j][1]
);
}
mask.bezierCurveTo(
data.o[j - 1][0] + data.v[j - 1][0],
data.o[j - 1][1] + data.v[j - 1][1],
data.i[0][0] + data.v[0][0],
data.i[0][1] + data.v[0][1],
data.v[0][0],
data.v[0][1]
);
if (data.c) {
//TODO
}
}
last = !!arr[i]
}
return [front, after]
})
mask.endFill();
return mask
}
function funLoop(fun: (index: number) => void, times: number) {
if (times <= 0) return;
let count = times;
// while (count--) fun(count);//刚好索引从times-1开始,到0结束
while (count--) fun(times - count - 1);//刚好索引从0开始,到times-1结束
}
function fillUp<T>(value: T): T[] {//最后一个数据应该用不到吧
return Array(...Array(totalOp)).map((a, i) => JSON.parse(JSON.stringify(value)));
}
\ No newline at end of file
import { AnimationClip, IAnimationTrack } from "../AnimationClip";
import { HashObject } from "../HashObject";
import { Container, Sprite } from "../display";
import { BaseTexture, Texture } from "../texture";
import { getBezierEasing } from "./forLottie/BezierEaser"
import { buildBezierProp } from "./forLottie/buildBezierProps";
import { createImage, TextureCache } from "../utils";
import { Shape } from "../graphics";
export class Lottie extends Container {
/**
* 原始数据,尽量只获取,不修改
*/
private rawData: LottieData;
/**
* 总时间,秒计
*/
get totalTime(): number {
if (!this.rawData) return 0;
const { op, ip } = this.rawData;
return (op - ip) * (1 / this.fps);
};
/**
* 总帧数
*/
get totalFrames(): number {
if (!this.rawData) return 0;
const { op, ip } = this.rawData;
return op - ip;
};
/**
* 动画显示宽度
*/
get videoWidth(): number {
return this.rawData && this.rawData.w || 0;
};
/**
* 动画显示高度
*/
get videoHeight(): number {
return this.rawData && this.rawData.h || 0;
};
private _fps: number;
/**
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps() {
//没设置过就用数据里的
return this._fps || this.rawData && this.rawData.fr || null;
}
/**
* 设置每秒刷新率,可能值30,60等等
*/
set fps(v: number) {
this._fps = v;
}
//lottie图层主容器
private lottieContainer: LottieContainer;
/**
* 用于控制动画,这里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
*/
animationClip: AnimationClip;
constructor(data: LottieData) {
super();
this._instanceType = "Lottie";
this.init(data)
}
public init(data: LottieData) {
if (!data || data == this.rawData) return;
this.rawData = data;
//名字
this.name = data.nm;
//初始化图片 有assets但无textures
if (data.assets && !data.textures) {
data.textures = {};
data.assets.forEach((a) => {
if (a.layers) {//合成嵌套的情况
data.compositions = data.compositions || {};
data.compositions[a.id] = a.layers;
return;
}
let imgTag = createImage();
imgTag.src = a.p;
data.textures[a.id] = new Texture(new BaseTexture(imgTag));
})
}
//原先有的,先销毁
this.lottieContainer && this.lottieContainer.destroy();
//初始化一个容器;
this.lottieContainer = this.addChild(new LottieContainer());
//生成时间轴
var tracks = createLottieTracks(
this.lottieContainer,
data.layers,
data.ip,
data.textures,
data.compositions
);
//合成所有时间轴
if (!this.animationClip) {
this.animationClip = new AnimationClip(tracks, this.totalFrames);
} else {
this.animationClip.init(tracks, this.totalFrames)
}
}
private lastTime: number
update() {
var now = Date.now()
var delta = this.lastTime ? (now - this.lastTime) * 0.001 : 0.01667
this.lastTime = now;
//时间需要转换下帧小数,animationClip及各个track里的都是帧数
this.animationClip && this.animationClip.update(delta * this.fps);
super.update();
// this.children.forEach((c: Lottie) => {
// if (c instanceof Lottie) {
// c.update()
// }
// })
}
/**
* 从头播放,需要自定义的用this.animationClip
* @param loop 播放测试,默认0,一直循环播放
* @param callback 回调
*/
play(loop: number = 0, callback?: () => void) {
this.animationClip.startAniRange(0, this.animationClip.totalTime, loop, callback)
}
/**
* 停止,需要自定义的用this.animationClip
* @param isReset 是否重置,默认false,不重置
*/
stop(isReset: boolean = false) {
isReset ? this.animationClip.gotoAndStop(0, true) : this.animationClip.stop();
}
}
interface LottieData {
"fr": number,//帧率 30 60等
"ip": number,//开始帧
"op": number,//结束帧
"w": number,//宽度
"h": number,//高度
"nm": string,//名字
"layers": LayerData[],
"assets"?: {
"id": string,//图片id,与layers里的refId对应
"w": number,
"h": number,
"p": string,//base64数据
"layers": LayerData[],//合成嵌套的
}[],
"textures"?: { [key: string]: Texture }//缓存的贴图,为了上面的assets里的图片数据,不进全局缓存,
// "components"?: { [key: string]: LottieData }
compositions?: { [key: string]: LayerData[] }
}
interface LayerData {
"ind": number,//id唯一
"ty": number,//类型,2是图片,0是合成
"nm": string//"owl_sleep.png",//暂时就是图片
"refId": string,
"parent"?: number,//父级id
"ks": KsData;
"ip": number,//开始帧
"op": number,//结束帧
"st": number,
"w"?: number,//合成的会有
"h"?: number,//合成的会有
hasMask?: boolean,
masksProperties?: ILottieMaskData[],//遮罩数据
}
interface KsData {
o: KeyData //透明度
r: KeyData //旋转
p: KeyData //位置
a: KeyData //锚点
s: KeyData //缩放
}
interface KeyData {
a: number,//貌似没用
k: KeyAniData[] | number[] | number,
x: string,//可能有表达式
}
interface KeyAniData {
t: number,
s: number[],
i: { x: number | number[], y: number | number[] },
o: { x: number | number[], y: number | number[] },
to: number[],
ti: number[],
h: number,//暂时用是否存在判断。有标识瞬切,不缓动,待测试
//后面记录的东西
bezierData?,//to存在时的贝塞尔数据
fnc?,//贝塞尔函数,可能是数组,
}
interface ILottieMaskData {
inv: boolean,//true需要处理矩形wh的矩形遮罩
mode: "n" | "a",//遮罩模式,一般是a
pt: {
"a": number,
"k": {
"i": [number, number][],
"o": [number, number][],
"v": [number, number][],
"c": boolean
},
"ix": number
},
"o": {
"a": number,
"k": number,
"ix": number
},
"x": {
"a": number,
"k": number,
"ix": number
},
"nm": string
}
enum LoopType {
pingpong = "pingpong",
cycle = "cycle",
}
interface LoopData {
loopInOrOut: 0 | 1 | 2,//0表示没有,1表示in前面循环,2表示out后续循环
type: LoopType,
duration: number,//为0表示全循环
}
/**
* 内部使用,不对外导出,这里track的时间统统是帧时间,所以对于AnimationClip的update传的时间做换算
* 注意lottie的透明度是不影响子级的
*/
class LottieBaseTrack extends HashObject implements IAnimationTrack {
constructor(
protected obj: Sprite,
private type?: "r" | "o" | "s" | "p",
private times?: KeyAniData[],
private loop?: LoopData,
private ip: number = 0,//偏移
) {
super();
}
/**
* 子类重写
* @param time
*/
setValue(time: number) {
time -= this.ip;
// if (!this.obj.visible) return
var value = this.cacValue(time);
switch (this.type) {
case "r":
this.obj.rotation = value[0];
break;
case "o":
this.obj.alpha = value[0] / 100;
break;
case "s":
this.obj.scale.set(value[0] / 100, value[1] / 100);
break;
case "p":
this.obj.position.set(value[0] - this.obj.anchorX, value[1] - this.obj.anchorY);
break;
}
}
/**
*
* @param time
* @returns
*/
protected cacValue(time: number) {
//计算真实时间,因为部分又表达式的
if (this.loop && this.loop.loopInOrOut) {
time = (this.loop.loopInOrOut == 1 ? loopIn : loopOut)(this.loop.type, this.loop.duration, this.times, time)
}
if (time <= this.times[0].t) return this.times[0].s;
if (time >= this.times[this.times.length - 1].t) return this.times[this.times.length - 1].s;
//其他的计算补间了要,找前后两个索引
var after = this.findIndex(time);
var before = after - 1;
var da = this.times[after];
var db = this.times[before];
var beforeValue = db.s, afterValue = da.s;
var value: number[]
//有路径的
if (db.to) {
db.fnc = db.fnc || getBezierEasing(db.o.x, db.o.y, db.i.x, db.i.y).get;
value = buildBezierProp(
beforeValue,
afterValue,
db.to,
db.ti,
db.t,
da.t,
time,
db.fnc
)
}
//有h的
else if (db.h === 1) {
value = beforeValue
} else {
db.fnc = db.fnc || generateFuncs(db.o, db.i, beforeValue.length);
var newValue = [], perc: number, delta = da.t - db.t;
for (var i = 0; i < beforeValue.length; i += 1) {
if (time >= da.t) {
perc = 1;
} else if (time < db.t) {
perc = 0;
} else {
if (!db.fnc) {//线性
perc = (time - db.t) / delta;
}
else if (db.fnc.constructor === Array) {
perc = db.fnc[i]((time - db.t) / delta);
} else {
perc = db.fnc((time - db.t) / delta);
}
}
newValue[i] = beforeValue[i] + (afterValue[i] - beforeValue[i]) * perc;;
}
value = newValue
}
return value;
}
/**
* 首尾已判断,得到后一位索引
* @param times
* @param time
* @returns
*/
protected findIndex(time: number): number {
var low = 0;
var high = this.times.length - 2;
if (high == 0) return 1;
var current = high >>> 1;
while (true) {
if (this.times[current + 1].t <= time)
low = current + 1;
else
high = current;
if (low == high)
return low + 1;
current = (low + high) >>> 1;
}
}
resetValue() {
this.setValue(0)
}
destroy() {
this.obj = null;
}
}
/**
* 部件的出场时间,就是个范围,
* 每个对象都有,优先判断
*/
class LottieVisibleTrack extends HashObject implements IAnimationTrack {
constructor(
protected obj: Container,
private inTime: number,
private outTime: number,
private startTime: number,
private ip: number = 0,//偏移
) {
super()
}
/**
* @param time
*/
setValue(time: number) {
time -= this.ip;
this.obj.visible = this.inTime <= time && this.outTime >= time
}
resetValue() {
}
destroy() {
this.obj = null
}
}
function loopOut(type: LoopType, duration: number, keyframes: KeyAniData[], currentFrame: number) {
var lastKeyFrame = keyframes[keyframes.length - 1].t;
if (currentFrame <= lastKeyFrame) return currentFrame;
if (!duration || duration > keyframes.length - 1) {
duration = keyframes.length - 1;
}
var firstKeyFrame = keyframes[keyframes.length - 1 - duration].t;
var cycleDuration = lastKeyFrame - firstKeyFrame;
// var i, len, ret;
if (type === 'pingpong') {
var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration);
if (iterations % 2 !== 0) {
return cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame;
}
}
//其他的再说
// else if(){
// }
return (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame;
}
//给出时间就行,自行判断是否计算,根据
function loopIn(type: LoopType, duration: number, keyframes: KeyAniData[], currentFrame: number) {
var firstKeyFrame = keyframes[0].t;
if (currentFrame >= firstKeyFrame) return currentFrame;
//0时取全部
if (!duration || duration > keyframes.length - 1) {
duration = keyframes.length - 1;
}
var lastKeyFrame = keyframes[duration].t;
var cycleDuration = lastKeyFrame - firstKeyFrame;
// var i, len, ret;
if (type === LoopType.pingpong) {
var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration);
if (iterations % 2 === 0) {
return (firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame;
}
}
//其他的再说
// else if(){
// }
return cycleDuration - (firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame;
}
function generateFuncs(outV, inV, len) {
//无参数时直接返回null
if (!outV || !inV) return null;
var outX, outY, inX, inY
if (outV.x.constructor === Array) {
var fncts = [];
for (var i = 0; i < len; i++) {
outX = (typeof outV.x[i] === 'undefined') ? outV.x[0] : outV.x[i];
outY = (typeof outV.y[i] === 'undefined') ? outV.y[0] : outV.y[i];
inX = (typeof inV.x[i] === 'undefined') ? inV.x[0] : inV.x[i];
inY = (typeof inV.y[i] === 'undefined') ? inV.y[0] : inV.y[i];
fncts[i] = getBezierEasing(outX, outY, inX, inY).get;
}
return fncts;
} else {
outX = outV.x;
outY = outV.y;
inX = inV.x;
inY = inV.y;
return getBezierEasing(outX, outY, inX, inY).get;
}
}
function getLoopData(x: string): LoopData {
if (!x) return null;
//取数字
var rr = +x.replace(/[^0-9]/ig, "");
var loopInOrOut: 0 | 1 | 2 = 0;
if (x.indexOf("loopOut") >= 0) {
loopInOrOut = 2
} else if (x.indexOf("loopIn") >= 0) {
loopInOrOut = 1
}
var type: LoopType;
if (x.indexOf("pingpong") >= 0) {
type = LoopType.pingpong
} else if (x.indexOf("cycle") >= 0) {
type = LoopType.cycle
}
return { loopInOrOut, type, duration: rr }
}
/**
*
*/
interface ILottieLater {
_mark: boolean;
_isLottieLayer: boolean;
_ind: number
_parentId: number
}
/**
* 一个子级完全平铺,但是子级却存在父级id记录的容器类,lottie会继承自他,还有合成的也继承自他
*/
class LottieContainer extends Container {
/**
* 是图片,但有额外属性
*/
children: ((Sprite | LottieContainer) & ILottieLater)[];
/**
* 对所有的进行刷新,,根据cParent进行迭代刷新
* 层级有问题,只能平铺,手动计算矩阵
* 因为要平铺,所以记录cParent和ind 从1开始,也许只需要+1就行,还是用ind记录查找吧
* 遍历找
*/
updateTransform() {
//super不行,到时查
this.displayObjectUpdateTransform();
this.children.forEach((c) => {
this._recursivePostUpdateTransformAA(c);
})
this.children.forEach((c) => {
c._mark = false;
})
}
private findChildByInd(ind: number) {
for (var i = 0; i < this.children.length; i++) {
if (this.children[i]._ind === ind) return this.children[i]
}
return null
}
private _recursivePostUpdateTransformAA(c: (Sprite | LottieContainer) & ILottieLater) {
if (c._isLottieLayer && c._parentId) {
//ind从1开始,所以不用考虑0,且不应该存在 p没有的情况
var p = this.findChildByInd(c._parentId)
this._recursivePostUpdateTransformAA(p);
if (!c._mark) {
c._mark = true;
// c.transform.updateWorldMatrix(p.transform);
//用临时父级的方式,否则合成嵌套的子级不会更新
const cacheParent = c.parent;
c.parent = p;
c.updateTransform();
c.parent = cacheParent;
//透明度单独计算,不跟p保持,这个到时要测试下,可以需要向上递归取透明度才是对的
c._worldAlpha = c.alpha * c.parent._worldAlpha;
}
}
//直接进行tans
else if (!c._mark) {
c.updateTransform();//alpha跟父级有关,这里透明度也一样
c._mark = true
}
}
}
/**
* 递归创建lottie时间轴,
* @param root
* @param layers
* @param offset
* @param textures
* @param compositions
* @param tracks
* @returns
*/
function createLottieTracks(
root: LottieContainer,
layers: LayerData[],
offset: number,
textures: { [key: string]: Texture },
compositions?: { [key: string]: LayerData[] },
tracks: (LottieVisibleTrack | LottieBaseTrack)[] = [],
) {
for (var i = layers.length - 1; i >= 0; i--) {
let layer = layers[i];
const { ty, refId, ks, parent, ind, ip, op, st, hasMask, masksProperties } = layer;
let c: (Sprite | LottieContainer) & ILottieLater;
//如果存在组件嵌套
if (ty == 0 && compositions) {
c = root.addChild(new LottieContainer()) as LottieContainer & ILottieLater;
//递归创建,宽高和名字暂时不需要,
createLottieTracks(
c,
compositions[refId],
offset + ip,
textures,
compositions,
tracks
);
}
//图片图层,不能判断2.因为有部分是Container
else {
c = root.addChild(new Sprite(
refId ?
textures ?
textures[refId] :
TextureCache[refId] ||
TextureCache[refId + ".png"] : null
) as Sprite & ILottieLater);
}
//遮罩数据,遮罩矩阵和c一致,所以加在c里面
if (hasMask && masksProperties && masksProperties.length) {
c.mask = c.addChild(createLottieMask(masksProperties, layer.w, layer.h));
}
//标记下
c._isLottieLayer = true;
//记录一下数据,查找父级的时候有用
c._ind = ind, c._parentId = parent;
//处理下锚点,基本不会有锚点动画
var ad = typeof ks.a.k[0] == "number" ? ks.a.k : ks.a.k[0].s;
c.anchor.set(ad[0], ad[1]);
//加显示隐藏的
tracks.push(new LottieVisibleTrack(c, ip, op, st, offset))
//@ts-ignore透明度
if (ks.o.k.length) {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "o", ks.o.k, getLoopData(ks.o.x), offset)
)
} else {
//@ts-ignore
c.alpha = ks.o.k / 100
}
//@ts-ignore 旋转
if (ks.r.k.length) {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "r", ks.r.k, getLoopData(ks.r.x), offset)
)
} else {
//@ts-ignore
c.rotation = ks.r.k
}
//位置
if (typeof ks.p.k[0] != "number") {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "p", ks.p.k, getLoopData(ks.p.x), offset)
)
} else {
//@ts-ignore
var p = ks.p.k;
c.position.set(p[0] - ad[0], p[1] - ad[1])
}
//缩放
if (typeof ks.s.k[0] != "number") {
tracks.push(
//@ts-ignore
new LottieBaseTrack(c, "s", ks.s.k, getLoopData(ks.s.x), offset)
)
} else {
//@ts-ignore
var s = ks.s.k;
c.scale.set(s[0] / 100, s[1] / 100)
}
}
return tracks;
}
/**
* 生成lottie的遮罩矢量
* @param masksProperties
* @param w
* @param h
* @returns
*/
function createLottieMask(masksProperties: ILottieMaskData[], w: number, h: number): Shape {
var mask = new Shape();
mask.beginFill();
masksProperties.forEach((m) => {
if (m.mode !== 'n') {
if (m.inv) {
mask.moveTo(0, 0);
mask.lineTo(w, 0);
mask.lineTo(w, h);
mask.lineTo(0, h);
mask.lineTo(0, 0);
}
var data = m.pt.k;
mask.moveTo(data.v[0][0], data.v[0][1]);
var j, jLen = data.v.length;
for (j = 1; j < jLen; j++) {
mask.bezierCurveTo(
data.o[j - 1][0] + data.v[j - 1][0],
data.o[j - 1][1] + data.v[j - 1][1],
data.i[j][0] + data.v[j][0],
data.i[j][1] + data.v[j][1],
data.v[j][0],
data.v[j][1]
);
}
mask.bezierCurveTo(
data.o[j - 1][0] + data.v[j - 1][0],
data.o[j - 1][1] + data.v[j - 1][1],
data.i[0][0] + data.v[0][0],
data.i[0][1] + data.v[0][1],
data.v[0][0],
data.v[0][1]
);
if (data.c) {
//TODO
}
}
})
mask.endFill();
return mask
}
......@@ -146,6 +146,8 @@ export class MovieClip extends Container {
s._instanceType = "MovieClip";
//初始化
if (mv) s.init(mv);
console.warn("MovieClip类即将废弃,建议使用SvgaAni显示类")
}
/**
......
import { AnimationClip, IAnimationTrack } from "../AnimationClip";
import { Container, Sprite } from "../display";
import { Shape } from "../graphics";
import { HashObject } from "../HashObject";
import { Matrix } from "../math";
import { BaseTexture, Texture } from "../texture";
import { clamp, createCanvas, createImage, rgb2hex, TextureCache } from "../utils";
import { AnimationNode } from "./AnimationNode";
/**
* svga动画的承载显示类
*/
export class SvgaAni extends AnimationNode {
/**
* 原始数据,接口在解析类上,不搞先,尽量只获取,不修改
*/
protected rawData: VideoEntity;
/**
* 总时间,秒计
*/
get totalTime(): number {
return this.rawData && this.rawData.frames * (1 / this.fps) || 0;
};
/**
* 总帧数
*/
get totalFrames(): number {
return this.rawData && this.rawData.frames || 0;
};
/**
* 动画显示宽度
*/
get videoWidth(): number {
return this.rawData && this.rawData.videoSize.width || null;
};
/**
* 动画显示高度
*/
get videoHeight(): number {
return this.rawData && this.rawData.videoSize.height || null;
};
/**
* 每秒刷新帧数,没设置过直接用数据里的
*/
get fps() {
//没设置过就用数据里的
return this._fps || this.rawData && this.rawData.FPS || null;
}
set fps(v: number) {
this._fps = v;
}
/**
* 用于控制动画,这里面的按帧数计,animationClip.totalTime是总帧数,因为文件标记的是帧,而不是时间
*/
animationClip: AnimationClip;
constructor(data: VideoEntity) {
super(data);//里面执行了init
this._instanceType = "SvgaAni";
}
/**
* 初始化方法
* @param data
* @returns
*/
init(data: VideoEntity) {
if (!data || data == this.rawData) return;
//记录原数据
this.rawData = data;
//移除所有子级,后续考虑销毁
this.removeAllChildren();
//缓存图片
if (data.images && !data.textures) {//带图片数据的待测试
data.textures = {};
for (var key in data.images) {
var src = data.images[key];
if (src.indexOf("iVBO") === 0 || src.indexOf("/9j/2w") === 0) {
src = 'data:image/png;base64,' + src;
}//图片链接时的宽高适配再说,暂时没有遇到过
let imgTag = createImage();
imgTag.src = src;
data.textures[key] = new Texture(new BaseTexture(imgTag));//这种方法不会缓存进全局
}
}
var tracks: SvgaTrack[] = [];
//处理子级
data.sprites.forEach((ele) => {
if (!ele.imageKey) return;
let sprite = this.addChild(new Sprite(
this.rawData.textures ?
this.rawData.textures[ele.imageKey] ://自身没存的,取全局的,有图片单独处理出去
TextureCache[ele.imageKey] ||
TextureCache[ele.imageKey + ".png"] || null
));
//记录一下用到的图片标识,否则将找不到该显示对象;
sprite["imageKey"] = ele.imageKey;
tracks.push(new SvgaTrack(
sprite,
ele.frames
))
})
//合成所有时间轴,总时间按总帧数传,其实可能应该用this.totalFrames-1的,无所谓了,只是最后一帧停留了一帧
if (!this.animationClip) {
this.animationClip = new AnimationClip(tracks, this.totalFrames);
} else {
this.animationClip.init(tracks, this.totalFrames)
}
}
/**
* 添加一个动画部件,主要用于换装添加部件
* @param imageKey 用于查找图层对应动画的key值,只找首个用key的图层,所以建议视觉保证使用唯一
* @param child 需要设置动画的显示对象
* @param index 需要显示的层级
* @param x 调整位置x
* @param y 调整位置y
* @param scaleX
* @param scaleY
* @param rotation
* @param anchorX
* @param anchorY
* @returns
*/
addAniPart(
imageKey: string,
child: Container,
index: number,
x?: number,
y?: number,
scaleX?: number,
scaleY?: number,
rotation?: number,
anchorX?: number,
anchorY?: number
) {
if (!child || !imageKey || !this.rawData || !this.animationClip) return;
var oriFrames: FrameEntity[], sprites = this.rawData.sprites;
for (var i = 0; i < sprites.length; i++) {
if (sprites[i].imageKey == imageKey) {
oriFrames = sprites[i].frames;
break;
}
}
if (!oriFrames) {
console.warn("未找到" + imageKey + "对应的数据")
return;
}
var frames = SvgaAni.deepCopyFrames(oriFrames, x, y, scaleX, scaleY, rotation, anchorX, anchorY);
//总帧数肯定一样的,直接往tracks里push吧
this.animationClip["tracks"].push(new SvgaTrack(
this.addChildAt(child, index),
frames
))
}
/**
* 给图层修改图片,一般用于出奖动效的奖品图片的替换,尺寸不一致时会做居中适配
* @param imagekey 会查找所有用了imagekey的图层
* @param imageUrl 图片路径
*/
setImage(imagekey: string, imageUrl: string) {
//先获取原先贴图,为了宽高适配,,这种如何保证base64已经加载完毕,考虑lottie的图片宽高信息存过来?
let cs = this.getSpritesByImageKey(imagekey);
if (!cs.length) return;
var texture: Texture = cs[0].texture;
var width = texture.width;
var height = texture.height;
let image = createImage();
image.onload = function () {
let newTexture: Texture
if (image.width == width && image.height == height) {
newTexture = Texture.from(image);
} else {
var canvas = createCanvas()//document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d")
//适配绘制,为了全部显示在canvas中
var scaleCan = width / height;
var scale = image.width / image.height;
if (scaleCan > scale) {
//定高
ctx.drawImage(image, 0, 0, image.width, image.height
, (width - scale * height) / 2, 0, scale * height, height)
} else {
//定宽
ctx.drawImage(image, 0, 0, image.width, image.height
, 0, (height - width / scale) / 2, width, width / scale)
}
newTexture = Texture.fromCanvas(canvas)
}
//修改相应sprite
cs.forEach((c) => { c.texture = newTexture; })
}
image.src = imageUrl;
}
/**
* 给对应图层修改纹理,直接替换,所以建议原纹理和替换纹理尺寸一致
* @param imagekey 会查找所有用了imagekey的图层
* @param texture 纹理
*/
setTexture(imagekey: string, texture: Texture) {
let cs = this.getSpritesByImageKey(imagekey);
cs.forEach((c) => { c.texture = texture; })
}
/**
* 根据imagekey获取所有用到过的图层
* @param imagekey
* @returns
*/
getSpritesByImageKey(imagekey: string): Sprite[] {
let cs: Sprite[] = [];//找都找了,就全记录吧
for (var i = 0; i < this.children.length; i++) {
if (this.children[i]["imageKey"] == imagekey) {
cs.push(this.children[i] as Sprite)
}
}
return cs;
}
/**
* 用源数据拷贝一份,用相应参数,并未拷贝遮罩或矢量数据
* @param frames 源数据
* @param x 偏移x,默认0
* @param y 偏移y,默认0
* @param scaleX 相对缩放x,默认1
* @param scaleY 相对缩放y,默认1
* @param rotation 相对旋转,角度制,默认0
* @param anchorX 相对锚点x,默认0
* @param anchorY 相对锚点y,默认0
*/
static deepCopyFrames(
frames: FrameEntity[],
x: number = 0,
y: number = 0,
scaleX: number = 1,
scaleY: number = 1,
rotation: number = 0,
anchorX: number = 0,
anchorY: number = 0
) {
var cf = [];
rotation *= Math.PI / 180;
//@ts-ignore
var lt: Matrix = {};
lt.a = Math.cos(rotation) * scaleX;
lt.b = Math.sin(rotation) * scaleX;
lt.c = -Math.sin(rotation) * scaleY;
lt.d = Math.cos(rotation) * scaleY;
lt.tx = x + anchorX - ((anchorX * lt.a) + (anchorY * lt.c));
lt.ty = y + anchorY - ((anchorX * lt.b) + (anchorY * lt.d));
for (var j = 0; j < frames.length; j++) {
var frame = frames[j];
const pt = frame.transform;
var f = { alpha: 0, transform: null };
//透明度
f.alpha = frame.alpha;
f.transform = {
a: (lt.a * pt.a) + (lt.b * pt.c),
b: (lt.a * pt.b) + (lt.b * pt.d),
c: (lt.c * pt.a) + (lt.d * pt.c),
d: (lt.c * pt.b) + (lt.d * pt.d),
tx: (lt.tx * pt.a) + (lt.ty * pt.c) + pt.tx,
ty: (lt.tx * pt.b) + (lt.ty * pt.d) + pt.ty,
};
cf.push(f)
}
return cf;
}
}
/**
* 导出只是当作类型接口用
*/
interface VideoEntity {
/**
* SVGA 文件版本
*/
version: string;
/**
* 影片尺寸
*/
videoSize: {
width: number;
height: number;
};
/**
* 帧率,60,30等每秒
*/
FPS: number;
/**
* 总帧数
*/
frames: number;
/**
* base64图片数据记录
*/
images: {
[key: string]: string
};
/**
* 缓存的纹理
*/
textures: {
[key: string]: Texture
}
/**
* 图片是否已被缓存,缓存全局,注意名字覆盖
*/
hasBeenCached: boolean;
/**
* sprite对象数据
*/
sprites: SpriteEntity[];
}
interface SpriteEntity {
/**
* 暂时没用
*/
matteKey: string;
/**
* 图片key值
*/
imageKey: string;
/**
* 帧数据数组
*/
frames: FrameEntity[];
}
/**
* 还有很多其他数据,暂不需要,比如矢量路径和遮罩路径暂时都无
*/
interface FrameEntity {
/**
* 透明度
*/
alpha: number;
/**
* 2维矩阵数据
*/
transform: {
a: number,
b: number,
c: number,
d: number,
tx: number,
ty: number,
};
/**
* 遮罩数据
*/
maskPath?: { _d: string, _styles: any, _transform: any }
}
class SvgaTrack extends HashObject implements IAnimationTrack {
constructor(
private obj: Container,
private frames: FrameEntity[],
) {
super();
this._instanceType = "SvgaTrack";
}
/**
* 这里用的帧数
* @param time 帧小数
*/
setValue(time: number) {
//处理time
time = Math.round(clamp(time, 0, this.frames.length - 1));
//找对应数据
var frame = this.frames[time], sprite = this.obj;
//layout不晓得干嘛用,暂不管
if (frame.alpha < 0.05) {
sprite.alpha = 0;
} else {
sprite.alpha = frame.alpha;
//先判断transform是否相等
if (!Matrix.isEqual(sprite.transform.localMatrix, frame.transform as Matrix)) {
sprite.transform.localMatrix.copy(frame.transform)
sprite.transform._parentID = -1;
}
//遮罩
if (frame.maskPath) {
//记录一个
if (!sprite["cusMask"]) sprite["cusMask"] = new Shape();
//没遮罩加上
if (!sprite.mask) sprite.mask = sprite.addChild(sprite["cusMask"]);
let mask = sprite.mask;
drawBezierShape(mask, frame.maskPath);
} else if (sprite.mask) {
sprite.removeChild(sprite.mask)
sprite.mask = null;
}
}
}
resetValue() {
this.setValue(0);
}
destroy() {
this.obj = null
}
}
function drawBezierShape(mask: Shape = new Shape, data: { _d: string, _styles: any, _transform: any }, useStyle: boolean = false) {
mask.clear();
const styles = data._styles;
if (useStyle && styles) {//待测试,两个都有的时候是否能正常绘制,毕竟都执行了beginPath
if (styles.stroke) {
mask.beginStroke(
rgb2hex(styles.stroke),
styles.strokeWidth || undefined,
styles.lineCap || undefined,
styles.lineJoin || undefined,
styles.miterLimit || undefined,
styles.stroke[3]
)
}
if (styles.fill) {
mask.beginFill(rgb2hex(styles.fill), styles.fill[3])
}
// if (styles && styles.lineDash) {
// ctx.setLineDash(styles.lineDash);
// }
} else {
//简单绘制
mask.beginFill();
}
//会用到的,再说TODO
if (data._transform) {
//用于修改所有路径的矩阵数据
mask.transform.localMatrix.copy(data._transform)
mask.transform._parentID = -1;
}
let currentPoint = { x: 0, y: 0, x1: 0, y1: 0, x2: 0, y2: 0 };
const validMethods = 'MLHVCSQRZmlhvcsqrz'
const d = data._d.replace(/([a-zA-Z])/g, '|||$1 ').replace(/,/g, ' ');
d.split('|||').forEach(segment => {
if (segment.length == 0) return;
const firstLetter = segment.substr(0, 1);
if (validMethods.indexOf(firstLetter) >= 0) {
const args = segment.substr(1).trim().split(" ");
switch (firstLetter) {
case 'M':
currentPoint.x = Number(args[0]);
currentPoint.y = Number(args[1]);
mask.moveTo(currentPoint.x, currentPoint.y);
break;
case 'm':
currentPoint.x += Number(args[0]);
currentPoint.y += Number(args[1]);
mask.moveTo(currentPoint.x, currentPoint.y);
break;
case 'L':
currentPoint.x = Number(args[0]);
currentPoint.y = Number(args[1]);
mask.lineTo(currentPoint.x, currentPoint.y);
break;
case 'l':
currentPoint.x += Number(args[0]);
currentPoint.y += Number(args[1]);
mask.lineTo(currentPoint.x, currentPoint.y);
break;
case 'H':
currentPoint.x = Number(args[0]);
mask.lineTo(currentPoint.x, currentPoint.y);
break;
case 'h':
currentPoint.x += Number(args[0]);
mask.lineTo(currentPoint.x, currentPoint.y);
break;
case 'V':
currentPoint.y = Number(args[0]);
mask.lineTo(currentPoint.x, currentPoint.y);
break;
case 'v':
currentPoint.y += Number(args[0]);
mask.lineTo(currentPoint.x, currentPoint.y);
break;
case 'C':
currentPoint.x1 = Number(args[0]);
currentPoint.y1 = Number(args[1]);
currentPoint.x2 = Number(args[2]);
currentPoint.y2 = Number(args[3]);
currentPoint.x = Number(args[4]);
currentPoint.y = Number(args[5]);
mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
break;
case 'c':
currentPoint.x1 = currentPoint.x + Number(args[0]);
currentPoint.y1 = currentPoint.y + Number(args[1]);
currentPoint.x2 = currentPoint.x + Number(args[2]);
currentPoint.y2 = currentPoint.y + Number(args[3]);
currentPoint.x += Number(args[4]);
currentPoint.y += Number(args[5]);
mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
break;
case 'S':
if (currentPoint.x1 && currentPoint.y1 && currentPoint.x2 && currentPoint.y2) {
currentPoint.x1 = currentPoint.x - currentPoint.x2 + currentPoint.x;
currentPoint.y1 = currentPoint.y - currentPoint.y2 + currentPoint.y;
currentPoint.x2 = Number(args[0]);
currentPoint.y2 = Number(args[1]);
currentPoint.x = Number(args[2]);
currentPoint.y = Number(args[3]);
mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
} else {
currentPoint.x1 = Number(args[0]);
currentPoint.y1 = Number(args[1]);
currentPoint.x = Number(args[2]);
currentPoint.y = Number(args[3]);
mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
}
break;
case 's':
if (currentPoint.x1 && currentPoint.y1 && currentPoint.x2 && currentPoint.y2) {
currentPoint.x1 = currentPoint.x - currentPoint.x2 + currentPoint.x;
currentPoint.y1 = currentPoint.y - currentPoint.y2 + currentPoint.y;
currentPoint.x2 = currentPoint.x + Number(args[0]);
currentPoint.y2 = currentPoint.y + Number(args[1]);
currentPoint.x += Number(args[2]);
currentPoint.y += Number(args[3]);
mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
} else {
currentPoint.x1 = currentPoint.x + Number(args[0]);
currentPoint.y1 = currentPoint.y + Number(args[1]);
currentPoint.x += Number(args[2]);
currentPoint.y += Number(args[3]);
mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
}
break;
case 'Q':
currentPoint.x1 = Number(args[0]);
currentPoint.y1 = Number(args[1]);
currentPoint.x = Number(args[2]);
currentPoint.y = Number(args[3]);
mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
break;
case 'q':
currentPoint.x1 = currentPoint.x + Number(args[0]);
currentPoint.y1 = currentPoint.y + Number(args[1]);
currentPoint.x += Number(args[2]);
currentPoint.y += Number(args[3]);
mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
break;
case 'A':
break;
case 'a':
break;
case 'Z':
case 'z':
mask.closePath();
break;
default:
break;
}
}
})
if (useStyle && styles) {//待测试,两者是否都能执行
if (styles.fill) mask.endFill();
if (styles.stroke) mask.endStroke();
} else {
//简单,直接绘制
mask.endFill();
}
//返回
return mask;
}
\ No newline at end of file
import { getBezierEasing } from "./BezierEaser";
export function buildBezierEaserProps(
startValue: number[],
endValue: number[],
outV,
inV,
startIndex: number,
endIndex: number,
points,
useH:boolean,
limit?: number
) {
if(useH){
for (var k = startIndex; k < endIndex; k++) {//这时不算最后一个
points[k] = startValue.slice();
}
return
}
var outX, outY, inX, inY, keyValue, perc;
var len = startValue.length;
var fncts, fnc;
if (outV.x.constructor === Array) {
fncts = [];
for (var i = 0; i < len; i++) {
outX = (typeof outV.x[i] === 'undefined') ? outV.x[0] : outV.x[i];
outY = (typeof outV.y[i] === 'undefined') ? outV.y[0] : outV.y[i];
inX = (typeof inV.x[i] === 'undefined') ? inV.x[0] : inV.x[i];
inY = (typeof inV.y[i] === 'undefined') ? inV.y[0] : inV.y[i];
fncts[i] = getBezierEasing(outX, outY, inX, inY).get;
}
} else {
outX = outV.x;
outY = outV.y;
inX = inV.x;
inY = inV.y;
fnc = getBezierEasing(outX, outY, inX, inY).get;
}
var delta = endIndex - startIndex
for (var k = startIndex; k <= endIndex; k++) {
if (limit) {
//小于0的不算了,浪费时间
if (k < 0) continue;
//超出的也不要了
if (k >= limit) break;//遇到那种
}
var frameNum = k;
var newValue = [];
for (var i = 0; i < len; i += 1) {
if (frameNum == endIndex) {
perc = 1;
} else if (frameNum == startIndex) {
perc = 0;
} else {
if (fncts) {
perc = fncts[i]((frameNum - startIndex) / delta);
} else {
perc = fnc((frameNum - startIndex) / delta);
}
}
keyValue = startValue[i] + (endValue[i] - startValue[i]) * perc;
newValue[i] = keyValue;
}
points[k] = newValue
}
}
\ No newline at end of file
export function buildBezierProps(pt1, pt2, pt3, pt4, startIndex, endIndex, points, fnc, limit?: number) {
var bezierData = buildBezierData(pt1, pt2, pt3, pt4);
//处理完所有的点
for (var i = startIndex; i <= endIndex; i++) {
if (limit) {
//小于0的不算了,浪费时间
if (i < 0) continue;
//超出的也不要了
if (i >= limit) break;//遇到那种
}
var perc = fnc((i - startIndex) / (endIndex - startIndex));
var distanceInLine = bezierData.segmentLength * perc;
if (perc == 0) {
points[i] = bezierData.points[0].point;
continue;
}
if (perc == 1) {
points[i] = bezierData.points[bezierData.points.length - 1].point;
continue;
}
//找最近的点
points[i] = findNearest(distanceInLine, bezierData.points);
}
}
export function buildBezierProp(pt1, pt2, pt3, pt4, startIndex, endIndex, time, fnc) {
var bezierData = buildBezierData(pt1, pt2, pt3, pt4);
var perc = fnc((time - startIndex) / (endIndex - startIndex));
......
......@@ -3,8 +3,10 @@ export * from "./FrameAni";
export * from "./ScrollPage";
export * from "./ScrollList";
export * from "./MovieClip";
export * from "./MovieClip";//暂时只有一个项目用过换装,三只松鼠,暂时再放几个版本后删掉
// export * from "./Lottie";
// export { SvgaAni as MovieClip } from "./SvgaAni";//也可以考虑直接修改名字
export * from "./LottieNew";
\ No newline at end of file
export * from "./SvgaAni";
export * from "./Lottie";
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment