pacer-js 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Pacer.js +230 -107
- package/README.md +6 -8
- package/package.json +49 -38
- package/pacer.svg +0 -1256
package/Pacer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Copyright ©️ 2025 Stewart Smith. See LICENSE for details.
|
|
1
|
+
// Copyright ©️ 2025–2026 Stewart Smith. See LICENSE for details.
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
normalize01,
|
|
28
28
|
lerp
|
|
29
29
|
|
|
30
|
-
} from '
|
|
30
|
+
} from 'snacks-js'
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
@@ -53,11 +53,19 @@ class Pacer {
|
|
|
53
53
|
constructor( label, units ){
|
|
54
54
|
|
|
55
55
|
this._label = isUsefulString( label ) ? label : 'Untitled Pacer instance'
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// This is purely to help humans reason around their Pacers.
|
|
59
|
+
// No particular value is required.
|
|
60
|
+
// All that matters is that YOU, the human, are consisent with your units.
|
|
61
|
+
// If you want to make use of this property, some suggested values:
|
|
62
|
+
// ms, milliseconds, s, seconds, #, n, norm, normalize, normalized, %, percent
|
|
63
|
+
|
|
64
|
+
this._units = isUsefulString( units ) ? units : 'ms'
|
|
57
65
|
|
|
58
66
|
this.keys = []
|
|
59
67
|
this.keyIndex = -1
|
|
60
|
-
this.
|
|
68
|
+
this.lastCreatedKey = null
|
|
61
69
|
|
|
62
70
|
this.values = {}
|
|
63
71
|
this.n = 0
|
|
@@ -72,7 +80,16 @@ class Pacer {
|
|
|
72
80
|
|
|
73
81
|
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
////////////////////////
|
|
86
|
+
// //
|
|
87
|
+
// Non-chainables //
|
|
88
|
+
// //
|
|
89
|
+
////////////////////////
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
// ie. They do not return `this` (your Pacer instance).
|
|
76
93
|
|
|
77
94
|
inspect( useRelative ){
|
|
78
95
|
|
|
@@ -120,7 +137,6 @@ class Pacer {
|
|
|
120
137
|
// This logic is unnecessary,
|
|
121
138
|
// but leaving it here for future reference.
|
|
122
139
|
|
|
123
|
-
|
|
124
140
|
// const tweenLabel = keyA.tween.label
|
|
125
141
|
// if( direction < 0 && tweenLabel !== 'linear' ){
|
|
126
142
|
|
|
@@ -135,9 +151,9 @@ class Pacer {
|
|
|
135
151
|
method = this.isClamped ? normalize01 : normalize,
|
|
136
152
|
n = method(
|
|
137
153
|
|
|
138
|
-
now,
|
|
139
154
|
keyA.timeAbsolute,
|
|
140
|
-
keyB.timeAbsolute
|
|
155
|
+
keyB.timeAbsolute,
|
|
156
|
+
now
|
|
141
157
|
),
|
|
142
158
|
a = Object.keys( keyA.values ),
|
|
143
159
|
b = Object.keys( keyB.values ),
|
|
@@ -147,7 +163,7 @@ class Pacer {
|
|
|
147
163
|
if( isNotUsefulNumber( a )) return output
|
|
148
164
|
const b = keyB.values[ key ]
|
|
149
165
|
if( isNotUsefulNumber( b )) return output
|
|
150
|
-
output[ key ] = lerp( tween( n )
|
|
166
|
+
output[ key ] = lerp( a, b, tween( n ))
|
|
151
167
|
return output
|
|
152
168
|
|
|
153
169
|
}, {})
|
|
@@ -162,7 +178,17 @@ class Pacer {
|
|
|
162
178
|
|
|
163
179
|
|
|
164
180
|
|
|
165
|
-
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
////////////////////
|
|
184
|
+
// //
|
|
185
|
+
// Chainables //
|
|
186
|
+
// //
|
|
187
|
+
////////////////////
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
// ie. They all return `this` (your Pacer instance),
|
|
191
|
+
// so you can pipe from one method to another, to another…
|
|
166
192
|
|
|
167
193
|
setTimeBounds(){
|
|
168
194
|
|
|
@@ -182,7 +208,7 @@ class Pacer {
|
|
|
182
208
|
|
|
183
209
|
|
|
184
210
|
// Yes, we have to operate directly on the `keys` Array
|
|
185
|
-
// rather than `key` element reference
|
|
211
|
+
// rather than a `key` element reference
|
|
186
212
|
// if we want to actually write this property.
|
|
187
213
|
// Otherwise it silently fails. Lovely.
|
|
188
214
|
// And we don’t want to use Array.map
|
|
@@ -190,10 +216,21 @@ class Pacer {
|
|
|
190
216
|
|
|
191
217
|
keys[ i ].index = i
|
|
192
218
|
|
|
219
|
+
|
|
220
|
+
// You know what?
|
|
221
|
+
// Let’s also keep track of _relative_ time
|
|
222
|
+
// so it’s as easy as pie
|
|
223
|
+
// to shift key times without more overhead.
|
|
224
|
+
|
|
225
|
+
keys[ i ].timeRelative = i === 0
|
|
226
|
+
? 0
|
|
227
|
+
: key.timeAbsolute - keys[ i - 1 ].timeAbsolute
|
|
228
|
+
|
|
193
229
|
|
|
194
230
|
// Also, here’s a nicety:
|
|
195
231
|
// if there’s a key with no values,
|
|
196
232
|
// let’s just copy the values from the previous key.
|
|
233
|
+
// (I have some further ideas about this… Stay tuned!)
|
|
197
234
|
|
|
198
235
|
if( key.values instanceof Object !== true &&
|
|
199
236
|
typeof keys[ i - 1 ] !== 'undefined' &&
|
|
@@ -201,23 +238,31 @@ class Pacer {
|
|
|
201
238
|
|
|
202
239
|
keys[ i ].values = keys[ i - 1 ].values
|
|
203
240
|
|
|
204
|
-
|
|
205
|
-
//
|
|
241
|
+
|
|
242
|
+
// There’s an argument to be made for this instead of the above:
|
|
243
|
+
// keys[ i ].values = Object.assign( {}, keys[ i - 1 ].values )
|
|
244
|
+
// Remove ability for original object owner to mutate values? Thoughts?
|
|
206
245
|
}
|
|
207
246
|
})
|
|
208
247
|
this.setTimeBounds()
|
|
209
248
|
return this
|
|
210
249
|
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
// CHAINABLE “Key-based” methods,
|
|
253
|
+
// ie. creates or relies on `this.lastCreatedKey`.
|
|
254
|
+
|
|
211
255
|
key( time, values, callback, isAbsolute ){
|
|
212
256
|
|
|
213
257
|
if( isAbsolute !== true &&// Making a theoretical `isRelative` the default for backwards compatibility.
|
|
214
258
|
this.keys.length > 0 ){
|
|
215
259
|
|
|
216
|
-
time += this.getLastKey().timeAbsolute
|
|
260
|
+
// time += this.getLastKey().timeAbsolute
|
|
261
|
+
time += this.lastCreatedKey.timeAbsolute// IT’S TRICKY TO ROCK A RHYME !
|
|
217
262
|
}
|
|
218
263
|
if( this.keys.length === 0 ) this.values = values
|
|
219
264
|
const key = new Key( time, values, callback )
|
|
220
|
-
this.
|
|
265
|
+
this.lastCreatedKey = key
|
|
221
266
|
this.keys.push( key )
|
|
222
267
|
this.sortKeys()
|
|
223
268
|
if( this.keys.length === 1 ) this.timeCursor = this.timeStart - 1
|
|
@@ -231,121 +276,185 @@ class Pacer {
|
|
|
231
276
|
|
|
232
277
|
return this.key( timeAbsolute, values, callback, true )
|
|
233
278
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
labelPacer( s ){
|
|
237
|
-
|
|
238
|
-
this._label = s
|
|
239
|
-
return this
|
|
240
|
-
}
|
|
241
279
|
label( s ){
|
|
242
280
|
|
|
243
|
-
this.
|
|
281
|
+
this.lastCreatedKey.label = s
|
|
244
282
|
return this
|
|
245
283
|
}
|
|
284
|
+
// Well this was stupid…
|
|
285
|
+
// Need to meditate on best approach for avoiding name collisions between
|
|
286
|
+
// 1. Regular old instance object properties,
|
|
287
|
+
// 2. Setters on this Pacer instance,
|
|
288
|
+
// 3. Setters actually intended for a Key instance.
|
|
289
|
+
// Particularly acute when it comes to .label(), for example.
|
|
290
|
+
// (I don’t love `label` vs `labelPacer`, but will do for now.)
|
|
246
291
|
// values( v ){
|
|
247
292
|
|
|
248
|
-
// this.
|
|
293
|
+
// this.lastCreatedKey.values = v
|
|
249
294
|
// return this
|
|
250
295
|
// }
|
|
251
|
-
|
|
296
|
+
onKey( fn ){
|
|
252
297
|
|
|
253
|
-
this.
|
|
298
|
+
this.lastCreatedKey.onKey = fn
|
|
254
299
|
return this
|
|
255
300
|
}
|
|
256
|
-
|
|
301
|
+
tween( fn ){
|
|
257
302
|
|
|
258
|
-
this.
|
|
303
|
+
this.lastCreatedKey.tween = fn
|
|
259
304
|
return this
|
|
260
305
|
}
|
|
261
|
-
|
|
262
|
-
|
|
306
|
+
onTween( fn ){
|
|
263
307
|
|
|
264
|
-
this.
|
|
308
|
+
this.lastCreatedKey.onTween = fn
|
|
265
309
|
return this
|
|
266
310
|
}
|
|
267
|
-
|
|
311
|
+
onCancel( fn ){
|
|
268
312
|
|
|
269
|
-
this.
|
|
313
|
+
this.lastCreatedKey.onCancel = fn
|
|
270
314
|
return this
|
|
271
315
|
}
|
|
272
316
|
|
|
273
317
|
|
|
274
|
-
|
|
318
|
+
// CHAINABLE “instance-wide” callback setters,
|
|
319
|
+
// ie. These do _not_ rely on `this.lastCreatedKey`
|
|
320
|
+
// and set callbacks for the instance to use.
|
|
321
|
+
|
|
322
|
+
onEveryKey( fn ){
|
|
275
323
|
|
|
276
|
-
this.
|
|
324
|
+
this._onEveryKey = fn
|
|
277
325
|
return this
|
|
278
326
|
}
|
|
279
|
-
|
|
327
|
+
onEveryTween( fn ){
|
|
280
328
|
|
|
281
|
-
this.
|
|
329
|
+
this._onEveryTween = fn
|
|
282
330
|
return this
|
|
283
331
|
}
|
|
284
|
-
|
|
332
|
+
onBeforeAll( fn ){
|
|
333
|
+
|
|
334
|
+
this._onBefore = fn
|
|
335
|
+
return this
|
|
336
|
+
}
|
|
337
|
+
onAfterAll( fn ){
|
|
285
338
|
|
|
286
|
-
this.
|
|
339
|
+
this._onAfter = fn
|
|
287
340
|
return this
|
|
288
341
|
}
|
|
289
342
|
|
|
290
343
|
|
|
291
|
-
//
|
|
344
|
+
// CHAINABLE “instance-wide” commands,
|
|
345
|
+
// ie. These do _not_ rely on `this.lastCreatedKey`
|
|
346
|
+
// and issue simple property setters.
|
|
292
347
|
|
|
293
|
-
|
|
348
|
+
labelPacer( s ){// This naming gives me pause.
|
|
294
349
|
|
|
295
|
-
this.
|
|
350
|
+
this._label = s
|
|
296
351
|
return this
|
|
297
352
|
}
|
|
298
|
-
|
|
353
|
+
units( u ){
|
|
299
354
|
|
|
300
|
-
this.
|
|
355
|
+
this._units = u
|
|
301
356
|
return this
|
|
302
357
|
}
|
|
303
|
-
|
|
358
|
+
clamp(){
|
|
304
359
|
|
|
305
|
-
this.
|
|
360
|
+
this.isClamped = true
|
|
306
361
|
return this
|
|
307
362
|
}
|
|
308
|
-
|
|
363
|
+
unclamp(){
|
|
309
364
|
|
|
310
|
-
this.
|
|
365
|
+
this.isClamped = false
|
|
311
366
|
return this
|
|
312
367
|
}
|
|
313
368
|
|
|
314
369
|
|
|
315
|
-
//
|
|
370
|
+
// A quick way to turn individual instances on/off,
|
|
371
|
+
// particuarly convenient if doing bulk updates
|
|
372
|
+
// like `Pacer.update()` ← Note that’s the Class method itself,
|
|
373
|
+
// not an instance method.
|
|
374
|
+
|
|
375
|
+
enable(){
|
|
376
|
+
|
|
377
|
+
this.isEnabled = true
|
|
378
|
+
return this
|
|
379
|
+
}
|
|
380
|
+
disable(){
|
|
381
|
+
|
|
382
|
+
this.isEnabled = false
|
|
383
|
+
return this
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
// All those moments will be lost in time,
|
|
388
|
+
// like tears in rain.
|
|
389
|
+
// Time to die.
|
|
390
|
+
|
|
391
|
+
remove(){
|
|
392
|
+
|
|
393
|
+
Pacer.remove( this )
|
|
394
|
+
return this
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
////////////////
|
|
401
|
+
// //
|
|
402
|
+
// Update //
|
|
403
|
+
// //
|
|
404
|
+
////////////////
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
// Yeah. This one method deserves its own rhombus comment block,
|
|
408
|
+
// because if it were a pickle it would wear a shirt that says
|
|
409
|
+
// “I’m a big dill.” Pho real.
|
|
410
|
+
// See also `onKey` and `onTween` below.
|
|
316
411
|
|
|
317
412
|
update( now ){
|
|
318
413
|
|
|
319
414
|
|
|
320
415
|
// We only need to update
|
|
321
|
-
// if
|
|
322
|
-
// and we have at least one keyframe
|
|
323
|
-
// that might have either a values object,
|
|
324
|
-
// an onKey callback,
|
|
325
|
-
// or would be included if there’s an onEveryKey callback.
|
|
416
|
+
// if our instance is enabled
|
|
417
|
+
// and we have at least one keyframe:
|
|
418
|
+
// that might have either a values{} object,
|
|
419
|
+
// an onKey() callback,
|
|
420
|
+
// or would be included if there’s an onEveryKey() callback.
|
|
326
421
|
|
|
327
422
|
if( this.isEnabled !== true ) return this
|
|
328
423
|
if( this.keys.length < 1 ) return this
|
|
329
424
|
|
|
330
|
-
|
|
331
|
-
// So I guess we’re doing this.
|
|
425
|
+
|
|
332
426
|
// What time is it?
|
|
333
|
-
//
|
|
427
|
+
// Remember that “time” is just a numeric value…
|
|
428
|
+
// You’ll likely use it for time, sure,
|
|
429
|
+
// but you could also follow Scroll Pacer’s example
|
|
430
|
+
// and instead send pixel values
|
|
431
|
+
// describing an element’s relationship to the viewport.
|
|
334
432
|
|
|
335
433
|
if( isNotUsefulNumber( now )) now = Date.now()
|
|
336
|
-
if( now === this.timeCursor ) return this// Unlikely for standalone animations, but very likely for scroll animations.
|
|
337
|
-
const direction = now < this.timeCursor ? -1 : 1
|
|
338
434
|
|
|
339
435
|
|
|
436
|
+
// The following is unlikely to be true for standalone animations,
|
|
437
|
+
// but very likely for scroll animations.
|
|
438
|
+
|
|
439
|
+
if( now === this.timeCursor ) return this
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
// If we made it to here, I guess we’re actually doing this.
|
|
443
|
+
// So what direction is our time flowing?
|
|
444
|
+
|
|
445
|
+
const direction = now < this.timeCursor ? -1 : 1
|
|
446
|
+
|
|
447
|
+
|
|
340
448
|
// What’s our total N gain for the this entire instance?
|
|
341
449
|
|
|
342
450
|
const method = this.isClamped ? normalize01 : normalize
|
|
343
|
-
this.n = method(
|
|
451
|
+
this.n = method( this.timeStart, this.timeStop, now )
|
|
344
452
|
|
|
345
453
|
|
|
346
454
|
// We know our keys are already sorted by time,
|
|
347
455
|
// and we’ve previously set the convenience variables
|
|
348
456
|
// `timeStart` and `timeStop`.
|
|
457
|
+
// (This sorting happens automagically with every key creation.)
|
|
349
458
|
|
|
350
459
|
// Note 1: Direction has no effect on the order of
|
|
351
460
|
// keyA and keyB, because we always want this to be true:
|
|
@@ -354,6 +463,7 @@ class Pacer {
|
|
|
354
463
|
|
|
355
464
|
// Note 2: For this step, it is perfectly reasonable
|
|
356
465
|
// for keyA or keyB to be undefined.
|
|
466
|
+
// That is a signal itself, not a lack of signal.
|
|
357
467
|
|
|
358
468
|
let
|
|
359
469
|
targetIndex = 0,
|
|
@@ -413,9 +523,10 @@ class Pacer {
|
|
|
413
523
|
}
|
|
414
524
|
|
|
415
525
|
|
|
416
|
-
// Let’s prep for
|
|
526
|
+
// Let’s prep for any onKey callbacks;
|
|
417
527
|
// You never know what someone’s callbacks
|
|
418
|
-
// are going to ask for on this instance
|
|
528
|
+
// are going to ask for on this instance
|
|
529
|
+
// so it’s best to be kind and sharing.
|
|
419
530
|
|
|
420
531
|
const keyIndexPrior = this.keyIndex
|
|
421
532
|
this.direction = direction
|
|
@@ -423,8 +534,6 @@ class Pacer {
|
|
|
423
534
|
|
|
424
535
|
|
|
425
536
|
|
|
426
|
-
|
|
427
|
-
|
|
428
537
|
///////////////
|
|
429
538
|
// //
|
|
430
539
|
// onKey //
|
|
@@ -490,6 +599,12 @@ class Pacer {
|
|
|
490
599
|
|
|
491
600
|
tempKey.onKey( tempKey.values, this )
|
|
492
601
|
}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
// In the future we may wish to distinguish
|
|
605
|
+
// between `onEveryKeyEarly` and `onEveryKeyLate`
|
|
606
|
+
// in order to allow users finer control.
|
|
607
|
+
|
|
493
608
|
if( typeof this._onEveryKey === 'function' ){
|
|
494
609
|
|
|
495
610
|
this._onEveryKey( tempKey.values, this )
|
|
@@ -503,8 +618,6 @@ class Pacer {
|
|
|
503
618
|
|
|
504
619
|
|
|
505
620
|
|
|
506
|
-
|
|
507
|
-
|
|
508
621
|
/////////////////
|
|
509
622
|
// //
|
|
510
623
|
// onTween //
|
|
@@ -512,10 +625,14 @@ class Pacer {
|
|
|
512
625
|
/////////////////
|
|
513
626
|
|
|
514
627
|
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
|
|
628
|
+
// It is possible that our `now` is _exactly_ on a
|
|
629
|
+
// keyframe’s intended firing time.
|
|
630
|
+
// If that’s the case, isn’t it redundant to tween?
|
|
631
|
+
// YES. But the moment we tried removing the ability to
|
|
632
|
+
// fire tween callbacks when it aligned with a keyframe,
|
|
633
|
+
// we ran into unintended logical “gotchas” on the user side.
|
|
634
|
+
// If you are incredibly anxious about performance,
|
|
635
|
+
// you may want to give more thought to your callbacks here.
|
|
519
636
|
|
|
520
637
|
// We need TWO valid keyframes in order to tween anything.
|
|
521
638
|
// If we don’t got, we bail now.
|
|
@@ -553,19 +670,21 @@ class Pacer {
|
|
|
553
670
|
|
|
554
671
|
keyA._onTween( this.values, this )
|
|
555
672
|
}
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
// In the future we may wish to distinguish
|
|
676
|
+
// between `onEveryTweenEarly` and `onEveryTweenLate`
|
|
677
|
+
// in order to allow users finer control.
|
|
678
|
+
|
|
556
679
|
if( typeof this._onEveryTween === 'function' ){
|
|
557
680
|
|
|
558
681
|
this._onEveryTween( this.values, this )
|
|
559
682
|
}
|
|
560
683
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
684
|
return this
|
|
564
685
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
686
|
+
|
|
687
|
+
|
|
569
688
|
reset( newTimeStartAbsolute ){
|
|
570
689
|
|
|
571
690
|
this.disable()
|
|
@@ -576,7 +695,11 @@ class Pacer {
|
|
|
576
695
|
.forEach( function( key, i, keys ){
|
|
577
696
|
|
|
578
697
|
timeCursor += key.timeRelative
|
|
579
|
-
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
// Again, see reasoning above for using .forEach rather than .reduce or .map here.
|
|
701
|
+
|
|
702
|
+
keys[ i ].timeAbsolute = timeCursor
|
|
580
703
|
})
|
|
581
704
|
this.setTimeBounds()
|
|
582
705
|
this.keyIndex = -1
|
|
@@ -586,37 +709,18 @@ class Pacer {
|
|
|
586
709
|
}
|
|
587
710
|
|
|
588
711
|
|
|
589
|
-
// A quick way to turn individual pacers on/off,
|
|
590
|
-
// particuarly convenient if doing builk updates
|
|
591
|
-
// like Pacer.update() ← Note that’s the Class method itself,
|
|
592
|
-
// not an instance method.
|
|
593
|
-
|
|
594
|
-
enable(){
|
|
595
|
-
|
|
596
|
-
this.isEnabled = true
|
|
597
|
-
return this
|
|
598
|
-
}
|
|
599
|
-
disable(){
|
|
600
|
-
|
|
601
|
-
this.isEnabled = false
|
|
602
|
-
return this
|
|
603
|
-
}
|
|
604
|
-
|
|
605
712
|
|
|
606
|
-
// All those moments will be lost in time,
|
|
607
|
-
// like tears in rain.
|
|
608
|
-
// Time to die.
|
|
609
713
|
|
|
610
|
-
remove(){
|
|
611
|
-
|
|
612
|
-
Pacer.remove( this )
|
|
613
|
-
return this
|
|
614
|
-
}
|
|
615
714
|
|
|
616
715
|
|
|
716
|
+
/////////////////
|
|
717
|
+
// //
|
|
718
|
+
// Statics //
|
|
719
|
+
// //
|
|
720
|
+
/////////////////
|
|
617
721
|
|
|
618
722
|
|
|
619
|
-
//
|
|
723
|
+
// ie `this === Pacer`
|
|
620
724
|
|
|
621
725
|
static all = []
|
|
622
726
|
static update( now ){
|
|
@@ -665,15 +769,28 @@ class Pacer {
|
|
|
665
769
|
|
|
666
770
|
|
|
667
771
|
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
////////////////
|
|
775
|
+
// //
|
|
776
|
+
// Easing //
|
|
777
|
+
// //
|
|
778
|
+
////////////////
|
|
779
|
+
|
|
780
|
+
|
|
668
781
|
// Tweening functions, aka Easing functions.
|
|
669
782
|
// “Tween” is of course short for “between”, as in _between_ the keyframes.
|
|
670
|
-
// We’ll start with our default tween (no easing):
|
|
783
|
+
// We’ll start with our default tween (no fancy easing):
|
|
671
784
|
|
|
672
785
|
Pacer.linear = function( n ){ return n }
|
|
673
786
|
Pacer.linear.label = 'linear'
|
|
674
787
|
|
|
675
788
|
|
|
676
|
-
//
|
|
789
|
+
// Robert Penner’s collection of easing functions.
|
|
790
|
+
// https://robertpenner.com/easing/
|
|
791
|
+
// Look how ’purty I’ve boxed up these symetric functions!
|
|
792
|
+
// Compare to how much lengthier and complicated this looks:
|
|
793
|
+
// https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
|
|
677
794
|
// Down the road we ought to add Bezier() and Custom options.
|
|
678
795
|
|
|
679
796
|
Object.entries({
|
|
@@ -707,6 +824,10 @@ Object.entries({
|
|
|
707
824
|
key = entry[ 0 ],
|
|
708
825
|
val = entry[ 1 ]
|
|
709
826
|
|
|
827
|
+
|
|
828
|
+
// Graft our easing logic right onto Pacer
|
|
829
|
+
// so it’s trivial to access.
|
|
830
|
+
|
|
710
831
|
Pacer[ key ] = {
|
|
711
832
|
|
|
712
833
|
label: key,
|
|
@@ -735,8 +856,7 @@ Object.entries({
|
|
|
735
856
|
Pacer.bounce = {
|
|
736
857
|
|
|
737
858
|
label: 'bounce',
|
|
738
|
-
|
|
739
|
-
out: function( n, n1, d1 ){
|
|
859
|
+
out: function( n, n1, d1 ){// Oddly, must be defined before `in` and `inOut`.
|
|
740
860
|
|
|
741
861
|
if( isNotUsefulNumber( n1 )) n1 = 7.5625
|
|
742
862
|
if( isNotUsefulNumber( d1 )) d1 = 2.75
|
|
@@ -745,7 +865,10 @@ Pacer.bounce = {
|
|
|
745
865
|
else if( n < 2.5 / d1 ) return n1 * ( n -= 2.25 / d1 ) * n + 0.9375
|
|
746
866
|
else return n1 * ( n -= 2.625 / d1 ) * n + 0.984375
|
|
747
867
|
},
|
|
748
|
-
|
|
868
|
+
in: n => 1 - Pacer.bounce.out( 1 - n ),
|
|
869
|
+
inOut: n => n < 0.5
|
|
870
|
+
? Pacer.bounce.out( n * 2 ) / 2
|
|
871
|
+
: Pacer.bounce.out( n * 2 - 1 ) / 2 + 0.5
|
|
749
872
|
}
|
|
750
873
|
Pacer.bounce.in.label = 'bounce'
|
|
751
874
|
Pacer.bounce.in.style = 'in'
|
package/README.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
<img src="./pacer.svg?raw=true" width="100%">
|
|
2
2
|
|
|
3
|
-
<br>
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
## TL;DR
|
|
9
7
|
|
|
10
|
-
__Pacer__ is a light-weight keyframing toolkit inspired by [Soledad Penadés](https://soledadpenades.com/)’ original [tween.js](https://soledadpenades.com/projects/tween-js/) masterpiece. List your keyframes as time / value pairs, and __Pacer__ will ✨ tween your numbers and 📞 call your callbacks. __It’s minimal__. Only does what it needs to. __It’s reliable__. We use this in our own professional projects. We found the bumps and sanded them down ✅ (so you won’t have to). Either include the `Pacer.js` ES6 module in your codebase, or install the [Node package](https://www.npmjs.com/package/pacer-js):
|
|
8
|
+
__Pacer__ is a light-weight keyframing toolkit inspired by [Soledad Penadés](https://soledadpenades.com/)’ original [tween.js](https://soledadpenades.com/projects/tween-js/) masterpiece. List your keyframes as time / value pairs, and __Pacer__ will ✨ tween your numbers and 📞 call your callbacks. __It’s minimal__. Only does what it needs to. __It’s reliable__. We use this in our own professional projects. We found the bumps and sanded them down ✅ (so you won’t have to). Either include the [`Pacer.js`](./Pacer.js) ES6 module (and its [one dependency](https://github.com/stewdio/snacks-js)) in your codebase, or install the [Node package](https://www.npmjs.com/package/pacer-js):
|
|
11
9
|
|
|
12
10
|
```shell
|
|
13
11
|
npm install pacer-js
|
|
@@ -42,7 +40,7 @@ That’s it. You’re good to go 👍
|
|
|
42
40
|
|
|
43
41
|
|
|
44
42
|
|
|
45
|
-
<br><br><br
|
|
43
|
+
<br><br><br>
|
|
46
44
|
|
|
47
45
|
|
|
48
46
|
|
|
@@ -131,7 +129,7 @@ Expanding on the above, a code block should read like a normal paragraph of text
|
|
|
131
129
|
|
|
132
130
|
### Relative _and_ absolute timestamps
|
|
133
131
|
|
|
134
|
-
By default, keyframes are specificed by _relative_ time. (“Do this two seconds after that last keyframe.”) This makes it trivial to swap pieces of an animation around—just cut and paste—without having to redo all the keyframe timings. Our TL;DR example uses the `key` command to illustrate this workflow, but we could have also used the slightly more descriptive `rel` (“relative”) alias to accomplish the exact same thing. All relative times are relative to the
|
|
132
|
+
By default, keyframes are specificed by _relative_ time. (“Do this two seconds after that last keyframe.”) This makes it trivial to swap pieces of an animation around—just cut and paste—without having to redo all the keyframe timings. Our [TL;DR example](#tldr) uses the `key` command to illustrate this workflow, but we could have also used the slightly more descriptive `rel` (“relative”) alias to accomplish the exact same thing. All relative times are relative to the _most recently created keyframe_ as determined the moment the `key` or `rel` command is processed. (And yes, you can specify a _negative_ relative time—if you’re into that sort of thing.) What about your _first_ keyframe—which has no prior keyframe to be relative to? Consider it relative to _zero_—which makes it both relative _and_ absolute. Note the use of the alias `rel` here rather than `key`:
|
|
135
133
|
|
|
136
134
|
```javascript
|
|
137
135
|
var now = Date.now()
|
|
@@ -174,7 +172,7 @@ new Pacer()
|
|
|
174
172
|
.onKey(()=> console.log( '2nd keyframe' ))
|
|
175
173
|
```
|
|
176
174
|
|
|
177
|
-
When you create a keyframe, it is added to your instance’s `keys` array, and that array of keyframes is then sorted in chronological order according to each keyframe’s absolute time. Meanwhile, your instance also keeps track of the last keyframe you have
|
|
175
|
+
When you create a keyframe, it is added to your instance’s `keys` array, and that array of keyframes is then sorted in chronological order according to each keyframe’s absolute time. (This ensures your `keys` array is always tidy.) Meanwhile, your instance also keeps track of the last keyframe you have created via a `lastCreatedKey` property, so that subsequent commands like `onKey` or `tween` always refer to the “intuitively correct” keyframe. Your code reads like a short story.
|
|
178
176
|
|
|
179
177
|
|
|
180
178
|
|
|
@@ -240,7 +238,7 @@ Each easing equation includes its `in`, `out`, and `inOut` variants, eg. `Pacer.
|
|
|
240
238
|
|
|
241
239
|
### Every key, every tween
|
|
242
240
|
|
|
243
|
-
If you find you’re running the same callback over and over, perhaps you’d prefer to declare that just once? We’ve got you covered. Use `onEveryKey` to declare a callback that will fire on _every_ keyframe, and `onEveryTween` to do the same for all tweens. [Something borrowed, something blue. Every tween
|
|
241
|
+
If you find you’re running the same callback over and over, perhaps you’d prefer to declare that just once? We’ve got you covered. Use `onEveryKey` to declare a callback that will fire on _every_ keyframe, and `onEveryTween` to do the same for all tweens. [Something borrowed, something blue. Every key and tween for you](https://youtu.be/4YR_Mft7yIM).
|
|
244
242
|
|
|
245
243
|
|
|
246
244
|
```javascript
|
|
@@ -462,7 +460,7 @@ Another thing to note is that `update` expects an _absolute_ number, rather than
|
|
|
462
460
|
|
|
463
461
|
### Forward _and_ backward
|
|
464
462
|
|
|
465
|
-
Mathematically, [time can flow both forward _and_ backward](https://en.wikipedia.org/wiki/Tenet_(film)). Why would __Pacer__ ignore that reality? The ability to scrub a timeline back and forth is incredibly valuable, and literally the mechanism that our
|
|
463
|
+
Mathematically, [time can flow both forward _and_ backward](https://en.wikipedia.org/wiki/Tenet_(film)). Why would __Pacer__ ignore that reality? The ability to scrub a timeline back and forth is incredibly valuable, and literally the mechanism that our [__Scroll Pacer__](https://github.com/stewdio/scroll-pacer-js) toolkit uses for scroll-based animations. (More on this to come.) Rest assured that your `update` call can handle time flowing in either direction (and at any speed). It just works.
|
|
466
464
|
|
|
467
465
|
|
|
468
466
|
|