diff --git a/src/animator.js b/src/animator.js index 95b07e3..a7b9714 100644 --- a/src/animator.js +++ b/src/animator.js @@ -117,6 +117,29 @@ export default class { return state; } + getPlayState(ntime) { + const timeline = this.timeline, + {iterations, duration, endDelay} = this[_timing]; + let state = 'running'; + + if(timeline == null) { + state = 'idle'; + } else if(timeline.paused) { + state = 'paused'; + } else if(timeline.getCurrentTime(ntime) < 0) { // 开始 pending + state = 'pending'; + } else { + const ed = timeline.getCurrentTime(ntime) - iterations * duration; + if(ed > 0 && ed < endDelay) { // 结束 pending + state = 'pending'; + } else if(ed >= endDelay) { + state = 'finished'; + } + } + return state; + } + + get progress() { if(!this.timeline) return 0; @@ -148,6 +171,37 @@ export default class { return p; } + getProgress(ntime) { + if(!this.timeline) return 0; + + const {duration, iterations} = this[_timing]; + const timeline = this.timeline, + playState = this.getPlayState(ntime); + + let p; + + if(playState === 'idle') { + p = 0; + } else if(playState === 'paused' && timeline.getCurrentTime(ntime) < 0) { + p = 0; + } else if(playState === 'pending') { + if(timeline.getCurrentTime(ntime) < 0) { + p = 0; + } else { + const time = timeline.seekLocalTime(iterations * duration); + p = periodicity(time, duration)[1] / duration; + } + } else if(playState === 'running' || playState === 'paused') { + p = periodicity(timeline.getCurrentTime(ntime), duration)[1] / duration; + } + + if(playState === 'finished') { + p = periodicity(iterations, 1)[1]; + } + + return p; + } + get frame() { const playState = this.playState, initState = this[_initState], @@ -160,7 +214,35 @@ export default class { const {currentTime} = this.timeline, keyframes = this[_keyframes].slice(0); - const {p, inverted} = getProgress(this.timeline, this[_timing], this.progress); + const {p, inverted} = getProgress(currentTime, this[_timing], this.progress); + + let frameState = initState; + if(currentTime < 0 && playState === 'pending') { + // 在开始前 delay 阶段 + if(fill === 'backwards' || fill === 'both') { + frameState = inverted ? keyframes[keyframes.length - 1] : keyframes[0]; + } + } else if((playState !== 'pending' && playState !== 'finished') + || fill === 'forwards' || fill === 'both') { + // 不在 endDelay 或结束状态,或 forwards + frameState = getCurrentFrame(this[_timing], keyframes, this[_effects], p); + } + return frameState; + } + + getFrame(ntime) { + const playState = this.getPlayState(ntime), + initState = this[_initState], + {fill} = this[_timing]; + + if(playState === 'idle') { + return initState; + } + + const currentTime = this.timeline.getCurrentTime(ntime), + keyframes = this[_keyframes].slice(0); + + const {p, inverted} = getProgress(currentTime, this[_timing], this.getProgress(ntime)); let frameState = initState; if(currentTime < 0 && playState === 'pending') { @@ -192,13 +274,14 @@ export default class { return this[_timing].timeline; } - [_activeReadyTimer]() { + [_activeReadyTimer](ntime) { if(this[_readyDefer] && !this[_readyDefer].timerID) { - if(this.timeline.currentTime < 0) { + var currentTime = this.timeline.getCurrentTime(ntime); + if(currentTime < 0) { this[_readyDefer].timerID = this.timeline.setTimeout(() => { this[_readyDefer].resolve(); delete this[_readyDefer]; - }, {delay: -this.timeline.currentTime, heading: false}); + }, {delay: -currentTime, heading: false}); } else { this[_readyDefer].timerID = this.timeline.setTimeout(() => { this[_readyDefer].resolve(); @@ -208,9 +291,9 @@ export default class { } } - [_activeFinishTimer]() { + [_activeFinishTimer](ntime) { const {duration, iterations, endDelay} = this[_timing]; - const delay = Math.ceil(duration * iterations + endDelay - this.timeline.currentTime) + 1; + const delay = Math.ceil(duration * iterations + endDelay - this.timeline.getCurrentTime(ntime)) + 1; if(this[_finishedDefer] && !this[_finishedDefer].timerID) { this[_finishedDefer].timerID = this.timeline.setTimeout(() => { this[_finishedDefer].resolve(); @@ -226,7 +309,7 @@ export default class { } } - play() { + play(ntime) { if(this.playState === 'finished') { this.cancel(); } @@ -235,16 +318,18 @@ export default class { if(this.playbackRate <= 0) { return; } + ntime = ntime === undefined ? Timeline.nowtime() : ntime; const {delay, playbackRate, timeline} = this[_timing]; this.timeline = new Timeline({ originTime: delay, playbackRate, + ntime }, timeline); - this[_activeReadyTimer](); - this[_activeFinishTimer](); + this[_activeReadyTimer](ntime); + this[_activeFinishTimer](ntime); } else if(this.playState === 'paused') { this.timeline.playbackRate = this.playbackRate; - this[_activeReadyTimer](); + this[_activeReadyTimer](ntime); } } diff --git a/src/utils.js b/src/utils.js index 801f548..39cc7bd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -62,9 +62,8 @@ export function calculateFramesOffset(keyframes) { return keyframes; } -export function getProgress(timeline, timing, p) { - const {currentTime} = timeline, - {direction, duration} = timing; +export function getProgress(currentTime, timing, p) { + const {direction, duration} = timing; let inverted = false; if(direction === 'reverse') { p = 1 - p; diff --git a/test/index.js b/test/index.js index 865ba02..f6afe79 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,7 @@ const test = require("ava") const colors = require('colors') -import {Animator} from '../lib/index' +import {Animator} from '../src/index' function sleep(time) { const startTime = Date.now() @@ -13,45 +13,45 @@ function sleep(time) { } -function makeTimeCompare(caseID, startTime){ - return function(actual, expect, passedTime = Math.max(Date.now() - startTime, 100)){ +function makeTimeCompare(caseID, startTime) { + return function (actual, expect, passedTime = Math.max(Date.now() - startTime, 100)) { const precision = Math.abs(actual - expect).toFixed(2), - percent = precision / passedTime + percent = precision / passedTime let color = colors.green, - pass = true + pass = true - if(percent > 0.05 && percent <= 0.10){ + if(percent > 0.05 && percent <= 0.10) { color = colors.cyan } - if(percent > 0.10 && percent <= 0.20){ + if(percent > 0.10 && percent <= 0.20) { color = colors.yellow } - if(percent > 0.20 || Number.isNaN(percent)){ + if(percent > 0.20 || Number.isNaN(percent)) { color = colors.red pass = false } console.log(color(`${caseID} - actual: ${actual}, expect: ${expect}, precision: ${precision} | ${percent.toFixed(2)}`)) - return pass + return pass } } -function _case(fn){ +function _case(fn) { const caseID = _case.caseID || 0 _case.caseID = caseID + 1 - return async function(t){ + return async function (t) { const startTime = Date.now() t.time_compare = makeTimeCompare(caseID, startTime) return await fn(t) } } -function _caseSync(fn){ +function _caseSync(fn) { const caseID = _case.caseID || 0 _case.caseID = caseID + 1 - return function(t){ + return function (t) { const startTime = Date.now() t.time_compare = makeTimeCompare(caseID, startTime) return fn(t) @@ -79,12 +79,12 @@ test('normal animation', _case(async t => { })) test('animation delay', _case(async t => { - const animator = new Animator({x: 10}, [{x: 20, y: 0, c:'red'}, {x: 50, y: 100, c:'green'}], - {delay: 200, duration:500, endDelay: 300}) + const animator = new Animator({x: 10}, [{x: 20, y: 0, c: 'red'}, {x: 50, y: 100, c: 'green'}], + {delay: 200, duration: 500, endDelay: 300}) t.truthy(t.time_compare(animator.progress, 0, 1)) t.is(animator.playState, 'idle') - + animator.play() t.is(animator.playState, 'pending') @@ -105,13 +105,13 @@ test('animation delay', _case(async t => { await sleep(400) t.truthy(t.time_compare(animator.progress, 1, 1)) - t.is(animator.playState, 'finished') + t.is(animator.playState, 'finished') t.is(animator.frame.c, undefined) })) test('animation ready', _case(async t => { - const animator = new Animator({x: 10}, [{x: 20, y: 0, c:'red'}, {x: 50, y: 100, c:'green'}], - {delay: 200, duration:500, endDelay: 300}) + const animator = new Animator({x: 10}, [{x: 20, y: 0, c: 'red'}, {x: 50, y: 100, c: 'green'}], + {delay: 200, duration: 500, endDelay: 300}) t.is(animator.playState, 'idle') @@ -139,8 +139,8 @@ test('animation ready', _case(async t => { })) test('animation finished', _case(async t => { - const animator = new Animator({x: 10}, [{x: 20, y: 0, c:'red'}, {x: 50, y: 100, c:'green'}], - {delay: 200, duration:500, endDelay: 300}) + const animator = new Animator({x: 10}, [{x: 20, y: 0, c: 'red'}, {x: 50, y: 100, c: 'green'}], + {delay: 200, duration: 500, endDelay: 300}) t.is(animator.playState, 'idle') animator.play() @@ -166,3 +166,53 @@ test('animation finished', _case(async t => { t.truthy(t.time_compare(Date.now() - now, 100)) })) +test('animation getProgress', t => { + const animator = new Animator( + {x: 10}, + [{x: 20, y: 0, c: 'red'}, {x: 50, y: 100, c: 'green'}], + {delay: 10, duration: 100, endDelay: 40} + ) + + + animator.play(0); + + var p = animator.getProgress(5); + t.is(p, 0); + + var p = animator.getProgress(10); + t.is(p, 0); + + var p = animator.getProgress(60); + t.is(p, .5); + + p = animator.getProgress(110); + t.is(p, 1); + + p = animator.getProgress(120); + t.is(p, 1); +}) + + +test('animation getFrame', t => { + const animator = new Animator( + {x: 10}, + [{x: 20, y: 0, c: 'red'}, {x: 50, y: 100, c: 'green'}], + {delay: 10, duration: 100, endDelay: 40} + ) + animator.play(0); + var f = animator.getFrame(5); + t.deepEqual(f, {x: 10}); + + var f = animator.getFrame(10); + t.deepEqual(f, {x: 20, y:0, c: 'red'}); + + f = animator.getFrame(60); + t.deepEqual(f, {x: 35, y: 50, c: 'red'}); + + f = animator.getFrame(110); + t.deepEqual(f, {x: 50, y: 100, c: 'green'}); + + f = animator.getFrame(120); + t.deepEqual(f, {x: 10}); +}) +