chnobli 0.1.8 → 0.1.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.
Files changed (202) hide show
  1. package/package.json +9 -5
  2. package/src/animation/Animation.ts +2 -2
  3. package/src/animation/public.ts +39 -5
  4. package/src/chnobli.ts +15 -11
  5. package/src/property/Property.ts +2 -0
  6. package/src/property/StyleProperty.ts +2 -2
  7. package/src/scroll/Scroll.ts +172 -88
  8. package/src/scroll/index.ts +8 -0
  9. package/src/scroll/public.ts +23 -12
  10. package/src/stagger/stagger.ts +28 -14
  11. package/src/target/ObjectTarget.ts +124 -0
  12. package/src/target/Target.ts +13 -0
  13. package/src/tests/properties.test.ts +51 -1
  14. package/src/timeline/Nested.ts +1 -1
  15. package/src/timeline/{TimeLine.ts → Timeline.ts} +21 -1
  16. package/src/timeline/public.ts +168 -47
  17. package/src/timing/Timing.ts +37 -3
  18. package/src/utils/internal.ts +6 -3
  19. package/src/utils/utils.ts +16 -7
  20. package/src/values/StyleValue.ts +13 -1
  21. package/tsconfig.json +0 -1
  22. package/.editorconfig +0 -11
  23. package/.github/workflows/ci.yaml +0 -34
  24. package/.prettierignore +0 -4
  25. package/.prettierrc +0 -6
  26. package/.vscode/settings.json +0 -4
  27. package/LICENSE.md +0 -21
  28. package/README.md +0 -5
  29. package/auto-docs/easing.js.html +0 -342
  30. package/auto-docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  31. package/auto-docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
  32. package/auto-docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  33. package/auto-docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  34. package/auto-docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  35. package/auto-docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  36. package/auto-docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  37. package/auto-docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
  38. package/auto-docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  39. package/auto-docs/fonts/OpenSans-Light-webfont.eot +0 -0
  40. package/auto-docs/fonts/OpenSans-Light-webfont.svg +0 -1831
  41. package/auto-docs/fonts/OpenSans-Light-webfont.woff +0 -0
  42. package/auto-docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  43. package/auto-docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  44. package/auto-docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  45. package/auto-docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  46. package/auto-docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
  47. package/auto-docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  48. package/auto-docs/global.html +0 -4810
  49. package/auto-docs/index.html +0 -65
  50. package/auto-docs/scripts/linenumber.js +0 -25
  51. package/auto-docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  52. package/auto-docs/scripts/prettify/lang-css.js +0 -2
  53. package/auto-docs/scripts/prettify/prettify.js +0 -28
  54. package/auto-docs/styles/jsdoc-default.css +0 -358
  55. package/auto-docs/styles/prettify-jsdoc.css +0 -111
  56. package/auto-docs/styles/prettify-tomorrow.css +0 -132
  57. package/dist/animation/Animation.d.ts +0 -36
  58. package/dist/animation/Animation.d.ts.map +0 -1
  59. package/dist/animation/Animation.js +0 -230
  60. package/dist/animation/Animation.js.map +0 -1
  61. package/dist/animation/public.d.ts +0 -58
  62. package/dist/animation/public.d.ts.map +0 -1
  63. package/dist/animation/public.js +0 -107
  64. package/dist/animation/public.js.map +0 -1
  65. package/dist/chnobli.d.ts +0 -16
  66. package/dist/chnobli.d.ts.map +0 -1
  67. package/dist/chnobli.js +0 -63
  68. package/dist/chnobli.js.map +0 -1
  69. package/dist/property/BasicProperty.d.ts +0 -38
  70. package/dist/property/BasicProperty.d.ts.map +0 -1
  71. package/dist/property/BasicProperty.js +0 -72
  72. package/dist/property/BasicProperty.js.map +0 -1
  73. package/dist/property/ClassProperty.d.ts +0 -32
  74. package/dist/property/ClassProperty.d.ts.map +0 -1
  75. package/dist/property/ClassProperty.js +0 -108
  76. package/dist/property/ClassProperty.js.map +0 -1
  77. package/dist/property/Property.d.ts +0 -13
  78. package/dist/property/Property.d.ts.map +0 -1
  79. package/dist/property/Property.js +0 -20
  80. package/dist/property/Property.js.map +0 -1
  81. package/dist/property/StyleProperty.d.ts +0 -25
  82. package/dist/property/StyleProperty.d.ts.map +0 -1
  83. package/dist/property/StyleProperty.js +0 -221
  84. package/dist/property/StyleProperty.js.map +0 -1
  85. package/dist/property/TransformProperty.d.ts +0 -15
  86. package/dist/property/TransformProperty.d.ts.map +0 -1
  87. package/dist/property/TransformProperty.js +0 -43
  88. package/dist/property/TransformProperty.js.map +0 -1
  89. package/dist/property/TransformXYProperty.d.ts +0 -28
  90. package/dist/property/TransformXYProperty.d.ts.map +0 -1
  91. package/dist/property/TransformXYProperty.js +0 -65
  92. package/dist/property/TransformXYProperty.js.map +0 -1
  93. package/dist/responsive/ResponsiveEvent.d.ts +0 -17
  94. package/dist/responsive/ResponsiveEvent.d.ts.map +0 -1
  95. package/dist/responsive/ResponsiveEvent.js +0 -45
  96. package/dist/responsive/ResponsiveEvent.js.map +0 -1
  97. package/dist/responsive/TestEvent.d.ts +0 -11
  98. package/dist/responsive/TestEvent.d.ts.map +0 -1
  99. package/dist/responsive/TestEvent.js +0 -26
  100. package/dist/responsive/TestEvent.js.map +0 -1
  101. package/dist/scroll/Scroll.d.ts +0 -27
  102. package/dist/scroll/Scroll.d.ts.map +0 -1
  103. package/dist/scroll/Scroll.js +0 -132
  104. package/dist/scroll/Scroll.js.map +0 -1
  105. package/dist/scroll/ScrollEvent.d.ts +0 -17
  106. package/dist/scroll/ScrollEvent.d.ts.map +0 -1
  107. package/dist/scroll/ScrollEvent.js +0 -39
  108. package/dist/scroll/ScrollEvent.js.map +0 -1
  109. package/dist/scroll/public.d.ts +0 -7
  110. package/dist/scroll/public.d.ts.map +0 -1
  111. package/dist/scroll/public.js +0 -17
  112. package/dist/scroll/public.js.map +0 -1
  113. package/dist/stagger/stagger.d.ts +0 -19
  114. package/dist/stagger/stagger.d.ts.map +0 -1
  115. package/dist/stagger/stagger.js +0 -41
  116. package/dist/stagger/stagger.js.map +0 -1
  117. package/dist/target/BaseTarget.old.d.ts +0 -33
  118. package/dist/target/BaseTarget.old.d.ts.map +0 -1
  119. package/dist/target/BaseTarget.old.js +0 -73
  120. package/dist/target/BaseTarget.old.js.map +0 -1
  121. package/dist/target/DomTarget.d.ts +0 -41
  122. package/dist/target/DomTarget.d.ts.map +0 -1
  123. package/dist/target/DomTarget.js +0 -230
  124. package/dist/target/DomTarget.js.map +0 -1
  125. package/dist/target/Target.d.ts +0 -22
  126. package/dist/target/Target.d.ts.map +0 -1
  127. package/dist/target/Target.js +0 -21
  128. package/dist/target/Target.js.map +0 -1
  129. package/dist/target/TestDomNode.d.ts +0 -14
  130. package/dist/target/TestDomNode.d.ts.map +0 -1
  131. package/dist/target/TestDomNode.js +0 -55
  132. package/dist/target/TestDomNode.js.map +0 -1
  133. package/dist/timeline/Nested.d.ts +0 -30
  134. package/dist/timeline/Nested.d.ts.map +0 -1
  135. package/dist/timeline/Nested.js +0 -51
  136. package/dist/timeline/Nested.js.map +0 -1
  137. package/dist/timeline/TimeLine.d.ts +0 -32
  138. package/dist/timeline/TimeLine.d.ts.map +0 -1
  139. package/dist/timeline/TimeLine.js +0 -288
  140. package/dist/timeline/TimeLine.js.map +0 -1
  141. package/dist/timeline/public.d.ts +0 -135
  142. package/dist/timeline/public.d.ts.map +0 -1
  143. package/dist/timeline/public.js +0 -316
  144. package/dist/timeline/public.js.map +0 -1
  145. package/dist/timing/AnimationFrameTicker.d.ts +0 -32
  146. package/dist/timing/AnimationFrameTicker.d.ts.map +0 -1
  147. package/dist/timing/AnimationFrameTicker.js +0 -134
  148. package/dist/timing/AnimationFrameTicker.js.map +0 -1
  149. package/dist/timing/Ease.d.ts +0 -6
  150. package/dist/timing/Ease.d.ts.map +0 -1
  151. package/dist/timing/Ease.js +0 -15
  152. package/dist/timing/Ease.js.map +0 -1
  153. package/dist/timing/TestTicker.d.ts +0 -19
  154. package/dist/timing/TestTicker.d.ts.map +0 -1
  155. package/dist/timing/TestTicker.js +0 -81
  156. package/dist/timing/TestTicker.js.map +0 -1
  157. package/dist/timing/Ticker.d.ts +0 -14
  158. package/dist/timing/Ticker.d.ts.map +0 -1
  159. package/dist/timing/Ticker.js +0 -2
  160. package/dist/timing/Ticker.js.map +0 -1
  161. package/dist/timing/Timing.d.ts +0 -62
  162. package/dist/timing/Timing.d.ts.map +0 -1
  163. package/dist/timing/Timing.js +0 -257
  164. package/dist/timing/Timing.js.map +0 -1
  165. package/dist/timing/easing.d.ts +0 -158
  166. package/dist/timing/easing.d.ts.map +0 -1
  167. package/dist/timing/easing.js +0 -273
  168. package/dist/timing/easing.js.map +0 -1
  169. package/dist/utils/Events.d.ts +0 -9
  170. package/dist/utils/Events.d.ts.map +0 -1
  171. package/dist/utils/Events.js +0 -35
  172. package/dist/utils/Events.js.map +0 -1
  173. package/dist/utils/internal.d.ts +0 -2
  174. package/dist/utils/internal.d.ts.map +0 -1
  175. package/dist/utils/internal.js +0 -6
  176. package/dist/utils/internal.js.map +0 -1
  177. package/dist/utils/motionpath.d.ts +0 -16
  178. package/dist/utils/motionpath.d.ts.map +0 -1
  179. package/dist/utils/motionpath.js +0 -41
  180. package/dist/utils/motionpath.js.map +0 -1
  181. package/dist/utils/utils.d.ts +0 -43
  182. package/dist/utils/utils.d.ts.map +0 -1
  183. package/dist/utils/utils.js +0 -93
  184. package/dist/utils/utils.js.map +0 -1
  185. package/dist/values/ColorValue.d.ts +0 -12
  186. package/dist/values/ColorValue.d.ts.map +0 -1
  187. package/dist/values/ColorValue.js +0 -133
  188. package/dist/values/ColorValue.js.map +0 -1
  189. package/dist/values/StyleValue.d.ts +0 -40
  190. package/dist/values/StyleValue.d.ts.map +0 -1
  191. package/dist/values/StyleValue.js +0 -149
  192. package/dist/values/StyleValue.js.map +0 -1
  193. package/dist/values/Value.d.ts +0 -13
  194. package/dist/values/Value.d.ts.map +0 -1
  195. package/dist/values/Value.js +0 -38
  196. package/dist/values/Value.js.map +0 -1
  197. package/dist/values/defaultcolors.d.ts +0 -2
  198. package/dist/values/defaultcolors.d.ts.map +0 -1
  199. package/dist/values/defaultcolors.js +0 -149
  200. package/dist/values/defaultcolors.js.map +0 -1
  201. package/docs/INDEX.md +0 -15
  202. package/jsr.json +0 -9
@@ -0,0 +1,124 @@
1
+ import StyleValue from '../values/StyleValue.js';
2
+ import Value from '../values/Value.js';
3
+ import { Target } from './Target.js';
4
+
5
+ export default class ObjectTarget implements Target {
6
+ private target: Record<string, any>;
7
+
8
+ // private _extFns: ExternalFunctions;
9
+
10
+ // private transformValues: Map<string, (Value | null)[]>;
11
+ private styleValues: Map<string, StyleValue | undefined>;
12
+ // private values: Map<string, Value>;
13
+
14
+ /**
15
+ * target a dom node or svg
16
+ *
17
+ * extFns: {getComputedStyle, getRootElement}
18
+ */
19
+ constructor(target: Record<string, any>) {
20
+ this.target = target;
21
+
22
+ this.styleValues = new Map();
23
+ // this.values = new Map();
24
+ }
25
+
26
+ inner() {
27
+ return this.target;
28
+ }
29
+
30
+ type() {
31
+ return 'object';
32
+ }
33
+
34
+ getValue(name: string): Value | null {
35
+ // return this.values.get(name) ?? null;
36
+ throw new Error('not supported');
37
+ }
38
+
39
+ setValue(name: string, value: Value) {
40
+ // this.values.set(name, value);
41
+ throw new Error('not supported');
42
+ }
43
+
44
+ removeValue(name: string) {
45
+ // this.values.delete(name);
46
+ throw new Error('not supported');
47
+ }
48
+
49
+ getTransformValue(name: string): Value | null {
50
+ throw new Error('not supported');
51
+ }
52
+
53
+ setTransformValue(name: string, value: Value) {
54
+ throw new Error('not supported');
55
+ }
56
+
57
+ removeTransformValue(name: string) {
58
+ throw new Error('not supported');
59
+ }
60
+
61
+ getStyleValue(name: string): StyleValue | null {
62
+ const v = this.styleValues.get(name) ?? null;
63
+
64
+ if (v !== null) return v;
65
+
66
+ const styleV = this.target[name];
67
+ if (typeof styleV === 'undefined' || styleV === null) {
68
+ return styleV;
69
+ }
70
+
71
+ try {
72
+ const value = StyleValue.parse(styleV);
73
+ this.styleValues.set(name, value);
74
+
75
+ return value;
76
+ } catch (e) {
77
+ console.log('could not parse value', styleV);
78
+ return v;
79
+ }
80
+ }
81
+
82
+ setStyleValue(name: string, value: StyleValue): void {
83
+ this.styleValues.set(name, value);
84
+ }
85
+
86
+ removeStyleValue(name: string): void {
87
+ this.styleValues.set(name, undefined);
88
+ }
89
+
90
+ hasClass(name: string): boolean {
91
+ throw new Error('not supported');
92
+ }
93
+
94
+ addClass(name: string): void {
95
+ throw new Error('not supported');
96
+ }
97
+
98
+ removeClass(name: string): void {
99
+ throw new Error('not supported');
100
+ }
101
+
102
+ unifyValues(name: string, a: Value, b: Value): [Value, Value] {
103
+ if (a.unit === b.unit) return [a, b];
104
+
105
+ if (!a.unit && b.unit) a.unit = b.unit;
106
+
107
+ if (!b.unit && a.unit) b.unit = a.unit;
108
+
109
+ return [a, b];
110
+ }
111
+
112
+ apply() {
113
+ // style
114
+ for (const [k, v] of this.styleValues.entries()) {
115
+ if (v === undefined) {
116
+ console.log('delete', k);
117
+ delete this.target[k];
118
+ this.styleValues.delete(k);
119
+ } else {
120
+ this.target[k] = v.toMixed();
121
+ }
122
+ }
123
+ }
124
+ }
@@ -1,6 +1,15 @@
1
1
  import Value from '../values/Value.js';
2
2
  import DomTarget from './DomTarget.js';
3
3
  import StyleValue from '../values/StyleValue.js';
4
+ import type TestDomNode from './TestDomNode.js';
5
+ import ObjectTarget from './ObjectTarget.js';
6
+
7
+ export type Targets = AnimationTarget | AnimationTarget[];
8
+ export type AnimationTarget =
9
+ | HTMLElement
10
+ | SVGElement
11
+ | TestDomNode
12
+ | Record<string, any>;
4
13
 
5
14
  export function newTarget(target: any): Target {
6
15
  if (typeof window === 'undefined') {
@@ -19,6 +28,10 @@ export function newTarget(target: any): Target {
19
28
  }
20
29
  }
21
30
 
31
+ if (typeof target === 'object' && target !== null) {
32
+ return new ObjectTarget(target);
33
+ }
34
+
22
35
  throw new Error('unknown target');
23
36
  }
24
37
 
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import TestTicker from '../timing/TestTicker.js';
3
3
  import TestResponsiveEvent from '../responsive/TestEvent.js';
4
4
  import { animate, timeline } from '../chnobli.js';
5
- import { responsive, curve } from '../utils/utils.js';
5
+ import { responsive, curve, fromTo } from '../utils/utils.js';
6
6
  import { el } from '../target/TestDomNode.js';
7
7
  import { timeout } from 'fire/utils';
8
8
 
@@ -180,4 +180,54 @@ describe('properties', () => {
180
180
  'translate3d(50.000px,20.000px,0.000px)',
181
181
  );
182
182
  });
183
+
184
+ it('object-target', () => {
185
+ const ticker = new TestTicker();
186
+
187
+ const obj: { x: number; width?: number } = {
188
+ x: 0,
189
+ };
190
+ const tl = timeline()
191
+ .add(obj, {
192
+ x: 100,
193
+ width: fromTo(0, 100),
194
+ duration: 10,
195
+ })
196
+ .play();
197
+
198
+ ticker.run(5);
199
+ expect(obj.x).toBe(50);
200
+ expect(obj.width).toBe(50);
201
+
202
+ ticker.run();
203
+ expect(obj.x).toBe(100);
204
+ expect(obj.width).toBe(100);
205
+
206
+ tl.reverse();
207
+ tl.play();
208
+ ticker.run();
209
+ expect(obj.x).toBe(undefined);
210
+ expect(obj.width).toBe(undefined);
211
+ });
212
+
213
+ it('reset values', () => {
214
+ const ticker = new TestTicker();
215
+
216
+ const div = el();
217
+ div.computedStyle.width = '0px';
218
+ const tl = timeline()
219
+ .add(div, {
220
+ width: 100,
221
+ duration: 10,
222
+ })
223
+ .play();
224
+
225
+ ticker.run();
226
+ expect(div.style.width).toBe('100.000px');
227
+
228
+ tl.reverse();
229
+ tl.play();
230
+ ticker.run();
231
+ expect(div.style.width).toBe(undefined);
232
+ });
183
233
  });
@@ -1,5 +1,5 @@
1
1
  import { takeProp } from '../utils/internal.js';
2
- import Timeline from './TimeLine.js';
2
+ import Timeline from './Timeline.js';
3
3
  import { timelineAdd } from './public.js';
4
4
 
5
5
  export default class NestedTimeline {
@@ -24,7 +24,7 @@ type Entry = {
24
24
  * Timeline is the most versatile class
25
25
  *
26
26
  * A timeline can contain multiple animation, ordered one after the other or
27
- * offset.
27
+ * by an offset.
28
28
  */
29
29
  export default class Timeline {
30
30
  timing: Timing;
@@ -49,6 +49,8 @@ export default class Timeline {
49
49
  */
50
50
  constructor(props: Record<string, any> = {}) {
51
51
  const nProps = {
52
+ // duration here is not important since it will be calculated
53
+ // based on the animations that get added
52
54
  duration: 1000,
53
55
  ease: parseEase(takeProp(props, 'ease', null)),
54
56
  repeat: parseRepeat(takeProp(props, 'repeat', false)),
@@ -145,6 +147,21 @@ export default class Timeline {
145
147
  this._updateTimings();
146
148
  }
147
149
 
150
+ /**
151
+ * Returns the position of a label
152
+ *
153
+ * needs to be initialized
154
+ */
155
+ labelPosition(label: string): number {
156
+ const entry = this.entries.find(e => {
157
+ return e.type === 'label' && e.value === label;
158
+ });
159
+
160
+ if (!entry) throw new Error('could not find label ' + label);
161
+
162
+ return entry.start! / this.timing.duration;
163
+ }
164
+
148
165
  init() {
149
166
  if (this._initialized) return;
150
167
  this._initialized = true;
@@ -254,6 +271,9 @@ export default class Timeline {
254
271
  }
255
272
  }
256
273
 
274
+ /**
275
+ * Updates the timings of all animations
276
+ */
257
277
  _updateTimings() {
258
278
  this._renderQueue = {
259
279
  upcoming: [],
@@ -1,19 +1,49 @@
1
1
  import { takeProp } from '../utils/internal.js';
2
- import Timeline from './TimeLine.js';
3
- import { STATE_ENDED } from '../timing/Timing.js';
4
- import { callStagger } from '../stagger/stagger.js';
2
+ import Timeline from './Timeline.js';
3
+ import Timing, {
4
+ STATE_BEFORE,
5
+ STATE_ENDED,
6
+ TimingProps,
7
+ } from '../timing/Timing.js';
8
+ import Stagger, { callStagger } from '../stagger/stagger.js';
5
9
  import Events from '../utils/Events.js';
6
10
  import ResponsiveEvent from '../responsive/ResponsiveEvent.js';
7
11
  import NestedTimeline from './Nested.js';
12
+ import { Targets } from '../chnobli.js';
8
13
 
9
14
  const STATE_PAUSED = 0;
10
15
  const STATE_RENDER_ONCE = 1;
11
- const STATE_PLAYING = 2;
16
+ const STATE_SEEK = 2;
17
+ const STATE_PLAYING = 3;
12
18
 
13
19
  export type Responsive = {
14
20
  responsive: () => void;
15
21
  };
16
22
 
23
+ export type TimelineProps = {
24
+ // default properties for all animations
25
+ defaults?: Record<string, any>;
26
+
27
+ // make the timeline responsive default is true
28
+ // this means the timeline will update on resize
29
+ responsive?: boolean;
30
+
31
+ // ease
32
+ ease?: (t: number) => number;
33
+
34
+ // smoothSeek
35
+ smoothSeek?: SmoothSeekValue;
36
+
37
+ // repeat
38
+ // alternate
39
+ // reversed
40
+
41
+ // other properties
42
+ [key: string]: any;
43
+ };
44
+
45
+ export type Offset = number | string | Stagger<number | string>;
46
+
17
47
  export default class PublicTimeline {
18
48
  /**
19
49
  * @ignore
@@ -27,14 +57,17 @@ export default class PublicTimeline {
27
57
  _inner: Timeline;
28
58
 
29
59
  private _events: Events;
60
+ private _triggeredStart: boolean;
30
61
 
31
62
  private _state: number;
32
63
  private _renderedOnce: boolean;
33
64
  private _runningTicker: any;
34
65
  private _responsiveBlocks: { responsive: () => void }[];
35
66
  private _responsiveEvent: any;
67
+ private _smoothSeek: SmoothSeek | null;
68
+ private _seekTo: number;
36
69
 
37
- constructor(props: Record<string, any> = {}) {
70
+ constructor(props: TimelineProps = {}) {
38
71
  this._defaults = takeProp(props, 'defaults', {});
39
72
  this._responsive = takeProp(props, 'responsive', true);
40
73
 
@@ -43,10 +76,16 @@ export default class PublicTimeline {
43
76
  if ('duration' in props)
44
77
  throw new Error('a timeline does not accept a duration');
45
78
 
79
+ this._smoothSeek = parseSmoothSeek(
80
+ takeProp<SmoothSeekValue>(props, 'smoothSeek', undefined),
81
+ );
82
+ this._seekTo = 0;
83
+
46
84
  this._inner = new Timeline(props);
47
85
 
48
86
  // 'start', 'end'
49
87
  this._events = new Events();
88
+ this._triggeredStart = false;
50
89
 
51
90
  this._state = STATE_PAUSED;
52
91
  this._renderedOnce = false;
@@ -70,9 +109,10 @@ export default class PublicTimeline {
70
109
 
71
110
  /**
72
111
  * Set's properties
112
+ *
73
113
  * offset can be staggered, a number, a label or a string `+=10`
74
114
  */
75
- set(targets: any, props: Record<string, any>, offset = null): this {
115
+ set(targets: Targets, props: Record<string, any>, offset?: Offset): this {
76
116
  // for the moment let's just add as usual but set the duration to 0
77
117
  return this.add(targets, { ...props, duration: 0 }, offset);
78
118
  }
@@ -82,14 +122,14 @@ export default class PublicTimeline {
82
122
  *
83
123
  * offset can be staggered, a number, a label or a string `+=10`
84
124
  */
85
- add(targets: any, props: Record<string, any>, offset = null): this {
125
+ add(targets: Targets, props: Record<string, any>, offset?: Offset): this {
86
126
  timelineAdd(this, targets, props, offset);
87
127
 
88
128
  return this;
89
129
  }
90
130
 
91
131
  /**
92
- * Adds a label at to the current nOffse
132
+ * Adds a label at to the current nOffset
93
133
  *
94
134
  * This label can then be used in offsets
95
135
  */
@@ -170,13 +210,14 @@ export default class PublicTimeline {
170
210
  seekMs(ms: number) {
171
211
  this._inner.init();
172
212
 
173
- this._inner.seekMs(ms);
174
-
175
- if (this._state !== STATE_PLAYING) this._state = STATE_RENDER_ONCE;
176
-
177
- this._startTicker();
213
+ this.seek(this._inner.timing.normalizeMs(ms));
178
214
  }
179
215
 
216
+ // todo maybe we should have two functions
217
+ // seek
218
+ // and setPosition
219
+ // seek would include reverse but setPosition would not
220
+
180
221
  /**
181
222
  * Seeks to a normalized position in the timeline
182
223
  *
@@ -185,19 +226,37 @@ export default class PublicTimeline {
185
226
  seek(pos: number) {
186
227
  this._inner.init();
187
228
 
188
- this._inner.seek(pos);
229
+ if (this._smoothSeek) {
230
+ this._smoothSeek.update(this._inner.timing.positionLinear(), pos);
231
+
232
+ if (this._state !== STATE_PLAYING) this._state = STATE_SEEK;
233
+ } else {
234
+ // this._inner.seek(pos);
235
+ this._seekTo = pos;
189
236
 
190
- if (this._state !== STATE_PLAYING) this._state = STATE_RENDER_ONCE;
237
+ if (this._state !== STATE_PLAYING) this._state = STATE_RENDER_ONCE;
238
+ }
191
239
 
192
240
  this._startTicker();
193
241
  }
194
242
 
243
+ /**
244
+ * Returns the position of a label
245
+ */
246
+ labelPosition(label: string): number {
247
+ this._inner.init();
248
+
249
+ return this._inner.labelPosition(label);
250
+ }
251
+
195
252
  // seek in Range (useful for scroll timelines)
196
253
  // seek(startOffset, endOffset, pos)
197
254
 
198
255
  /**
199
256
  * Resets the current timeline to the start
200
257
  * without changing anything
258
+ *
259
+ * this operation is not smooth use seek 0 if you wan't that
201
260
  */
202
261
  reset() {
203
262
  this._inner.seek(0);
@@ -244,6 +303,8 @@ export default class PublicTimeline {
244
303
  */
245
304
  reverse() {
246
305
  this._inner.timing.reverse();
306
+
307
+ // todo does this work with smooth seek?
247
308
  }
248
309
 
249
310
  /**
@@ -280,6 +341,13 @@ export default class PublicTimeline {
280
341
  this._inner = null;
281
342
  }
282
343
 
344
+ _maybeTriggerEndEvent() {
345
+ if (this._state !== STATE_PLAYING) return;
346
+
347
+ this._events.trigger('end');
348
+ this._triggeredStart = false;
349
+ }
350
+
283
351
  /**
284
352
  * @ignore
285
353
  */
@@ -288,48 +356,45 @@ export default class PublicTimeline {
288
356
 
289
357
  this._inner.init();
290
358
 
359
+ const timingEnded = () => this._inner.timing.state >= STATE_ENDED;
360
+ const smoothSeekEnded = () =>
361
+ !this._smoothSeek || this._smoothSeek.state >= STATE_ENDED;
362
+
291
363
  this._runningTicker = this._inner.ticker.add(change => {
292
- if (this._inner.timing.state >= STATE_ENDED) {
293
- if (!this._renderedOnce) {
294
- this._renderedOnce = true;
295
- this._inner.render();
364
+ if (!this._triggeredStart) {
365
+ // todo check that we are at the start
366
+ if (this._inner.timing.state <= STATE_BEFORE) {
367
+ this._events.trigger('start');
368
+ this._triggeredStart = true;
296
369
  }
297
-
298
- if (this._state === STATE_PLAYING) this._events.trigger('end');
299
-
300
- this._state = STATE_PAUSED;
301
- this._stopTicker();
302
- return;
303
370
  }
304
371
 
305
- // if (this._inner.timing.state <= STATE_START) {
306
- // this._events.trigger('start');
307
- // }
308
-
309
- // if (this._inner.timing.state >= STATE_ENDED) {
310
- // this._stopTicker();
311
- // this._events.trigger('end');
312
- // return;
313
- // }
314
-
315
- if (this._state === STATE_PLAYING) this._inner.advance(change);
316
-
317
- // todo when we add smooth seeks we need to update this
372
+ const allowSmoothSeek =
373
+ this._state === STATE_PLAYING || this._state === STATE_SEEK;
374
+ if (
375
+ allowSmoothSeek &&
376
+ this._smoothSeek &&
377
+ this._smoothSeek.state < STATE_ENDED
378
+ ) {
379
+ this._smoothSeek.advance(change);
380
+ this._inner.seek(this._smoothSeek.value);
381
+ } else if (this._state === STATE_RENDER_ONCE) {
382
+ this._inner.seek(this._seekTo);
383
+ } else if (this._state === STATE_PLAYING) {
384
+ this._inner.advance(change);
385
+ }
318
386
 
319
387
  this._inner.render();
320
388
 
321
- if (this._inner.timing.state >= STATE_ENDED) {
322
- if (this._state === STATE_PLAYING) this._events.trigger('end');
389
+ // we rendered once let's stop
390
+ const renderOnce = this._state === STATE_RENDER_ONCE;
323
391
 
324
- this._state = STATE_PAUSED;
325
- this._stopTicker();
326
- return;
327
- }
392
+ if (renderOnce || (timingEnded() && smoothSeekEnded())) {
393
+ this._maybeTriggerEndEvent();
328
394
 
329
- // we rendered once let's stop
330
- if (this._state === STATE_RENDER_ONCE) {
331
395
  this._state = STATE_PAUSED;
332
396
  this._stopTicker();
397
+ return;
333
398
  }
334
399
  });
335
400
  }
@@ -384,8 +449,64 @@ export function timelineAdd(
384
449
  nProps[prop] = callStagger(nProps[prop], i, targets.length);
385
450
  }
386
451
 
387
- const nOffset = callStagger(offset, i);
452
+ const nOffset = callStagger(offset, i, targets.length);
388
453
 
389
454
  tl._inner.add(target, nProps, nOffset, i);
390
455
  }
391
456
  }
457
+
458
+ export type SmoothSeekValue =
459
+ | number
460
+ | undefined
461
+ | { duration: number; ease?: (t: number) => number };
462
+
463
+ /**
464
+ * Smooth seek
465
+ *
466
+ * can either just be a number (duration) or an object { duration, ease }
467
+ */
468
+ function parseSmoothSeek(smoothSeek: SmoothSeekValue): SmoothSeek | null {
469
+ if (!smoothSeek) return null;
470
+
471
+ if (typeof smoothSeek === 'number') {
472
+ return new SmoothSeek({
473
+ duration: smoothSeek,
474
+ });
475
+ }
476
+
477
+ return new SmoothSeek({
478
+ duration: smoothSeek.duration,
479
+ ease: smoothSeek?.ease,
480
+ });
481
+ }
482
+
483
+ class SmoothSeek {
484
+ timing: Timing;
485
+ from: number;
486
+ to: number;
487
+
488
+ constructor(timing: TimingProps) {
489
+ this.timing = new Timing(timing);
490
+ this.timing.seek(1);
491
+ this.from = 0;
492
+ this.to = 0;
493
+ }
494
+
495
+ update(from: number, to: number) {
496
+ this.from = from;
497
+ this.to = to;
498
+ this.timing.seek(0);
499
+ }
500
+
501
+ advance(change: number) {
502
+ this.timing.advance(change);
503
+ }
504
+
505
+ get value(): number {
506
+ return this.from + (this.to - this.from) * this.timing.position;
507
+ }
508
+
509
+ get state(): number {
510
+ return this.timing.state;
511
+ }
512
+ }
@@ -12,6 +12,14 @@ export type Position = {
12
12
  progress: number;
13
13
  };
14
14
 
15
+ export type TimingProps = {
16
+ duration: number;
17
+ ease?: (t: number) => number;
18
+ repeat?: number;
19
+ alternate?: boolean;
20
+ reversed?: boolean;
21
+ };
22
+
15
23
  /**
16
24
  * This class handles everything timing related
17
25
  *
@@ -58,7 +66,7 @@ export default class Timing {
58
66
  alternate
59
67
  }
60
68
  */
61
- constructor(props: Record<string, any>) {
69
+ constructor(props: TimingProps) {
62
70
  // these vars are only readonly
63
71
  // use functions to update them
64
72
 
@@ -90,6 +98,7 @@ export default class Timing {
90
98
  }
91
99
 
92
100
  positionMs() {
101
+ // todo should this not return pos * duration?
93
102
  return this.position * this.iterDuration;
94
103
  }
95
104
 
@@ -132,6 +141,31 @@ export default class Timing {
132
141
  }
133
142
 
134
143
  this._updateProgress(ms / this.iterDuration);
144
+
145
+ // return this.seek(this.normalizeMs(ms));
146
+ }
147
+
148
+ normalizeMs(ms: number) {
149
+ if (this.iterDuration <= 0) {
150
+ return Math.sign(ms);
151
+ }
152
+
153
+ const pos = ms / this.iterDuration;
154
+
155
+ if (this.isFinite()) {
156
+ return pos / (this.repeat + 1);
157
+ }
158
+
159
+ return pos;
160
+ }
161
+
162
+ // todo how to call this
163
+ positionLinear(): number {
164
+ if (this.isFinite()) {
165
+ return this._progress / (this.repeat + 1);
166
+ }
167
+
168
+ return this._progress;
135
169
  }
136
170
 
137
171
  // should be between 0-1 if outside might change the state
@@ -241,7 +275,7 @@ export default class Timing {
241
275
  }
242
276
  }
243
277
 
244
- export function newTiming(props: Record<string, any>) {
278
+ export function newTiming(props: Record<string, any>): Timing {
245
279
  const nProps = {
246
280
  duration: parseDuration(takeProp(props, 'duration', 1000)),
247
281
  ease: parseEase(takeProp(props, 'ease', null)),
@@ -253,7 +287,7 @@ export function newTiming(props: Record<string, any>) {
253
287
  return new Timing(nProps);
254
288
  }
255
289
 
256
- export function parseDuration(dur: any) {
290
+ export function parseDuration(dur: any): number {
257
291
  if (typeof dur !== 'number') throw new Error('duration is not a number');
258
292
 
259
293
  dur = Math.round(dur);
@@ -1,8 +1,11 @@
1
- export function takeProp(
1
+ /**
2
+ * Take a property from an object and delete it from the object.
3
+ */
4
+ export function takeProp<T>(
2
5
  props: Record<string, any>,
3
6
  name: string,
4
- def: any = null,
5
- ): any {
7
+ def: T,
8
+ ): T {
6
9
  const v = props[name] ?? def;
7
10
  delete props[name];
8
11