Sequence bugfixes and documentation
* The alternate playback directions now work * Clicking outside the playable area no longer raises errors * Documented the playback API
This commit is contained in:
parent
31d6acefca
commit
3cd126186e
4 changed files with 118 additions and 101 deletions
|
@ -12,18 +12,37 @@ export interface ISequence {
|
||||||
* Returns a promise that either resolves to true when the playback completes,
|
* Returns a promise that either resolves to true when the playback completes,
|
||||||
* or resolves to false if playback gets interrupted (for example by calling sequence.pause())
|
* or resolves to false if playback gets interrupted (for example by calling sequence.pause())
|
||||||
*/
|
*/
|
||||||
play(
|
play(conf?: {
|
||||||
conf?: Partial<{
|
/**
|
||||||
iterationCount: number
|
* The number of times the animation must run. Must be an integer larger
|
||||||
range: IPlaybackRange
|
* than 0. Defaults to 1. Pick Infinity to run forever
|
||||||
rate: number
|
*/
|
||||||
direction: IPlaybackDirection
|
iterationCount?: number
|
||||||
}>,
|
/**
|
||||||
): Promise<boolean>
|
* Limits the range to be played. Default is [0, sequence.length]
|
||||||
|
*/
|
||||||
|
range?: IPlaybackRange
|
||||||
|
/**
|
||||||
|
* The playback rate. Defaults to 1. Choosing 2 would play the animation
|
||||||
|
* at twice the speed.
|
||||||
|
*/
|
||||||
|
rate?: number
|
||||||
|
/**
|
||||||
|
* The direction of the playback. Similar to CSS's animation-direction
|
||||||
|
*/
|
||||||
|
direction?: IPlaybackDirection
|
||||||
|
}): Promise<boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses the currently playing animation
|
||||||
|
*/
|
||||||
pause(): void
|
pause(): void
|
||||||
|
|
||||||
time: number
|
/**
|
||||||
|
* The current position of the playhead.
|
||||||
|
* In a time-based sequence, this represents the current time.
|
||||||
|
*/
|
||||||
|
position: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TheatreSequence implements ISequence {
|
export default class TheatreSequence implements ISequence {
|
||||||
|
@ -72,11 +91,11 @@ export default class TheatreSequence implements ISequence {
|
||||||
privateAPI(this).pause()
|
privateAPI(this).pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
get time() {
|
get position() {
|
||||||
return privateAPI(this).position
|
return privateAPI(this).position
|
||||||
}
|
}
|
||||||
|
|
||||||
set time(t: number) {
|
set position(position: number) {
|
||||||
privateAPI(this).position = t
|
privateAPI(this).position = position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,98 +70,93 @@ export default class DefaultPlaybackController implements IPlaybackController {
|
||||||
this.playing = true
|
this.playing = true
|
||||||
|
|
||||||
const ticker = this._ticker
|
const ticker = this._ticker
|
||||||
let lastTickerTime = ticker.time
|
const iterationLength = range.end - range.start
|
||||||
const dur = range.end - range.start
|
|
||||||
const prevTime = this.getCurrentPosition()
|
|
||||||
|
|
||||||
if (prevTime < range.start || prevTime > range.end) {
|
{
|
||||||
|
const startPos = this.getCurrentPosition()
|
||||||
|
|
||||||
|
if (startPos < range.start || startPos > range.end) {
|
||||||
this._updatePositionInState(range.start)
|
this._updatePositionInState(range.start)
|
||||||
} else if (
|
} else if (
|
||||||
prevTime === range.end &&
|
startPos === range.end &&
|
||||||
(direction === 'normal' || direction === 'alternate')
|
(direction === 'normal' || direction === 'alternate')
|
||||||
) {
|
) {
|
||||||
this._updatePositionInState(range.start)
|
this._updatePositionInState(range.start)
|
||||||
} else if (
|
} else if (
|
||||||
prevTime === range.start &&
|
startPos === range.start &&
|
||||||
(direction === 'reverse' || direction === 'alternateReverse')
|
(direction === 'reverse' || direction === 'alternateReverse')
|
||||||
) {
|
) {
|
||||||
this._updatePositionInState(range.end)
|
this._updatePositionInState(range.end)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let goingForward =
|
|
||||||
direction === 'alternateReverse' || direction === 'reverse' ? -1 : 1
|
|
||||||
|
|
||||||
let countSoFar = 1
|
|
||||||
|
|
||||||
const deferred = defer<boolean>()
|
const deferred = defer<boolean>()
|
||||||
|
const initialTickerTime = ticker.time
|
||||||
const tick = (tickerTimeInMs: number) => {
|
const totalPlaybackLength = iterationLength * iterationCount
|
||||||
const tickerTime = tickerTimeInMs / 1000
|
let initialElapsedPos = this.getCurrentPosition() - range.start
|
||||||
const lastTime = this.getCurrentPosition()
|
if (direction === 'reverse' || direction === 'alternateReverse') {
|
||||||
const timeDiff = (tickerTime - lastTickerTime) * (rate * goingForward)
|
initialElapsedPos = range.end - initialElapsedPos
|
||||||
lastTickerTime = tickerTime
|
|
||||||
/*
|
|
||||||
* I don't know why exactly this happens, but every 10 times or so, the first sequence.play({iterationCount: 1}),
|
|
||||||
* the first call of tick() will have a timeDiff < 0.
|
|
||||||
* This might be because of Spectre mitigation (they randomize performance.now() a bit), or it could be that
|
|
||||||
* I'm using performance.now() the wrong way.
|
|
||||||
* Anyway, this seems like a working fix for it:
|
|
||||||
*/
|
|
||||||
if (timeDiff < 0) {
|
|
||||||
requestNextTick()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
const newTime = lastTime + timeDiff
|
|
||||||
|
|
||||||
if (newTime < range.start) {
|
const tick = (currentTickerTime: number) => {
|
||||||
if (countSoFar === iterationCount) {
|
const elapsedTickerTime = Math.max(
|
||||||
this._updatePositionInState(range.start)
|
currentTickerTime - initialTickerTime,
|
||||||
this.playing = false
|
0,
|
||||||
deferred.resolve(true)
|
)
|
||||||
return
|
const elapsedTickerTimeInSeconds = elapsedTickerTime / 1000
|
||||||
} else {
|
|
||||||
countSoFar++
|
const elapsedPos = Math.min(
|
||||||
const diff = (range.start - newTime) % dur
|
elapsedTickerTimeInSeconds * rate + initialElapsedPos,
|
||||||
|
totalPlaybackLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (elapsedPos !== totalPlaybackLength) {
|
||||||
|
const iterationNumber = Math.floor(elapsedPos / iterationLength)
|
||||||
|
let currentIterationPos =
|
||||||
|
((elapsedPos / iterationLength) % 1) * iterationLength
|
||||||
|
|
||||||
|
if (direction !== 'normal') {
|
||||||
if (direction === 'reverse') {
|
if (direction === 'reverse') {
|
||||||
this._updatePositionInState(range.end - diff)
|
currentIterationPos = iterationLength - currentIterationPos
|
||||||
} else {
|
} else {
|
||||||
goingForward = 1
|
const isCurrentIterationNumberEven = iterationNumber % 2 === 0
|
||||||
this._updatePositionInState(range.start + diff)
|
if (direction === 'alternate') {
|
||||||
|
if (!isCurrentIterationNumberEven) {
|
||||||
|
currentIterationPos = iterationLength - currentIterationPos
|
||||||
}
|
}
|
||||||
requestNextTick()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if (newTime === range.end) {
|
|
||||||
this._updatePositionInState(range.end)
|
|
||||||
if (countSoFar === iterationCount) {
|
|
||||||
this.playing = false
|
|
||||||
deferred.resolve(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
requestNextTick()
|
|
||||||
return
|
|
||||||
} else if (newTime > range.end) {
|
|
||||||
if (countSoFar === iterationCount) {
|
|
||||||
this._updatePositionInState(range.end)
|
|
||||||
this.playing = false
|
|
||||||
deferred.resolve(true)
|
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
countSoFar++
|
if (isCurrentIterationNumberEven) {
|
||||||
const diff = (newTime - range.end) % dur
|
currentIterationPos = iterationLength - currentIterationPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updatePositionInState(currentIterationPos)
|
||||||
|
requestNextTick()
|
||||||
|
} else {
|
||||||
if (direction === 'normal') {
|
if (direction === 'normal') {
|
||||||
this._updatePositionInState(range.start + diff)
|
this._updatePositionInState(range.end)
|
||||||
|
} else if (direction === 'reverse') {
|
||||||
|
this._updatePositionInState(range.start)
|
||||||
} else {
|
} else {
|
||||||
goingForward = -1
|
const isLastIterationEven = (iterationCount - 1) % 2 === 0
|
||||||
this._updatePositionInState(range.end - diff)
|
if (direction === 'alternate') {
|
||||||
}
|
if (isLastIterationEven) {
|
||||||
requestNextTick()
|
this._updatePositionInState(range.end)
|
||||||
return
|
} else {
|
||||||
|
this._updatePositionInState(range.start)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._updatePositionInState(newTime)
|
if (isLastIterationEven) {
|
||||||
requestNextTick()
|
this._updatePositionInState(range.start)
|
||||||
return
|
} else {
|
||||||
|
this._updatePositionInState(range.end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.playing = false
|
||||||
|
deferred.resolve(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,29 +99,29 @@ describe(`SheetObject`, () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(seq.time).toEqual(0)
|
expect(seq.position).toEqual(0)
|
||||||
|
|
||||||
expect(objValues.next().value).toMatchObject({
|
expect(objValues.next().value).toMatchObject({
|
||||||
position: {x: 0, y: 3, z: 2},
|
position: {x: 0, y: 3, z: 2},
|
||||||
})
|
})
|
||||||
|
|
||||||
seq.time = 5
|
seq.position = 5
|
||||||
expect(seq.time).toEqual(5)
|
expect(seq.position).toEqual(5)
|
||||||
expect(objValues.next().value).toMatchObject({
|
expect(objValues.next().value).toMatchObject({
|
||||||
position: {x: 0, y: 3, z: 2},
|
position: {x: 0, y: 3, z: 2},
|
||||||
})
|
})
|
||||||
|
|
||||||
seq.time = 11
|
seq.position = 11
|
||||||
expect(objValues.next().value).toMatchObject({
|
expect(objValues.next().value).toMatchObject({
|
||||||
position: {x: 0, y: 3.29999747758308, z: 2},
|
position: {x: 0, y: 3.29999747758308, z: 2},
|
||||||
})
|
})
|
||||||
|
|
||||||
seq.time = 15
|
seq.position = 15
|
||||||
expect(objValues.next().value).toMatchObject({
|
expect(objValues.next().value).toMatchObject({
|
||||||
position: {x: 0, y: 4.5, z: 2},
|
position: {x: 0, y: 4.5, z: 2},
|
||||||
})
|
})
|
||||||
|
|
||||||
seq.time = 22
|
seq.position = 22
|
||||||
expect(objValues.next().value).toMatchObject({
|
expect(objValues.next().value).toMatchObject({
|
||||||
position: {x: 0, y: 6, z: 2},
|
position: {x: 0, y: 6, z: 2},
|
||||||
})
|
})
|
||||||
|
|
|
@ -128,9 +128,12 @@ function useDragHandlers(
|
||||||
const initialPositionInClippedSpace =
|
const initialPositionInClippedSpace =
|
||||||
event.clientX - containerEl!.getBoundingClientRect().left
|
event.clientX - containerEl!.getBoundingClientRect().left
|
||||||
|
|
||||||
const initialPositionInUnitSpace = val(
|
const initialPositionInUnitSpace = clamp(
|
||||||
layoutP.clippedSpace.toUnitSpace,
|
val(layoutP.clippedSpace.toUnitSpace)(initialPositionInClippedSpace),
|
||||||
)(initialPositionInClippedSpace)
|
0,
|
||||||
|
Infinity,
|
||||||
|
)
|
||||||
|
|
||||||
sequence = val(layoutP.sheet).getSequence()
|
sequence = val(layoutP.sheet).getSequence()
|
||||||
|
|
||||||
sequence.position = initialPositionInUnitSpace
|
sequence.position = initialPositionInUnitSpace
|
||||||
|
|
Loading…
Reference in a new issue