chnobli 0.1.2 → 0.1.3
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/.eslintignore +1 -0
- package/.github/workflows/ci.yaml +14 -1
- package/LICENSE.md +1 -1
- package/docs/INDEX.md +15 -0
- package/jsdoc.conf.json +1 -1
- package/package.json +6 -3
- package/src/animation/animation.js +88 -25
- package/src/animation/property.js +408 -43
- package/src/animation/public.js +72 -2
- package/src/animator.js +2 -0
- package/src/chnobli.js +8 -3
- package/src/responsive/event.js +46 -0
- package/src/responsive/testevent.js +31 -0
- package/src/scroll/event.js +1 -1
- package/src/stagger/stagger.js +5 -8
- package/src/target/basetarget.js +92 -0
- package/src/target/domtarget.js +266 -0
- package/src/target/target.js +22 -0
- package/src/target/testdomnode.js +61 -0
- package/src/tests/properties.test.js +150 -0
- package/src/tests/timeline.test.js +234 -0
- package/src/tests/timing.test.js +270 -0
- package/src/tests/usecases.test.js +64 -0
- package/src/tests/values.test.js +162 -0
- package/src/timeline/public.js +192 -13
- package/src/timeline/timeline.js +76 -22
- package/src/timing/testticker.js +98 -0
- package/src/timing/ticker.js +49 -9
- package/src/timing/timing.js +121 -21
- package/src/utils/events.js +4 -0
- package/src/utils/motionpath.js +3 -0
- package/src/utils/utils.js +22 -2
- package/src/values/colorvalue.js +167 -0
- package/src/values/defaultcolors.js +148 -0
- package/src/values/stylevalue.js +148 -0
- package/src/{utils → values}/value.js +15 -7
- package/src/utils/target.js +0 -164
package/src/timeline/public.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { takeProp } from '../utils/internal.js';
|
|
2
2
|
import Timeline from './timeline.js';
|
|
3
|
-
import {
|
|
3
|
+
import { STATE_ENDED } from '../timing/timing.js';
|
|
4
4
|
import { callStagger } from '../stagger/stagger.js';
|
|
5
5
|
import Events from '../utils/events.js';
|
|
6
|
+
import ResponsiveEvent from '../responsive/event.js';
|
|
6
7
|
|
|
7
8
|
const STATE_PAUSED = 0;
|
|
8
9
|
const STATE_RENDER_ONCE = 1;
|
|
@@ -11,20 +12,52 @@ const STATE_PLAYING = 2;
|
|
|
11
12
|
export default class PublicTimeline {
|
|
12
13
|
constructor(props = {}) {
|
|
13
14
|
this._defaults = takeProp(props, 'defaults', {});
|
|
15
|
+
this._responsive = takeProp(props, 'responsive', true);
|
|
16
|
+
|
|
17
|
+
if (typeof this._responsive !== 'boolean')
|
|
18
|
+
throw new Error('responsive can only be a boolean');
|
|
19
|
+
if ('duration' in props)
|
|
20
|
+
throw new Error('a timeline does not accept a duration');
|
|
21
|
+
|
|
14
22
|
this._inner = new Timeline(props);
|
|
15
23
|
|
|
16
24
|
// 'start', 'end'
|
|
17
25
|
this._events = new Events;
|
|
18
26
|
|
|
19
27
|
this._state = STATE_PAUSED;
|
|
28
|
+
this._renderedOnce = false;
|
|
20
29
|
this._runningTicker = null;
|
|
30
|
+
this._responsiveBlocks = [];
|
|
31
|
+
if (this._responsive) {
|
|
32
|
+
this._responsiveEvent = ResponsiveEvent.global()?.add((...args) => {
|
|
33
|
+
this._onResponsive(...args);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// setup callback so we can notify our listeners
|
|
37
|
+
this._inner.setBeforeResponsiveFn(() => {
|
|
38
|
+
this._responsiveBlocks.forEach(block => {
|
|
39
|
+
block.responsive();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
this._responsiveEvent = null;
|
|
44
|
+
}
|
|
21
45
|
}
|
|
22
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Set's properties
|
|
49
|
+
* offset can be staggered, a number, a label or a string `+=10`
|
|
50
|
+
*/
|
|
23
51
|
set(targets, props, offset = null) {
|
|
24
52
|
// for the moment let's just add as usual but set the duration to 0
|
|
25
53
|
return this.add(targets, { ...props, duration: 0 }, offset);
|
|
26
54
|
}
|
|
27
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Add an animation to the timeline
|
|
58
|
+
*
|
|
59
|
+
* offset can be staggered, a number, a label or a string `+=10`
|
|
60
|
+
*/
|
|
28
61
|
add(targets, props, offset = null) {
|
|
29
62
|
if (Array.from(targets).length === 0)
|
|
30
63
|
targets = [targets];
|
|
@@ -52,12 +85,41 @@ export default class PublicTimeline {
|
|
|
52
85
|
return this;
|
|
53
86
|
}
|
|
54
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Adds a label at to the current nOffse
|
|
90
|
+
*
|
|
91
|
+
* This label can then be used in offsets
|
|
92
|
+
*/
|
|
55
93
|
label(label, offset = null) {
|
|
56
94
|
this._inner.label(label, offset);
|
|
57
95
|
|
|
58
96
|
return this;
|
|
59
97
|
}
|
|
60
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Adds a responsive function call which get's called before the responsive
|
|
101
|
+
* functions in properties
|
|
102
|
+
*/
|
|
103
|
+
addResponsive(responsive) {
|
|
104
|
+
if (!this._responsiveEvent)
|
|
105
|
+
throw new Error('this timeline is not responsive');
|
|
106
|
+
|
|
107
|
+
if (Array.isArray(responsive)) {
|
|
108
|
+
responsive.forEach(resp => this.addResponsive(resp));
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof responsive.responsive !== 'function')
|
|
113
|
+
throw new Error('expected a responsive() function');
|
|
114
|
+
|
|
115
|
+
this._responsiveBlocks.push(responsive);
|
|
116
|
+
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Starts to play the timeline if it hasn't started
|
|
122
|
+
*/
|
|
61
123
|
play() {
|
|
62
124
|
if (this._state === STATE_PLAYING)
|
|
63
125
|
return;
|
|
@@ -69,6 +131,9 @@ export default class PublicTimeline {
|
|
|
69
131
|
return this;
|
|
70
132
|
}
|
|
71
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Pause the timeline at the current position
|
|
136
|
+
*/
|
|
72
137
|
pause() {
|
|
73
138
|
if (this._state === STATE_PAUSED)
|
|
74
139
|
return;
|
|
@@ -78,7 +143,25 @@ export default class PublicTimeline {
|
|
|
78
143
|
this._stopTicker();
|
|
79
144
|
}
|
|
80
145
|
|
|
81
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Seeks to a position in the timeline
|
|
148
|
+
*/
|
|
149
|
+
seekMs(ms) {
|
|
150
|
+
this._inner.init();
|
|
151
|
+
|
|
152
|
+
this._inner.seekMs(ms);
|
|
153
|
+
|
|
154
|
+
if (this._state !== STATE_PLAYING)
|
|
155
|
+
this._state = STATE_RENDER_ONCE;
|
|
156
|
+
|
|
157
|
+
this._startTicker();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Seeks to a normalized position in the timeline
|
|
162
|
+
*
|
|
163
|
+
* @param pos = number between 0 and 1
|
|
164
|
+
*/
|
|
82
165
|
seek(pos) {
|
|
83
166
|
this._inner.init();
|
|
84
167
|
|
|
@@ -90,23 +173,94 @@ export default class PublicTimeline {
|
|
|
90
173
|
this._startTicker();
|
|
91
174
|
}
|
|
92
175
|
|
|
176
|
+
// seek in Range (useful for scroll timelines)
|
|
177
|
+
// seek(startOffset, endOffset, pos)
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Resets the current timeline to the start
|
|
181
|
+
* without changing anything
|
|
182
|
+
*/
|
|
93
183
|
reset() {
|
|
94
184
|
this._inner.seek(0);
|
|
95
185
|
}
|
|
96
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Resets every used prop to it's previous value
|
|
189
|
+
*/
|
|
190
|
+
resetProps() {
|
|
191
|
+
const reversed = this._inner.timing.reverse;
|
|
192
|
+
this._inner.timing.reverse = false;
|
|
193
|
+
this._inner.seek(-1);
|
|
194
|
+
this._inner.render();
|
|
195
|
+
this._inner.ticker.applyTargets();
|
|
196
|
+
|
|
197
|
+
this._inner.timing.reverse = reversed;
|
|
198
|
+
this._inner.seek(-1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Recalculates all values this should be called for example after a resize
|
|
203
|
+
*/
|
|
204
|
+
update() {
|
|
205
|
+
this._inner.update();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns wether the timeline is set to reversed
|
|
210
|
+
*/
|
|
211
|
+
isReversed() {
|
|
212
|
+
return this._inner.timing.reversed;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Set's the timeline to a specific direction
|
|
217
|
+
*/
|
|
218
|
+
setReversed(reversed) {
|
|
219
|
+
if (this.isReversed() == reversed)
|
|
220
|
+
return;
|
|
221
|
+
this.reverse();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Reverses the order of the current timeline
|
|
226
|
+
*/
|
|
97
227
|
reverse() {
|
|
98
228
|
this._inner.timing.reverse();
|
|
99
229
|
}
|
|
100
230
|
|
|
101
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Listen on events
|
|
233
|
+
*
|
|
234
|
+
* returns a function to remove the event listener
|
|
235
|
+
*
|
|
236
|
+
* ## Events
|
|
237
|
+
*
|
|
238
|
+
* ### end
|
|
239
|
+
* Get's executed once the timeline comes to the end
|
|
240
|
+
* Does not get executed during seeking
|
|
241
|
+
*
|
|
242
|
+
*/
|
|
102
243
|
on(event, fn) {
|
|
103
244
|
return this._events.add(event, fn);
|
|
104
245
|
}
|
|
105
246
|
|
|
247
|
+
// not wroking
|
|
106
248
|
onPromise(event) {
|
|
107
249
|
return this._events.wait(event);
|
|
108
250
|
}
|
|
109
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Destroys this timeline and resets all props
|
|
254
|
+
*/
|
|
255
|
+
destroy() {
|
|
256
|
+
this._stopTicker();
|
|
257
|
+
this._inner.destroy();
|
|
258
|
+
this._events.destroy();
|
|
259
|
+
this._responsiveEvent?.remove();
|
|
260
|
+
this._responsiveBlocks = [];
|
|
261
|
+
this._inner = null;
|
|
262
|
+
}
|
|
263
|
+
|
|
110
264
|
_startTicker() {
|
|
111
265
|
if (this._runningTicker)
|
|
112
266
|
return;
|
|
@@ -114,16 +268,30 @@ export default class PublicTimeline {
|
|
|
114
268
|
this._inner.init();
|
|
115
269
|
|
|
116
270
|
this._runningTicker = this._inner.ticker.add(change => {
|
|
117
|
-
if (this._inner.timing.state
|
|
118
|
-
this.
|
|
119
|
-
|
|
271
|
+
if (this._inner.timing.state >= STATE_ENDED) {
|
|
272
|
+
if (!this._renderedOnce) {
|
|
273
|
+
this._renderedOnce = true;
|
|
274
|
+
this._inner.render();
|
|
275
|
+
}
|
|
120
276
|
|
|
121
|
-
|
|
277
|
+
if (this._state === STATE_PLAYING)
|
|
278
|
+
this._events.trigger('end');
|
|
279
|
+
|
|
280
|
+
this._state = STATE_PAUSED;
|
|
122
281
|
this._stopTicker();
|
|
123
|
-
|
|
124
|
-
return;
|
|
282
|
+
return
|
|
125
283
|
}
|
|
126
284
|
|
|
285
|
+
// if (this._inner.timing.state <= STATE_START) {
|
|
286
|
+
// this._events.trigger('start');
|
|
287
|
+
// }
|
|
288
|
+
|
|
289
|
+
// if (this._inner.timing.state >= STATE_ENDED) {
|
|
290
|
+
// this._stopTicker();
|
|
291
|
+
// this._events.trigger('end');
|
|
292
|
+
// return;
|
|
293
|
+
// }
|
|
294
|
+
|
|
127
295
|
if (this._state === STATE_PLAYING)
|
|
128
296
|
this._inner.advance(change);
|
|
129
297
|
|
|
@@ -131,6 +299,15 @@ export default class PublicTimeline {
|
|
|
131
299
|
|
|
132
300
|
this._inner.render();
|
|
133
301
|
|
|
302
|
+
if (this._inner.timing.state >= STATE_ENDED) {
|
|
303
|
+
if (this._state === STATE_PLAYING)
|
|
304
|
+
this._events.trigger('end');
|
|
305
|
+
|
|
306
|
+
this._state = STATE_PAUSED;
|
|
307
|
+
this._stopTicker();
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
|
|
134
311
|
// we rendered once let's stop
|
|
135
312
|
if (this._state === STATE_RENDER_ONCE) {
|
|
136
313
|
this._state = STATE_PAUSED;
|
|
@@ -147,8 +324,10 @@ export default class PublicTimeline {
|
|
|
147
324
|
this._runningTicker = null;
|
|
148
325
|
}
|
|
149
326
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
327
|
+
_onResponsive(_obj) {
|
|
328
|
+
// // make sure we don't render something if we never did
|
|
329
|
+
// if (!this._renderedOnce)
|
|
330
|
+
// return;
|
|
331
|
+
this._inner.update();
|
|
332
|
+
}
|
|
154
333
|
}
|
package/src/timeline/timeline.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { takeProp } from '../utils/internal.js';
|
|
2
2
|
import Timing, {
|
|
3
3
|
parseEase, parseRepeat, parseAlternate, parseReversed,
|
|
4
|
-
|
|
4
|
+
STATE_BEFORE, STATE_ENDED
|
|
5
5
|
} from '../timing/timing.js';
|
|
6
6
|
import Ticker from '../timing/ticker.js';
|
|
7
7
|
import Animation from '../animation/animation.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Timeline is the most versatile class
|
|
11
|
+
*
|
|
12
|
+
* A timeline can contain multiple animation, ordered one after the other or
|
|
13
|
+
* offset.
|
|
14
|
+
*/
|
|
9
15
|
export default class Timeline {
|
|
10
16
|
/*
|
|
11
17
|
{
|
|
@@ -29,6 +35,7 @@ export default class Timeline {
|
|
|
29
35
|
// { type: label|animation, value: {}, offset, start: x, end: y, render }
|
|
30
36
|
this.entries = [];
|
|
31
37
|
|
|
38
|
+
this._beforeResponsiveFn = () => {};
|
|
32
39
|
this._initialized = false;
|
|
33
40
|
this._renderQueue = null;
|
|
34
41
|
}
|
|
@@ -62,12 +69,23 @@ export default class Timeline {
|
|
|
62
69
|
return this;
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
// set's a function which get's called before any responsive call
|
|
73
|
+
setBeforeResponsiveFn(fn) {
|
|
74
|
+
this._beforeResponsiveFn = fn;
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
advance(change) {
|
|
66
78
|
this.timing.advance(change);
|
|
67
79
|
|
|
68
80
|
this._updateTimings();
|
|
69
81
|
}
|
|
70
82
|
|
|
83
|
+
seekMs(ms) {
|
|
84
|
+
this.timing.seekMs(ms);
|
|
85
|
+
|
|
86
|
+
this._updateTimings();
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
seek(pos) {
|
|
72
90
|
this.timing.seek(pos);
|
|
73
91
|
|
|
@@ -116,43 +134,44 @@ export default class Timeline {
|
|
|
116
134
|
|
|
117
135
|
this.timing.setDuration(duration);
|
|
118
136
|
|
|
119
|
-
this.
|
|
137
|
+
this._beforeResponsiveFn();
|
|
120
138
|
|
|
121
|
-
|
|
139
|
+
this._initAnimations();
|
|
122
140
|
}
|
|
123
141
|
|
|
124
|
-
_initAnimations() {
|
|
125
|
-
// we don't
|
|
142
|
+
_initAnimations(reset = false) {
|
|
143
|
+
// we don't wan't to have a reversed timing
|
|
126
144
|
const reversed = this.timing.reversed;
|
|
127
145
|
this.timing.reversed = false;
|
|
128
146
|
|
|
129
|
-
// now initialize all
|
|
147
|
+
// now initialize all animations
|
|
148
|
+
// initialization means
|
|
149
|
+
// we need to define which value should be used as the from and the to
|
|
150
|
+
// value
|
|
130
151
|
const startOrdered = this.entries.filter(e => e.type === 'animation');
|
|
131
152
|
startOrdered.sort((a, b) => a.start - b.start);
|
|
132
153
|
|
|
133
154
|
for (let i = 0; i < startOrdered.length; i++) {
|
|
134
155
|
const entry = startOrdered[i];
|
|
135
156
|
|
|
136
|
-
this.timing.
|
|
157
|
+
this.timing.seekMs(entry.start);
|
|
137
158
|
|
|
138
|
-
const pos = this.timing.
|
|
159
|
+
const pos = this.timing.positionMs();
|
|
139
160
|
|
|
140
161
|
// seek all previous and render
|
|
141
162
|
for (let y = 0; y < i; y++) {
|
|
142
163
|
const prevEntry = startOrdered[y];
|
|
143
164
|
const prevAnimation = prevEntry.value;
|
|
144
165
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
prevAnimation.seek(p);
|
|
166
|
+
prevAnimation.seekMs(pos - prevEntry.start);
|
|
148
167
|
prevAnimation.render();
|
|
149
168
|
}
|
|
150
169
|
|
|
151
|
-
entry.value.init();
|
|
170
|
+
entry.value.init(reset);
|
|
152
171
|
}
|
|
153
172
|
|
|
154
|
-
this.timing.seek(-1);
|
|
155
173
|
this.timing.reversed = reversed;
|
|
174
|
+
this.timing.seek(-1);
|
|
156
175
|
startOrdered.forEach(e => {
|
|
157
176
|
e.value.seek(-1);
|
|
158
177
|
e.value.render();
|
|
@@ -185,29 +204,64 @@ export default class Timeline {
|
|
|
185
204
|
active: []
|
|
186
205
|
};
|
|
187
206
|
|
|
188
|
-
const pos = this.timing.
|
|
207
|
+
const pos = this.timing.positionMsUnbounded();
|
|
189
208
|
|
|
190
209
|
for (const entry of this.entries) {
|
|
191
210
|
if (entry.type !== 'animation')
|
|
192
211
|
continue;
|
|
193
212
|
|
|
194
213
|
const animation = entry.value;
|
|
214
|
+
animation.seekMs(pos - entry.start);
|
|
195
215
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const prevState = animation.timing.state;
|
|
199
|
-
|
|
200
|
-
animation.seek(p);
|
|
201
|
-
|
|
202
|
-
if (p > 1) {
|
|
216
|
+
if (animation.timing.state >= STATE_ENDED) {
|
|
203
217
|
this._renderQueue.passed.push(animation);
|
|
204
|
-
} else if (
|
|
218
|
+
} else if (animation.timing.state <= STATE_BEFORE) {
|
|
205
219
|
this._renderQueue.upcoming.push(animation);
|
|
206
220
|
} else {
|
|
207
221
|
this._renderQueue.active.push(animation);
|
|
208
222
|
}
|
|
209
223
|
}
|
|
210
224
|
}
|
|
225
|
+
|
|
226
|
+
update() {
|
|
227
|
+
if (!this._initialized)
|
|
228
|
+
return;
|
|
229
|
+
|
|
230
|
+
const pos = this.timing.savePosition();
|
|
231
|
+
const reversed = this.timing.reversed;
|
|
232
|
+
this.timing.reversed = false;
|
|
233
|
+
|
|
234
|
+
this.timing.seek(-1);
|
|
235
|
+
this._updateTimings();
|
|
236
|
+
this.render();
|
|
237
|
+
this.ticker.applyTargets();
|
|
238
|
+
|
|
239
|
+
this._beforeResponsiveFn();
|
|
240
|
+
|
|
241
|
+
// we now have removed every style we set
|
|
242
|
+
// we should be able to recalculate everything now
|
|
243
|
+
this._initAnimations(true);
|
|
244
|
+
|
|
245
|
+
this.timing.reversed = reversed;
|
|
246
|
+
this.timing.restorePosition(pos);
|
|
247
|
+
this._updateTimings();
|
|
248
|
+
this.render();
|
|
249
|
+
this.ticker.applyTargets();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
destroy() {
|
|
253
|
+
for (const entry of this.entries) {
|
|
254
|
+
if (entry.type !== 'animation')
|
|
255
|
+
continue;
|
|
256
|
+
|
|
257
|
+
const animation = entry.value;
|
|
258
|
+
animation.destroy();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.entries = [];
|
|
262
|
+
this._beforeResponsiveFn = null;
|
|
263
|
+
this.ticker.applyTargets();
|
|
264
|
+
}
|
|
211
265
|
}
|
|
212
266
|
|
|
213
267
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { GlobalTargets } from './ticker.js';
|
|
2
|
+
|
|
3
|
+
export default class TestTicker {
|
|
4
|
+
constructor() {
|
|
5
|
+
this._listeners = new Set;
|
|
6
|
+
this.targets = new GlobalTargets;
|
|
7
|
+
|
|
8
|
+
this.running = false;
|
|
9
|
+
|
|
10
|
+
this._previousTick = null;
|
|
11
|
+
|
|
12
|
+
globalThis.chnobliTicker = this;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
registerTarget(target) {
|
|
16
|
+
return this.targets.register(target);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
unregisterTarget(target) {
|
|
20
|
+
return this.targets.unregister(target);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
applyTargets() {
|
|
24
|
+
return this.targets.apply();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
add(fn) {
|
|
28
|
+
this._listeners.add(fn);
|
|
29
|
+
|
|
30
|
+
if (!this.running)
|
|
31
|
+
this.running = true;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
remove: () => {
|
|
35
|
+
this.remove(fn);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
remove(fn) {
|
|
41
|
+
this._listeners.delete(fn);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// max expects a number for how many ms the ticker should run
|
|
45
|
+
///
|
|
46
|
+
/// runs until the ticker ends this allows to run
|
|
47
|
+
run(max = 3000, step = 1) {
|
|
48
|
+
if (!this.running)
|
|
49
|
+
return true;
|
|
50
|
+
|
|
51
|
+
if (this._previousTick === null)
|
|
52
|
+
this.tick(0);
|
|
53
|
+
|
|
54
|
+
let i = 0;
|
|
55
|
+
const start = this._previousTick ?? 0;
|
|
56
|
+
|
|
57
|
+
for (i = 1; i <= max; i += step) {
|
|
58
|
+
if (!this.tick(start + i))
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// elapsed in ms
|
|
66
|
+
tick(elapsed) {
|
|
67
|
+
if (!this.running)
|
|
68
|
+
return false;
|
|
69
|
+
|
|
70
|
+
if (this._previousTick === null)
|
|
71
|
+
this._previousTick = elapsed;
|
|
72
|
+
|
|
73
|
+
let change = elapsed - this._previousTick;
|
|
74
|
+
// lagSmoothing
|
|
75
|
+
if (change > 500) {
|
|
76
|
+
console.log('ticker lagged');
|
|
77
|
+
change = 33;
|
|
78
|
+
}
|
|
79
|
+
this._previousTick = elapsed;
|
|
80
|
+
|
|
81
|
+
for (const fn of this._listeners) {
|
|
82
|
+
fn(change, {
|
|
83
|
+
// changes,
|
|
84
|
+
remove: () => this.remove(fn)
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.targets.apply();
|
|
89
|
+
|
|
90
|
+
if (this._listeners.size === 0) {
|
|
91
|
+
this.running = false;
|
|
92
|
+
this._previousTick = null;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/timing/ticker.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { newTarget } from '../target/target.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The ticker is the main driver for all animations
|
|
5
|
+
*
|
|
6
|
+
* it also manages all targets allowing to only call the dom api
|
|
7
|
+
* at the end of every frame
|
|
8
|
+
*/
|
|
5
9
|
export default class Ticker {
|
|
6
10
|
constructor() {
|
|
7
11
|
this._listeners = new Set;
|
|
@@ -13,14 +17,30 @@ export default class Ticker {
|
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
static global() {
|
|
20
|
+
// allow to define a test ticker
|
|
21
|
+
if (typeof globalThis !== 'undefined' && globalThis.chnobliTicker)
|
|
22
|
+
return globalThis.chnobliTicker;
|
|
23
|
+
|
|
16
24
|
if (typeof window === 'undefined')
|
|
17
|
-
|
|
25
|
+
throw new Error('can only be executed in a browser context');
|
|
18
26
|
|
|
19
27
|
if (typeof window.chnobliTicker === 'undefined')
|
|
20
28
|
window.chnobliTicker = new Ticker;
|
|
21
29
|
return window.chnobliTicker;
|
|
22
30
|
}
|
|
23
31
|
|
|
32
|
+
registerTarget(target) {
|
|
33
|
+
return this._targets.register(target);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
unregisterTarget(target) {
|
|
37
|
+
return this._targets.unregister(target);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
applyTargets() {
|
|
41
|
+
return this._targets.apply();
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
add(fn) {
|
|
25
45
|
this._listeners.add(fn);
|
|
26
46
|
|
|
@@ -91,24 +111,44 @@ export default class Ticker {
|
|
|
91
111
|
// }
|
|
92
112
|
// }
|
|
93
113
|
|
|
94
|
-
class GlobalTargets {
|
|
114
|
+
export class GlobalTargets {
|
|
95
115
|
constructor() {
|
|
116
|
+
// {target, ref}
|
|
96
117
|
this._targets = new Map;
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
register(target) {
|
|
100
121
|
let storedTarget = this._targets.get(target);
|
|
101
122
|
if (!storedTarget) {
|
|
102
|
-
storedTarget =
|
|
123
|
+
storedTarget = {
|
|
124
|
+
target: newTarget(target),
|
|
125
|
+
ref: 0
|
|
126
|
+
};
|
|
103
127
|
this._targets.set(target, storedTarget);
|
|
104
128
|
}
|
|
129
|
+
storedTarget.ref += 1;
|
|
130
|
+
|
|
131
|
+
return storedTarget.target;
|
|
132
|
+
}
|
|
105
133
|
|
|
106
|
-
|
|
134
|
+
unregister(target) {
|
|
135
|
+
const storedTarget = this._targets.get(target);
|
|
136
|
+
storedTarget.ref -= 1;
|
|
107
137
|
}
|
|
108
138
|
|
|
109
139
|
apply() {
|
|
110
|
-
for (const
|
|
140
|
+
for (const storedTarget of this._targets.values()) {
|
|
141
|
+
const { target, ref } = storedTarget;
|
|
111
142
|
target.apply();
|
|
143
|
+
|
|
144
|
+
if (ref <= 0)
|
|
145
|
+
this._targets.delete(target.target);
|
|
112
146
|
}
|
|
113
147
|
}
|
|
148
|
+
|
|
149
|
+
size() {
|
|
150
|
+
return this._targets.size;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// todo we should probly run some kind of gc
|
|
114
154
|
}
|