tween timelines
This commit is contained in:
@@ -5,19 +5,24 @@ var rate = 1/240
|
||||
|
||||
var TweenEngine = {
|
||||
tweens: [],
|
||||
default_clock: null, // Will be set during init
|
||||
add(tween) {
|
||||
this.tweens.push(tween)
|
||||
},
|
||||
remove(tween) {
|
||||
this.tweens = this.tweens.filter(t => t != tween)
|
||||
},
|
||||
update(dt) {
|
||||
var now = time.number()
|
||||
for (var tween of this.tweens.slice()) {
|
||||
tween._update(now)
|
||||
update(current_time) {
|
||||
// If no time provided, use real time
|
||||
if (current_time == null) {
|
||||
current_time = time.number()
|
||||
}
|
||||
|
||||
$_.delay(_ => TweenEngine.update(), rate)
|
||||
for (var tween of this.tweens.slice()) {
|
||||
tween._update(current_time)
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
this.tweens = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,17 +34,26 @@ function Tween(obj) {
|
||||
this.easing = Ease.linear
|
||||
this.startTime = 0
|
||||
this.onCompleteCallback = function() {}
|
||||
this.onUpdateCallback = null
|
||||
this.engine = null // Track which engine owns this tween
|
||||
}
|
||||
|
||||
Tween.prototype.to = function(props, duration) {
|
||||
Tween.prototype.to = function(props, duration, start_time) {
|
||||
for (var key in props) {
|
||||
this.startVals[key] = this.obj[key]
|
||||
this.endVals[key] = props[key]
|
||||
}
|
||||
this.duration = duration
|
||||
this.startTime = time.number()
|
||||
|
||||
TweenEngine.add(this)
|
||||
// If no start_time provided, use the default clock
|
||||
if (start_time == null) {
|
||||
this.startTime = TweenEngine.default_clock ? TweenEngine.default_clock() : time.number()
|
||||
} else {
|
||||
this.startTime = start_time
|
||||
}
|
||||
|
||||
this.engine = this.engine || TweenEngine
|
||||
this.engine.add(this)
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -59,27 +73,136 @@ Tween.prototype.onUpdate = function(cb) {
|
||||
}
|
||||
|
||||
Tween.prototype._update = function(now) {
|
||||
var elapsed = now - this.startTime
|
||||
var t = Math.min(elapsed / this.duration, 1)
|
||||
this.seek(now)
|
||||
this.onUpdateCallback?.()
|
||||
}
|
||||
|
||||
Tween.prototype.seek = function(global_time) {
|
||||
var elapsed = global_time - this.startTime
|
||||
var t = Math.min(Math.max(elapsed / this.duration, 0), 1)
|
||||
var eased = this.easing(t)
|
||||
|
||||
for (var key in this.endVals) {
|
||||
var start = this.startVals[key]
|
||||
var end = this.endVals[key]
|
||||
this.obj[key] = start + (end - start) * eased
|
||||
this.onUpdateCallback?.()
|
||||
}
|
||||
|
||||
if (t == 1) {
|
||||
if (t == 1 && this.engine) {
|
||||
this.onCompleteCallback()
|
||||
TweenEngine.remove(this)
|
||||
this.engine.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
function tween(obj) {
|
||||
return new Tween(obj)
|
||||
Tween.prototype.toJSON = function() {
|
||||
return {
|
||||
startVals: this.startVals,
|
||||
endVals: this.endVals,
|
||||
duration: this.duration,
|
||||
startTime: this.startTime,
|
||||
easing: this.easing.name || 'linear'
|
||||
}
|
||||
}
|
||||
|
||||
$_.delay(_ => TweenEngine.update(), rate)
|
||||
function Timeline() {
|
||||
this.current_time = 0
|
||||
this.events = [] // { time, fn, fired }
|
||||
this.playing = false
|
||||
this.last_tick = 0
|
||||
this.engine = {
|
||||
tweens: [],
|
||||
add: TweenEngine.add.bind(this.engine),
|
||||
remove: TweenEngine.remove.bind(this.engine),
|
||||
update: TweenEngine.update.bind(this.engine),
|
||||
clear: TweenEngine.clear.bind(this.engine)
|
||||
}
|
||||
this.engine.tweens = []
|
||||
}
|
||||
|
||||
Timeline.prototype.add_event = function(time, fn) {
|
||||
this.events.push({ time, fn, fired: false })
|
||||
}
|
||||
|
||||
Timeline.prototype.add_tween = function(obj, props, duration, start_time) {
|
||||
var tw = new Tween(obj)
|
||||
tw.engine = this.engine
|
||||
return tw.to(props, duration, start_time)
|
||||
}
|
||||
|
||||
Timeline.prototype.play = function() {
|
||||
this.playing = true
|
||||
this.last_tick = time.number()
|
||||
var loop = () => {
|
||||
if (!this.playing) return
|
||||
var now = time.number()
|
||||
var dt = now - this.last_tick
|
||||
this.last_tick = now
|
||||
this.current_time += dt
|
||||
this.seek(this.current_time)
|
||||
$_.delay(loop, rate)
|
||||
}
|
||||
loop()
|
||||
}
|
||||
|
||||
Timeline.prototype.pause = function() {
|
||||
this.playing = false
|
||||
}
|
||||
|
||||
Timeline.prototype.seek = function(t) {
|
||||
this.current_time = t
|
||||
// Update all tweens in this timeline
|
||||
this.engine.update(t)
|
||||
// Fire any events
|
||||
for (var ev of this.events) {
|
||||
if (!ev.fired && t >= ev.time) {
|
||||
ev.fn()
|
||||
ev.fired = true
|
||||
} else if (ev.fired && t < ev.time) {
|
||||
// Reset fired flag when seeking backwards
|
||||
ev.fired = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timeline.prototype.toJSON = function() {
|
||||
return {
|
||||
current_time: this.current_time,
|
||||
events: this.events.map(e => ({ time: e.time, fired: e.fired })),
|
||||
tweens: this.engine.tweens.map(t => t.toJSON())
|
||||
}
|
||||
}
|
||||
|
||||
// Live update loop for fire-and-forget tweens
|
||||
function live_update_loop() {
|
||||
TweenEngine.update()
|
||||
$_.delay(live_update_loop, rate)
|
||||
}
|
||||
|
||||
// Factory function
|
||||
function tween(obj, engine) {
|
||||
var tw = new Tween(obj)
|
||||
if (engine) {
|
||||
tw.engine = engine
|
||||
}
|
||||
return tw
|
||||
}
|
||||
|
||||
// Initialize with a default clock that returns real time
|
||||
function init(default_clock) {
|
||||
TweenEngine.default_clock = default_clock || (() => time.number())
|
||||
// Start the live update loop
|
||||
live_update_loop()
|
||||
}
|
||||
|
||||
// Auto-init with real time if not explicitly initialized
|
||||
$_.delay(() => {
|
||||
if (!TweenEngine.default_clock) {
|
||||
init()
|
||||
}
|
||||
}, 0)
|
||||
|
||||
tween.init = init
|
||||
tween.Timeline = Timeline
|
||||
tween.TweenEngine = TweenEngine
|
||||
|
||||
return tween
|
||||
Reference in New Issue
Block a user