Commit dc936b45 authored by wjf's avatar wjf

l

parents
Pipeline #292743 failed with stages
in 0 seconds
# project ignores
node_modules
.vscode
package-lock.json
\ No newline at end of file
# project ignores
node_modules
/src
/build/svgaPlayer.min.js.map
tsConfig.json
webpack.config.js
/.vscode
/scripts
\ No newline at end of file
# SvgaPlayer
兼容web和淘宝小程序的svga文件播放器 1.0.3版本
仅web轻量解析 1.0.4版本
### 网页使用
```html
<div>
<canvas id="testCanvas" width="500" height="500" style="background-color: #000000; "></canvas>
</div>
<script src="../build/svgaPlayer.min.js"></script>
<script>
window.onload = function () {
var canvas = document.getElementById("testCanvas");
//创建一个动画容器,一个canvas,里面可放多个MovieClip,且各自的MovieClip可以修改位置,缩放,旋转,锚点。
var svgaStage = new SvgaPlayer.SvgaStage(canvas)
//循环起来
loop()
function loop(){
svgaStage.flush()
requestAnimationFrame(loop)
}
//加载动画数据
SvgaPlayer.loadSvga("./angel.svga", function (videoItem) {
//实例化动画
var mv = new SvgaPlayer.MovieClip(videoItem);
//添加进动画容器
svgaStage.addChild(mv)
//可设置canvas的尺寸,尽量自行适配,canva实际尺寸和canvas的style尺寸
canvas.width = mv.videoWidth;
canvas.height = mv.videoHeight;
//一些api的方法
mv.stop();
setTimeout(() => {
mv.gotoAndPlay(1, false)
}, 200)
setTimeout(() => {
mv.startAniRange(1, mv.totalFrames / 2, 2, () => {
console.log(112)
})
}, 2000)
// mv.rotation = 45
// mv.x= 100
// mv.scaleX=1.5
// mv.y= 200
mv.anchorX= 750/2;
mv.anchorY= 750/2;
setInterval(()=>{
mv.rotation+=1
},16.7)
})
//再加一个
SvgaPlayer.loadSvga("./step1-1.svga", function (videoItem) {
var mv = new SvgaPlayer.MovieClip(videoItem);
svgaStage.addChild(mv)
}, function (error) {
alert(error);
})
}
</script>
```
### npm使用
```js
npm install @mrkwon/svga-player
import { loadSvga, SvgaStage, MovieClip } from "@mrkwon/svga-player"
var svgaStage = new SvgaStage(canvas)
loadSvga("./svga/step1-1.svga", (v) => {
var mv = new MovieClip(videoItem);
//添加进动画容器
svgaStage.addChild(mv)
}, (err) => { console.log(err) })
//循环起来
loop()
function loop(){
svgaStage.flush()
requestAnimationFrame(loop)
}
```
### 淘宝小程序使用
```js
import { loadSvga, SvgaStage, MovieClip } from "@mrkwon/svga-player"
Page({
onCanvasReady() {
this.canvas = my.createCanvas({
id: 'canvas',
success: (canvas) => {
var systemInfo = my.getSystemInfoSync();
//适配尺寸
canvas.width = 750 * systemInfo.pixelRatio;
canvas.height = 500 * systemInfo.pixelRatio;
//创建一个动画容器,一个canvas
var svgaStage = new SvgaStage(canvas);
//加载动画数据,域名必须是阿里系的白名单域名,或者cloud链接
loadSvga("./angel.svga", function (videoItem) {
//实例化动画
var mv = new MovieClip(videoItem);
//添加进动画容器
svgaStage.addChild(mv)
}, function (error) {
alert(error);
})
//再加一个
loadSvga("./step1-1.svga", function (videoItem) {
var mv = new MovieClip(videoItem);
svgaStage.addChild(mv)
}, function (error) {
alert(error);
})
//循环起来
loop()
function loop(){
svgaStage.flush()
canvas.requestAnimationFrame(loop)
}
}
});
}
})
```
\ No newline at end of file
declare namespace SvgaPlayer{
export enum Event {
/**
* 每帧的监听
*/
ENTER_FRAME = "onEnterFrame",
/**
* 最后一帧的监听
*/
END_FRAME = "onEndFrame"
}
export class EventDispatcher {
protected eventTypes: any;
constructor();
addEventListener(type: string, listener: Function, context?: any): this;
/**
* 监听一次
* @param type
* @param listener
* @param context
* @param useCapture
*/
once(type: string, listener: Function, context?: any): this;
dispatchEvent(event: string, data?: any): boolean;
/**
* 移除对应类型的侦听
* @method removeEventListener
* @public
* @since 1.0.0
* @param {string} type 要移除的侦听类型
* @param {Function} listener 及侦听时绑定的回调方法
* @param context listener和context都相等的才移除,默认自身
*/
removeEventListener(type: string, listener: Function, context?: any): this;
/**
* 移除对象中所有的侦听
* @method removeAllEventListener
* @public
* @since 1.0.0
*/
removeAllEventListener(): void;
hasEventListener(type: string): boolean;
destroy(): void;
}
export class MovieClip extends EventDispatcher {
private transform;
private dirty;
x: number;
y: number;
scaleX: number;
scaleY: number;
/**
* 角度制
*/
rotation: number;
anchorX: number;
anchorY: number;
/**
* 锁步将按时间间隔来执行动画
*/
lockStep: boolean;
/**
* mc的当前帧,从1开始
* @property currentFrame
* @public
* @since 1.0.0
* @type {number}
* @default 1
* @readonly
*/
readonly currentFrame: number;
/**
* @property _curFrame
* @type {number}
* @private
* @since 2.0.0
* @default 1
*/
private _curFrame;
/**
* 当前动画是否处于播放状态
* @property isPlaying
* @readOnly
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
readonly isPlaying: boolean;
/**
* @property _isPlaying
* @type {boolean}
* @private
* @since 2.0.0
* @default true
*/
private _isPlaying;
/**
* 动画的播放方向,是顺着播还是在倒着播
* @property isFront
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
readonly isFront: boolean;
/**
* @property _isFront
* @type {boolean}
* @private
* @default true
*/
private _isFront;
/**
* 当前动画的总帧数
* @property totalFrames
* @public
* @since 1.0.0
* @type {number}
* @default 1
* @readonly
*/
readonly totalFrames: number;
readonly videoWidth: number;
readonly videoHeight: number;
/**
* 锁步的时间间隔,按fps定,毫秒
*/
private timeInterval;
/**
* 前提引擎按60设置
*/
private deltaFrame;
/**
* 中间帧计时
*/
private frameCount;
/**
* 动画数据
*/
videoItem: SvgaParser.VideoEntity;
/**
* 构造函数
* @method MovieClip
* @public
* @param mv VideoEntity格式,这里不给了
*/
constructor(mv: SvgaParser.VideoEntity);
/**
* 可以手动用init,
* @param mv
*/
init(mv: SvgaParser.VideoEntity): void;
/**
* 调用止方法将停止当前帧
* @method stop
* @public
* @since 1.0.0
*/
stop(): void;
/**
* 将播放头向后移一帧并停在下一帧,如果本身在最后一帧则不做任何反应
* @method nextFrame
* @since 1.0.0
* @public
*/
nextFrame(): void;
/**
* 将播放头向前移一帧并停在下一帧,如果本身在第一帧则不做任何反应
* @method prevFrame
* @since 1.0.0
* @public
*/
prevFrame(): void;
/**
* 将播放头跳转到指定帧并停在那一帧,如果本身在第一帧则不做任何反应
* @method gotoAndStop
* @public
* @since 1.0.0
* @param {number} frameIndex 批定帧的帧数或指定帧的标签名
*/
gotoAndStop(frameIndex: number): void;
/**
* 如果当前时间轴停在某一帧,调用此方法将继续播放.
* @method play
* @public
* @since 1.0.0
*/
play(isFront?: boolean): void;
/**
* @property _lastFrame
* @type {number}
* @private
* @default 0
*/
private _lastFrame:number;
/**
* 刚执行到的帧数,用于帧监听时判断用,刚好执行到当前帧,而不是之前保留的状态
* 不是60fps的videoItem的中间有几帧curFrame会不变,判断只执行一次监听时会出错,刚好动画满帧60fps时就无所谓
*/
readonly isInTimeFrame: boolean;
/**
* 将播放头跳转到指定帧并从那一帧开始继续播放
* @method gotoAndPlay
* @public
* @since 1.0.0
* @param {number} frameIndex 批定帧的帧数或指定帧的标签名
* @param {boolean} isFront 跳到指定帧后是向前播放, 还是向后播放.不设置些参数将默认向前播放
*/
gotoAndPlay(frameIndex: number, isFront?: boolean): void;
/**
* 记录的startAniRange对应的fun
*/
private startAniRangeFun;
/**
* 优先级最高,会覆盖
* @param beginFrame 默认1
* @param endFrame 默认 this.totalFrames
* @param loops 默认1 0
*/
startAniRange(beginFrame?: number, endFrame?: number, loops?: number, callback?: Function): void;
/**
* 开始时间,每次有play的时候就需要重置now
* 锁步思想,设置开始时间,后面每帧实际时间与开始时间相减,得到当前帧数
*
*/
private startTime;
/**
* 开始时的frame
*/
private startFrame;
/**
* 与startFrame相间隔的帧数量,绝对值
*/
private lastDeltaFrame;
/**
* 锁步时的每次end的标识
*/
private _endMark;
commonDeltaTime: number;
updateFrame(): void;
private getCurFrameWhenLockStep;
/**
* 修改自身位置
*/
updateTransform(): void;
destroy(): void;
}
export const _bitmapCache: {};
export class SvgaStage extends EventDispatcher {
/**
* 用于 requestAnimationFrame,cancelAnimationFrame ,createImage
*/
private canvas;
private context;
private children;
resolution: number;
/**
*
* @param canvas 渲染的canvas
* @param resolution canvas和显示对象的尺寸比例
*/
constructor(canvas: any, resolution?: number);
addChild(mv: MovieClip): MovieClip;
addChildAt(mv: MovieClip, index?: number): MovieClip;
removeChild(mv: MovieClip): MovieClip;
removeChildAt(index: number): any;
removeAllChildren(): MovieClip[];
/**
* 将所有MovieClip画到canvas上
*/
render(): void;
private drawSprite;
flush(): void;
}
export const loadSvga: typeof SvgaParser.loadSvga;}
declare module "svga-player" {export = SvgaPlayer;}
\ No newline at end of file
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.
{
"name": "@mrkwon/svga-player",
"version": "1.0.4",
"description": "",
"main": "build/svgaPlayer.min.js",
"types": "build/SvgaPlayer.d.ts",
"dependencies": {},
"devDependencies": {
"svga-parser": "^1.0.2",
"ts-loader": "^4.0.0",
"typescript": "^2.7.2",
"uglifyjs-webpack-plugin": "^2.1.2",
"webpack": "^4.1.0",
"webpack-cli": "^3.3.2"
},
"scripts": {
"declare": "node scripts/declare.js src/index.ts",
"build": "webpack",
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack -w",
"watch": "webpack --watch"
},
"author": "MrKwon",
"license": "ISC",
"keywords": [
"svga",
"player",
"canvas"
]
}
const fs = require('fs');
const ts = require("typescript");
const regLine = /(export|declare)((?!from).)*/g;
function compile(fileNames, options) {
const host = ts.createCompilerHost(options);
const exports = [];
host.writeFile = (fileName, fileContent) => {
const result = fileContent.match(regLine);
for (let line of result) {
if (line.match(/export (default)? \w+;/)) {
continue;
}
if (line.endsWith(';')) {
if (!line.startsWith('_') && !line.startsWith('export default function')) {
exports.push(line);
}
} else {
if (line.endsWith('{')) {
let start = fileContent.indexOf(line);
const block = fileContent.substring(start, fileContent.indexOf('\n}', start) + 2);
if (!block.startsWith('_')) {
exports.push(block);
}
}
}
}
};
const program = ts.createProgram(fileNames, options, host);
program.emit();
let allExports = exports.join('\n\n')
.replace(/export default _default;/g, '')
.replace(/export declare/g, 'export ')
.replace(/export default/g, 'export ')
.replace(/declare /g, 'export ')
;
// const content = `declare module FYGE{${allExports}}`;
const content = `declare namespace SvgaPlayer{\n${allExports}}\ndeclare module "svga-player" {export = SvgaPlayer;}`;
fs.writeFileSync('build/SvgaPlayer.d.ts', content);
}
compile(process.argv.slice(2), {
allowJs: true,
declaration: true,
emitDeclarationOnly: true,
});
export enum Event {
/**
* 每帧的监听
*/
ENTER_FRAME = "onEnterFrame",
/**
* 最后一帧的监听
*/
END_FRAME = "onEndFrame"
}
/**
* 只为动画用的事件
*/
export class EventDispatcher {
protected eventTypes: any = {};
public constructor() {
}
public addEventListener(type: string, listener: Function, context?: any): this {
if (!type) {
throw new Error("添加侦听的type值为undefined");
}
if (!listener) {
throw new Error("侦听回调函数不能为null");
}
let s = this;
let eventTypes = s.eventTypes;
if (!eventTypes[type]) {
eventTypes[type] = [];
}
for (var i = 0, len = eventTypes[type].length; i < len; i++) {
let ee: EE = eventTypes[type][i]
if (ee.fn === listener && ee.context === context) {
console.log("已添加过该事件")
return s
}
}
eventTypes[type].unshift(new EE(listener, context || s));
return s
}
/**
* 监听一次
* @param type
* @param listener
* @param context
* @param useCapture
*/
public once(type: string, listener: Function, context?: any): this {
if (!type) {
throw new Error("添加侦听的type值为undefined");
}
if (!listener) {
throw new Error("侦听回调函数不能为null");
}
let s = this;
let eventTypes = s.eventTypes;
if (!eventTypes[type]) {
eventTypes[type] = [];
}
//考虑是否要检查已添加过该事件
eventTypes[type].unshift(new EE(listener, context || s, true));
return s
}
public dispatchEvent(event: string, data: any = null): boolean {
let s = this;
let listeners: EE[] = s.eventTypes[event];
if (listeners) {
let len = listeners.length;
for (let i = len - 1; i >= 0; i--) {
if (listeners[i]) {
let listener = listeners[i];
listener.fn.call(listener.context, data)
//必须做单独指向,因为有可能出现上面的fn.里执行的就是removeEventListener,导致listeners[i]不存在
if (listener.once) {
s.removeEventListener(event, listener.fn, listener.context);
}
// listeners[i](event);
} else {
//空的直接移除,
listeners.splice(i, 1);
}
}
return true;
} else {
return false;
}
}
/**
* 移除对应类型的侦听
* @method removeEventListener
* @public
* @since 1.0.0
* @param {string} type 要移除的侦听类型
* @param {Function} listener 及侦听时绑定的回调方法
* @param context listener和context都相等的才移除,默认自身
*/
public removeEventListener(type: string, listener: Function, context?: any): this {
let s = this;
let listeners: EE[] = s.eventTypes[type];
if (listeners) {
let len = listeners.length;
let thisObject = context || s;
for (let i = len - 1; i >= 0; i--) {
if (listeners[i].fn === listener && listeners[i].context === thisObject) {
listeners.splice(i, 1);
}
}
}
return s
}
/**
* 移除对象中所有的侦听
* @method removeAllEventListener
* @public
* @since 1.0.0
*/
public removeAllEventListener() {
this.eventTypes = {};
}
public hasEventListener(type: string): boolean {
let s = this;
if (s.eventTypes[type] && s.eventTypes[type].length > 0) {
return true
}
return false;
}
destroy(): void {
let s = this;
s.removeAllEventListener();
s.eventTypes = null;
}
}
/**
* 为了实现带入this和once
* 暂不做回收处理,因为存在引用fn,context.内存销毁可能出问题
* 如果非要作回收,回收时必须将fn,context置null;这样还有必要回收?
*/
class EE {
fn: Function;
context: any;
once: boolean;
constructor(fn: Function, context: any, once: boolean = false) {
this.fn = fn;
this.context = context;
this.once = once;
}
}
import { EventDispatcher, Event } from "./EventDispatcher";
/**
* 动画类
*/
export class MovieClip extends EventDispatcher {
//如果需要修改自身的位置,缩放,旋转,锚点
//x,y,
private transform = {
x: 0,
y: 0,
sx: 1,
sy: 1,
r: 0,
ax: 0,
ay: 0,
}
private dirty: boolean
get x() {
return this.transform.x
}
set x(value: number) {
if (this.transform.x === value) return;
this.dirty = true;
this.transform.x = value;
}
get y() {
return this.transform.y
}
set y(value: number) {
if (this.transform.y === value) return;
this.dirty = true;
this.transform.y = value;
}
get scaleX() {
return this.transform.sx
}
set scaleX(value: number) {
if (this.transform.sx === value) return;
this.dirty = true;
this.transform.sx = value;
}
get scaleY() {
return this.transform.sy
}
set scaleY(value: number) {
if (this.transform.sy === value) return;
this.dirty = true;
this.transform.sy = value;
}
/**
* 角度制
*/
get rotation() {
return this.transform.r
}
set rotation(value: number) {
if (this.transform.r === value) return;
this.dirty = true;
this.transform.r = value;
}
get anchorX() {
return this.transform.ax
}
set anchorX(value: number) {
if (this.transform.ax === value) return;
this.dirty = true;
this.transform.ax = value;
}
get anchorY() {
return this.transform.ay
}
set anchorY(value: number) {
if (this.transform.ay === value) return;
this.dirty = true;
this.transform.ay = value;
}
/**
* 锁步将按时间间隔来执行动画
*/
public lockStep: boolean = false;
/**
* mc的当前帧,从1开始
* @property currentFrame
* @public
* @since 1.0.0
* @type {number}
* @default 1
* @readonly
*/
public get currentFrame(): number {
return this._curFrame;
}
/**
* @property _curFrame
* @type {number}
* @private
* @since 2.0.0
* @default 1
*/
private _curFrame: number = 1;
/**
* 当前动画是否处于播放状态
* @property isPlaying
* @readOnly
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
public get isPlaying(): boolean {
return this._isPlaying;
}
/**
* @property _isPlaying
* @type {boolean}
* @private
* @since 2.0.0
* @default true
*/
private _isPlaying: boolean = true;
/**
* 动画的播放方向,是顺着播还是在倒着播
* @property isFront
* @public
* @since 1.0.0
* @type {boolean}
* @default true
* @readonly
*/
get isFront(): boolean {
return this._isFront;
}
/**
* @property _isFront
* @type {boolean}
* @private
* @default true
*/
private _isFront: boolean = true;
/**
* 当前动画的总帧数
* @property totalFrames
* @public
* @since 1.0.0
* @type {number}
* @default 1
* @readonly
*/
get totalFrames(): number {
return this.videoItem && this.videoItem.frames || 0
};
get videoWidth(): number {
return this.videoItem && this.videoItem.videoSize.width || 0
};
get videoHeight(): number {
return this.videoItem && this.videoItem.videoSize.height || 0
};
/**
* 锁步的时间间隔,按fps定,毫秒
*/
private timeInterval;
/**
* 前提引擎按60设置
*/
private deltaFrame: number;
/**
* 中间帧计时
*/
private frameCount: number = 0;
/**
* 动画数据
*/
videoItem: SvgaParser.VideoEntity;
/**
* 构造函数
* @method MovieClip
* @public
* @param mv VideoEntity格式,这里不给了
*/
constructor(mv: SvgaParser.VideoEntity) {
super();
//初始化
if (mv) this.init(mv);
}
/**
* 可以手动用init,
* @param mv
*/
init(mv: SvgaParser.VideoEntity) {
this.videoItem = mv;
//记录基本信息,fps,每秒输出帧数,frames,总帧数,videoSize暂时不管
//如果fps小于60怎么处理。update时怎么处理
this.timeInterval = 1000 / mv.FPS;
this.startTime = Date.now();
this.startFrame = 1;
//间隔帧数,
this.deltaFrame = 60 / mv.FPS;
this.frameCount = this.deltaFrame;
this._curFrame = 1;
//拷贝一边frames
for (var i = 0; i < mv.sprites.length; i++) {
let sprite = mv.sprites[i];
//@ts-ignore
sprite.framesOri = deepCopyFrames(sprite.frames)
}
}
/**
* 调用止方法将停止当前帧
* @method stop
* @public
* @since 1.0.0
*/
public stop(): void {
let s = this;
s._isPlaying = false;
}
/**
* 将播放头向后移一帧并停在下一帧,如果本身在最后一帧则不做任何反应
* @method nextFrame
* @since 1.0.0
* @public
*/
public nextFrame(): void {
let s = this;
if (s._curFrame < s.totalFrames) {
s._curFrame++;
}
s._isPlaying = false;
}
/**
* 将播放头向前移一帧并停在下一帧,如果本身在第一帧则不做任何反应
* @method prevFrame
* @since 1.0.0
* @public
*/
public prevFrame(): void {
let s = this;
if (s._curFrame > 1) {
s._curFrame--;
}
s._isPlaying = false;
}
/**
* 将播放头跳转到指定帧并停在那一帧,如果本身在第一帧则不做任何反应
* @method gotoAndStop
* @public
* @since 1.0.0
* @param {number} frameIndex 批定帧的帧数或指定帧的标签名
*/
public gotoAndStop(frameIndex: number): void {
let s: any = this;
s._isPlaying = false;
if (frameIndex > s.totalFrames) {
frameIndex = s.totalFrames;
}
if (frameIndex < 1) {
frameIndex = 1;
}
s._curFrame = <number>frameIndex;
}
/**
* 如果当前时间轴停在某一帧,调用此方法将继续播放.
* @method play
* @public
* @since 1.0.0
*/
public play(isFront: boolean = true): void {
let s = this;
s.frameCount = s.deltaFrame;
s.startTime = Date.now();
s.startFrame = s._curFrame;
s._isPlaying = true;
s._isFront = isFront;
}
/**
* @property _lastFrame
* @type {number}
* @private
* @default 0
*/
private _lastFrame: number = 0;
/**
* 刚执行到的帧数,用于帧监听时判断用,刚好执行到当前帧,而不是之前保留的状态
* 不是60fps的videoItem的中间有几帧curFrame会不变,判断只执行一次监听时会出错,刚好动画满帧60fps时就无所谓
*/
public get isInTimeFrame(): boolean {
//相等时就是刚开始的curFrame
return this.frameCount == this.deltaFrame;
}
/**
* 将播放头跳转到指定帧并从那一帧开始继续播放
* @method gotoAndPlay
* @public
* @since 1.0.0
* @param {number} frameIndex 批定帧的帧数或指定帧的标签名
* @param {boolean} isFront 跳到指定帧后是向前播放, 还是向后播放.不设置些参数将默认向前播放
*/
public gotoAndPlay(frameIndex: number, isFront: boolean = true): void {
let s: any = this;
s._isFront = isFront;
s._isPlaying = true;
if (frameIndex > s.totalFrames) {
frameIndex = s.totalFrames;
}
if (frameIndex < 1) {
frameIndex = 1;
}
s.frameCount = s.deltaFrame;
s.startTime = Date.now();
s._curFrame = <number>frameIndex;
s.startFrame = s._curFrame;
}
/**
* 记录的startAniRange对应的fun
*/
private startAniRangeFun: Function
/**
* 优先级最高,会覆盖
* @param beginFrame 默认1
* @param endFrame 默认 this.totalFrames
* @param loops 默认1 0
*/
public startAniRange(
beginFrame: number = 1,
endFrame: number = this.totalFrames,
loops: number = 1,
callback?: Function
) {
if (beginFrame < 1) {
beginFrame = 1;
}
if (beginFrame > this.totalFrames) {
beginFrame = this.totalFrames;
}
if (endFrame < 1) {
endFrame = 1;
}
if (endFrame > this.totalFrames) {
endFrame = this.totalFrames;
}
if (beginFrame === endFrame) {
this.gotoAndStop(beginFrame)
//如果相等
return
} else if (beginFrame < endFrame) {
this._isFront = true;
} else {
this._isFront = false;
var temp = beginFrame;
beginFrame = endFrame;
endFrame = temp;
}
//移除原先的绑定吧
if (this.startAniRangeFun) this.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this)
this._curFrame = beginFrame;
//赋值count最大
this.frameCount = this.deltaFrame;
this.startTime = Date.now();
this.startFrame = this._curFrame;
this._isPlaying = true;
let loopCount = loops ? (loops + 0.5 >> 0) : Infinity;
let self = this;
this.addEventListener(Event.ENTER_FRAME, this.startAniRangeFun = (e) => {
if (self._isFront) {
//用大于等于可以避免锁步时出现的问题
if (self.currentFrame >= endFrame || self._endMark) {
loopCount--;
if (loopCount <= 0) {
self.gotoAndStop(endFrame);
self.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this);
this.startAniRangeFun = null;
callback && callback();
} else {
self.gotoAndPlay(beginFrame);
}
}
} else {
if (self.currentFrame <= beginFrame || self._endMark) {
loopCount--
if (loopCount <= 0) {
self.gotoAndStop(beginFrame);
self.removeEventListener(Event.ENTER_FRAME, this.startAniRangeFun, this);
this.startAniRangeFun = null;
callback && callback();
} else {
self.gotoAndPlay(endFrame, false);
}
}
}
}, this)
}
/**
* 开始时间,每次有play的时候就需要重置now
* 锁步思想,设置开始时间,后面每帧实际时间与开始时间相减,得到当前帧数
*
*/
private startTime: number;
/**
* 开始时的frame
*/
private startFrame: number;
/**
* 与startFrame相间隔的帧数量,绝对值
*/
private lastDeltaFrame: number
/**
* 锁步时的每次end的标识
*/
private _endMark: boolean
commonDeltaTime = 1000 / 60;
updateFrame() {
var s = this;
//1帧的时候也有相应的frameCount,无用,return
if (s.totalFrames == 0 || s.totalFrames == 1) return;
let isNeedUpdate = false;
if (s._lastFrame != s._curFrame) {
//帧不相等
isNeedUpdate = true;
s._lastFrame = s._curFrame;
//锁步的时候
s.startTime = Date.now();
s.startFrame = s._curFrame;
s._endMark = false;
} else {
if (s._isPlaying) {
if (s.lockStep) {
isNeedUpdate = s.getCurFrameWhenLockStep();
} else {
if (--s.frameCount == 0) {
s.frameCount = s.deltaFrame;
isNeedUpdate = true;
if (s._isFront) {
s._curFrame++;
if (s._curFrame > s.totalFrames) {
s._curFrame = 1;
}
} else {
s._curFrame--;
if (s._curFrame < 1) {
s._curFrame = s.totalFrames;
}
}
s._lastFrame = s._curFrame;
}
}
}
}
//如果需要更新 //真实数据应该是s._curFrame - 1
//事件播放结束监听
if (!s.lockStep) {
if (((s._curFrame == 1 && !s._isFront) || (s._curFrame == s.totalFrames && s._isFront)) && s.hasEventListener(Event.END_FRAME)) {
s.dispatchEvent(Event.END_FRAME, s._curFrame);
}
} else {
//锁步的时候另外判断
if (s._endMark && s.hasEventListener(Event.END_FRAME)) {
s.dispatchEvent(Event.END_FRAME, s._curFrame);
}
}
}
private getCurFrameWhenLockStep() {
var dateNow = Date.now()
//相差
var deltaFrame = ((dateNow - this.startTime) / this.timeInterval) >> 0;
//间隔帧数与上一帧一致,就不执行
if (deltaFrame == this.lastDeltaFrame) {
//设置不等
this.frameCount = 0;
return false
}
this._endMark = false;
//相等,刚好执行切换
this.frameCount = this.deltaFrame;
this.lastDeltaFrame = deltaFrame
if (this._isFront) {
//取余数
this._curFrame = (this.startFrame + deltaFrame) % this.totalFrames;
if (this._curFrame == 0) {
this._curFrame = this.totalFrames;
this._endMark = true;
}
//当上一帧大于_curFrame,并且上一帧不是totalFrames时,说明跳过了最后一帧
else if (this._lastFrame > this._curFrame &&
this._lastFrame != this.totalFrames) {
this._endMark = true;
}
} else {
this._curFrame = (this.startFrame - deltaFrame) % this.totalFrames;
if (this._curFrame == 0) {
this._curFrame = this.totalFrames;
} else if (this._curFrame < 0) {
this._curFrame += this.totalFrames;
}
if (this._curFrame == 1) {
this._endMark = true;
}
//当上一帧小于_curFrame,并且上一帧不是1时,说明跳过了第一帧
else if (this._lastFrame < this._curFrame &&
this._lastFrame != 1) {
this._endMark = true;
}
}
this._lastFrame = this._curFrame;
return true
}
/**
* 修改自身位置
*/
public updateTransform() {
if (!this.dirty) return;
this.dirty = false
var sprites = this.videoItem.sprites;
sprites.forEach((sprite, index) => {
//重新计算一边
sprite.frames = deepCopyFrames(
sprite["framesOri"],
this.x,
this.y,
this.scaleX,
this.scaleY,
this.rotation,
this.anchorX,
this.anchorY
)
});
}
public destroy(): void {
//todo-清除相应的数据引用
this.videoItem = null;
super.destroy();
}
}
function deepCopyFrames(
frames,
x = 0,
y = 0,
scaleX = 1,
scaleY = 1,
rotation = 0,
anchorX = 0,
anchorY = 0
) {
var cf = [];
rotation *= Math.PI / 180;
var lt: any = {};
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,
//需要反一下,父级和子级
a: (pt.a * lt.a) + (pt.b * lt.c),
b: (pt.a * lt.b) + (pt.b * lt.d),
c: (pt.c * lt.a) + (pt.d * lt.c),
d: (pt.c * lt.b) + (pt.d * lt.d),
tx: (pt.tx * lt.a) + (pt.ty * lt.c) + lt.tx,
ty: (pt.tx * lt.b) + (pt.ty * lt.d) + lt.ty,
};
cf.push(f)
}
return cf;
}
import { EventDispatcher, Event } from "./EventDispatcher";
import { MovieClip } from "./MovieClip";
import { VideoEntity } from "svga-parser";
/**
* 缓存的图片,缓存好的图片才能绘制,key为imageKey
*/
export const _bitmapCache = {};
/**
* svga播放动画的容器
*
*/
export class SvgaStage extends EventDispatcher {
//适配的尺寸怎么弄
/**
* 用于 requestAnimationFrame,cancelAnimationFrame ,createImage
*/
private canvas;
private context: CanvasRenderingContext2D;
private requestAnimationFrame
private children: MovieClip[];
resolution: number;
/**
*
* @param canvas 渲染的canvas
* @param resolution canvas和显示对象的尺寸比例
*/
constructor(canvas, resolution: number = 1) {
super();
this.canvas = canvas;
this.context = canvas.getContext("2d");
this.children = [];
//计算canvas尺寸和动画显示尺寸之间的比例,暂时只有缩放
this.resolution = resolution;
this.requestAnimationFrame = canvas.requestAnimationFrame&&canvas.requestAnimationFrame.bind(canvas)||window.requestAnimationFrame.bind(window)
//直接刷吧
// this.flush();
}
addChild(mv: MovieClip): MovieClip {
return this.addChildAt(mv, this.children.length);
}
addChildAt(mv: MovieClip, index: number = this.children.length) {
var s = this
//如果这里面已经有了,就先移掉
var indexOri = this.children.indexOf(mv);
if (indexOri > -1) this.removeChildAt(indexOri);
//自己添加
var len = s.children.length;
if (index >= len) {
s.children[s.children.length] = mv;
} else if (index == 0 || index < 0) {
s.children.unshift(mv);
} else {
s.children.splice(index, 0, mv);
}
return mv;
}
removeChild(mv: MovieClip) {
const index = this.children.indexOf(mv);
if (index === -1) return null;
this.removeChildAt(index);
return mv
}
removeChildAt(index: number) {
let s = this;
let child: any;
let len = s.children.length - 1;
if (len < 0) return;
if (index == len) {
child = s.children.pop();
} else if (index == 0) {
child = s.children.shift();
} else {
child = s.children.splice(index, 1)[0];
}
return child
}
removeAllChildren() {
this.children = [];
return this.children.slice();
}
/**
* 将所有MovieClip画到canvas上
*/
render() {
//清空画布
this.context.setTransform(1, 0, 0, 1, 0, 0)
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (var i = 0; i < this.children.length; i++) {
let child = this.children[i];
//更新位置信息
child.updateTransform();
//更新帧数据
child.updateFrame();
//派发事件
child.dispatchEvent(Event.ENTER_FRAME, child.currentFrame)
//获取当前帧
let frame = child.currentFrame - 1;
//根据当前帧绘制
var sprites = child.videoItem.sprites;
sprites.forEach((sprite, index) => {
var imageKey = sprite.imageKey;
//加载中
if (_bitmapCache[imageKey] == 1) return;
//加载
if (!_bitmapCache[imageKey]) {
let src = child.videoItem.images[imageKey];
if (src.indexOf("iVBO") === 0 || src.indexOf("/9j/2w") === 0) {
let imgTag = this.canvas.createImage && this.canvas.createImage() || new Image();
imgTag.onload = function () {
_bitmapCache[imageKey] = imgTag;
};
imgTag.src = 'data:image/png;base64,' + src;
_bitmapCache[imageKey] = 1;//表示在加载中
}
}
//绘制
else {
this.drawSprite(sprite, frame)
}
});
}
}
private drawSprite(sprite, frame) {
let frameItem = sprite.frames[frame];
if (frameItem.alpha < 0.05) return;
var ctx = this.context
ctx.globalAlpha = frameItem.alpha;
ctx.setTransform(
frameItem.transform.a * this.resolution,
frameItem.transform.b * this.resolution,
frameItem.transform.c * this.resolution,
frameItem.transform.d * this.resolution,
frameItem.transform.tx * this.resolution,
frameItem.transform.ty * this.resolution
)
ctx.drawImage(_bitmapCache[sprite.imageKey], 0, 0);
}
flush() {
var s = this;
if (!s.canvas) return;
s.render();
// (s.canvas.requestAnimationFrame || window.requestAnimationFrame)(s.flush.bind(s))
// this.requestAnimationFrame(s.flush.bind(s));
}
}
import * as SvgaParser from "svga-parser"
export const loadSvga = SvgaParser.loadSvga;
export * from "./SvgaStage"
export * from "./MovieClip"
export * from "./EventDispatcher"
\ No newline at end of file
File added
<html>
<body style="text-align: center">
<div>
<!-- <div id="testCanvas" style="background-color: #000000; width: 500px; height: 500px; margin: auto"></div> -->
<canvas id="testCanvas" width="500" height="500" style="background-color: #000000; "></canvas>
</div>
<script src="../build/svgaPlayer.min.js"></script>
<script>
window.onload = function () {
var canvas = document.getElementById("testCanvas");
//创建一个动画容器,一个canvas
var svgaStage = new SvgaPlayer.SvgaStage(canvas)
//加载动画数据
SvgaPlayer.loadSvga("./angel.svga", function (videoItem) {
//实例化动画
var mv = new SvgaPlayer.MovieClip(videoItem);
//添加进动画容器
svgaStage.addChild(mv)
//可设置canvas的尺寸,尽量自行适配,canva实际尺寸和canvas的style尺寸
canvas.width = mv.videoWidth;
canvas.height = mv.videoHeight;
//一些api的方法
mv.stop();
setTimeout(() => {
mv.gotoAndPlay(1, false)
}, 200)
setTimeout(() => {
mv.startAniRange(1, mv.totalFrames / 2, 2, () => {
console.log(112)
})
}, 2000)
// mv.rotation = 45
// mv.x= 100
// mv.scaleX=1.5
// mv.y= 200
mv.anchorX= 750/2;
mv.anchorY= 750/2;
setInterval(()=>{
mv.rotation+=1
},16.7)
}, function (error) {
alert(error.message);
})
//启循环
loop()
function loop(){
svgaStage.flush()
requestAnimationFrame(loop)
}
//再加一个
SvgaPlayer.loadSvga("./step1-1.svga", function (videoItem) {
var mv = new SvgaPlayer.MovieClip(videoItem);
svgaStage.addChild(mv)
}, function (error) {
alert(error);
})
}
</script>
</body>
</html>
\ No newline at end of file
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": true,
"removeComments": true,
"noEmitOnError":true,
// "declarationDir": "types",
// "declaration": true,
"outDir":"dist",
/*"outFile": "./index.js",*/
"lib": [
"es5",
"dom",
"es2015.promise"
]
},
"exclude": [
"node_modules"
]
}
\ No newline at end of file
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
var path = require('path');
var webpack = require('webpack')
module.exports = {
mode: "production",
entry: {
"svgaPlayer.min": "./src/index.ts",
},
output: {
path: __dirname,
filename: "build/[name].js",
libraryTarget: 'umd',
library: 'SvgaPlayer',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
devtool: 'source-map',
plugins: [
new UglifyJSPlugin(
{ sourceMap: true }
), new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
],
resolve: {
extensions: ['.webpack.js', '.web.js', '.ts', '.js']
}
}
\ 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