chnobli 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chnobli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "author": "Sören Meier <info@soerenmeier.ch>",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -231,4 +231,43 @@ describe('timeline', () => {
231
231
  ticker.run();
232
232
  expect(endCounter).toBe(4);
233
233
  });
234
+
235
+ it('nested timeline', () => {
236
+ const ticker = new TestTicker;
237
+
238
+ const target = el();
239
+ const tl = timeline()
240
+ .add(target, {
241
+ width: 50,
242
+ duration: 10
243
+ })
244
+ .nest(tl => {
245
+ tl.add(target, {
246
+ opacity: 0,
247
+ duration: 5
248
+ })
249
+ .add(target, {
250
+ opacity: 1,
251
+ duration: 5
252
+ });
253
+ })
254
+ .add(target, {
255
+ height: 50,
256
+ duration: 10
257
+ })
258
+ .play();
259
+
260
+ ticker.run(0);
261
+ expect(target.style.width).toBe('0.000px');
262
+ ticker.run(10);
263
+ expect(target.style.width).toBe('50.000px');
264
+ expect(target.style.opacity).toBe('1.000');
265
+ ticker.run(5);
266
+ expect(target.style.opacity).toBe('0.000');
267
+ ticker.run(5);
268
+ expect(target.style.opacity).toBe('1.000');
269
+ expect(target.style.height).toBe('0.000px');
270
+ ticker.run(10);
271
+ expect(target.style.height).toBe('50.000px');
272
+ });
234
273
  });
@@ -0,0 +1,61 @@
1
+ import { takeProp } from '../utils/internal.js';
2
+ import Timeline from './timeline.js';
3
+ import { timelineAdd } from './public.js';
4
+
5
+
6
+ export default class NestedTimeline {
7
+ constructor(props = {}) {
8
+ this._defaults = takeProp(props, 'defaults', {});
9
+
10
+ if ('duration' in props)
11
+ throw new Error('a timeline does not accept a duration');
12
+
13
+ this._inner = new Timeline(props);
14
+ }
15
+
16
+ /**
17
+ * Set's properties
18
+ * offset can be staggered, a number, a label or a string `+=10`
19
+ */
20
+ set(targets, props, offset = null) {
21
+ // for the moment let's just add as usual but set the duration to 0
22
+ return this.add(targets, { ...props, duration: 0 }, offset);
23
+ }
24
+
25
+ /**
26
+ * Add an animation to the timeline
27
+ *
28
+ * offset can be staggered, a number, a label or a string `+=10`
29
+ */
30
+ add(targets, props, offset = null) {
31
+ timelineAdd(this, targets, props, offset);
32
+
33
+ return this;
34
+ }
35
+
36
+ /**
37
+ * Adds a label at to the current nOffse
38
+ *
39
+ * This label can then be used in offsets
40
+ */
41
+ label(label, offset = null) {
42
+ this._inner.label(label, offset);
43
+
44
+ return this;
45
+ }
46
+
47
+ /**
48
+ * Allows to nest timelines
49
+ *
50
+ * fn: (timeline)
51
+ */
52
+ nest(fn, opts = {}, offset = null) {
53
+ const tl = new NestedTimeline(opts);
54
+
55
+ fn(tl);
56
+
57
+ this._inner.addTimeline(tl._inner, offset);
58
+
59
+ return this;
60
+ }
61
+ }
@@ -4,6 +4,7 @@ import { STATE_ENDED } from '../timing/timing.js';
4
4
  import { callStagger } from '../stagger/stagger.js';
5
5
  import Events from '../utils/events.js';
6
6
  import ResponsiveEvent from '../responsive/event.js';
7
+ import NestedTimeline from './nested.js';
7
8
 
8
9
  const STATE_PAUSED = 0;
9
10
  const STATE_RENDER_ONCE = 1;
@@ -59,28 +60,7 @@ export default class PublicTimeline {
59
60
  * offset can be staggered, a number, a label or a string `+=10`
60
61
  */
61
62
  add(targets, props, offset = null) {
62
- if (Array.from(targets).length === 0)
63
- targets = [targets];
64
- else
65
- targets = Array.from(targets);
66
-
67
- let i = -1;
68
- for (const target of targets) {
69
- i++;
70
-
71
- const nProps = {
72
- ...this._defaults,
73
- ...props
74
- };
75
-
76
- for (const prop in nProps) {
77
- nProps[prop] = callStagger(nProps[prop], i, targets.length);
78
- }
79
-
80
- const nOffset = callStagger(offset, i);
81
-
82
- this._inner.add(target, nProps, nOffset, i);
83
- }
63
+ timelineAdd(this, targets, props, offset);
84
64
 
85
65
  return this;
86
66
  }
@@ -96,6 +76,21 @@ export default class PublicTimeline {
96
76
  return this;
97
77
  }
98
78
 
79
+ /**
80
+ * Allows to nest timelines
81
+ *
82
+ * fn: (timeline)
83
+ */
84
+ nest(fn, opts = {}, offset = null) {
85
+ const tl = new NestedTimeline(opts);
86
+
87
+ fn(tl);
88
+
89
+ this._inner.addTimeline(tl._inner, offset);
90
+
91
+ return this;
92
+ }
93
+
99
94
  /**
100
95
  * Adds a responsive function call which get's called before the responsive
101
96
  * functions in properties
@@ -330,4 +325,33 @@ export default class PublicTimeline {
330
325
  // return;
331
326
  this._inner.update();
332
327
  }
328
+ }
329
+
330
+ /**
331
+ * Add an animation to the timeline
332
+ * tl needs to have _defaults, and _inner
333
+ */
334
+ export function timelineAdd(tl, targets, props, offset = null) {
335
+ if (Array.from(targets).length === 0)
336
+ targets = [targets];
337
+ else
338
+ targets = Array.from(targets);
339
+
340
+ let i = -1;
341
+ for (const target of targets) {
342
+ i++;
343
+
344
+ const nProps = {
345
+ ...tl._defaults,
346
+ ...props
347
+ };
348
+
349
+ for (const prop in nProps) {
350
+ nProps[prop] = callStagger(nProps[prop], i, targets.length);
351
+ }
352
+
353
+ const nOffset = callStagger(offset, i);
354
+
355
+ tl._inner.add(target, nProps, nOffset, i);
356
+ }
333
357
  }
@@ -40,6 +40,12 @@ export default class Timeline {
40
40
  this._renderQueue = null;
41
41
  }
42
42
 
43
+ isFinite() {
44
+ return !this.entries.some(e => {
45
+ return e.type === 'animation' && !e.value.isFinite();
46
+ });
47
+ }
48
+
43
49
  // relative represents the point which the offset should be taken
44
50
  // if relative 1 is done the offset will be taken from the previous - 1
45
51
  add(targets, props, offset = null, relative = 0) {
@@ -58,6 +64,18 @@ export default class Timeline {
58
64
  return this;
59
65
  }
60
66
 
67
+ addTimeline(tl, offset = null) {
68
+ if (!tl.isFinite())
69
+ throw new Error('the animation needs to be finite');
70
+
71
+ this.entries.push({
72
+ type: 'timeline',
73
+ value: tl,
74
+ offset: new Offset(offset),
75
+ relativeOffset: 0
76
+ });
77
+ }
78
+
61
79
  label(label, offset = null) {
62
80
  this.entries.push({
63
81
  type: 'label',
@@ -123,6 +141,11 @@ export default class Timeline {
123
141
  if (entry.type === 'animation')
124
142
  end += entry.value.duration;
125
143
 
144
+ if (entry.type === 'timeline') {
145
+ entry.value.init();
146
+ end += entry.value.timing.duration;
147
+ }
148
+
126
149
  if (entry.type === 'label')
127
150
  labels[entry.value] = start;
128
151
 
@@ -148,7 +171,9 @@ export default class Timeline {
148
171
  // initialization means
149
172
  // we need to define which value should be used as the from and the to
150
173
  // value
151
- const startOrdered = this.entries.filter(e => e.type === 'animation');
174
+ const startOrdered = this.entries.filter(e => {
175
+ return e.type === 'animation' || e.type === 'timeline';
176
+ });
152
177
  startOrdered.sort((a, b) => a.start - b.start);
153
178
 
154
179
  for (let i = 0; i < startOrdered.length; i++) {
@@ -207,7 +232,7 @@ export default class Timeline {
207
232
  const pos = this.timing.positionMsUnbounded();
208
233
 
209
234
  for (const entry of this.entries) {
210
- if (entry.type !== 'animation')
235
+ if (entry.type !== 'animation' && entry.type !== 'timeline')
211
236
  continue;
212
237
 
213
238
  const animation = entry.value;
@@ -251,6 +276,11 @@ export default class Timeline {
251
276
 
252
277
  destroy() {
253
278
  for (const entry of this.entries) {
279
+ if (entry.type === 'timeline') {
280
+ entry.value.destroy();
281
+ continue
282
+ }
283
+
254
284
  if (entry.type !== 'animation')
255
285
  continue;
256
286
 
@@ -43,13 +43,45 @@ export function chars(element) {
43
43
  element.dataset.processedChars = 'yes';
44
44
 
45
45
  return chars.map(c => {
46
+ if (c.trim().length === 0) {
47
+ element.appendChild(document.createTextNode(c));
48
+ return null;
49
+ }
50
+
46
51
  const span = document.createElement('span');
47
52
  span.textContent = c;
48
53
  span.style.display = 'inline-block';
49
54
  element.appendChild(span);
50
55
 
51
56
  return span;
52
- });
57
+ }).filter(c => !!c);
58
+ }
59
+
60
+ /**
61
+ * Converts an element with text into span with words
62
+ */
63
+ export function words(element) {
64
+ if (element.dataset.processedWords)
65
+ return Array.from(element.children);
66
+
67
+ const words = element.textContent.split(/(\s+)/);
68
+
69
+ element.textContent = '';
70
+ element.dataset.processedWords = 'yes';
71
+
72
+ return words.map(word => {
73
+ if (word.trim().length === 0) {
74
+ element.appendChild(document.createTextNode(word));
75
+ return null;
76
+ }
77
+
78
+ const span = document.createElement('span');
79
+ span.textContent = word;
80
+ span.style.display = 'inline-block';
81
+ element.appendChild(span);
82
+
83
+ return span;
84
+ }).filter(w => !!w);
53
85
  }
54
86
 
55
87
  /**