chnobli 0.1.10 → 0.1.11
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/dist/animation/Animation.d.ts +2 -1
- package/dist/animation/Animation.d.ts.map +1 -1
- package/dist/animation/Animation.js +5 -3
- package/dist/animation/Animation.js.map +1 -1
- package/dist/animator/Animator.d.ts +30 -0
- package/dist/animator/Animator.d.ts.map +1 -0
- package/dist/animator/Animator.js +97 -0
- package/dist/animator/Animator.js.map +1 -0
- package/dist/animator/index.d.ts +5 -0
- package/dist/animator/index.d.ts.map +1 -0
- package/dist/animator/index.js +53 -0
- package/dist/animator/index.js.map +1 -0
- package/dist/animator/public.d.ts +16 -0
- package/dist/animator/public.d.ts.map +1 -0
- package/dist/animator/public.js +50 -0
- package/dist/animator/public.js.map +1 -0
- package/dist/chnobli.d.ts +3 -3
- package/dist/chnobli.d.ts.map +1 -1
- package/dist/chnobli.js.map +1 -1
- package/dist/property/ClassProperty.js +1 -1
- package/dist/property/ClassProperty.js.map +1 -1
- package/dist/property/TransformXYProperty.d.ts.map +1 -1
- package/dist/scroll/Scroll.d.ts +24 -3
- package/dist/scroll/Scroll.d.ts.map +1 -1
- package/dist/scroll/Scroll.js +155 -43
- package/dist/scroll/Scroll.js.map +1 -1
- package/dist/scroll/ScrollEvent.d.ts.map +1 -1
- package/dist/scroll/ScrollEvent.js +1 -0
- package/dist/scroll/ScrollEvent.js.map +1 -1
- package/dist/scroll/index.d.ts +2 -0
- package/dist/scroll/index.d.ts.map +1 -1
- package/dist/scroll/index.js +1 -0
- package/dist/scroll/index.js.map +1 -1
- package/dist/scroll/public.d.ts +19 -1
- package/dist/scroll/public.d.ts.map +1 -1
- package/dist/scroll/public.js +26 -1
- package/dist/scroll/public.js.map +1 -1
- package/dist/target/DomTarget.js +1 -1
- package/dist/target/DomTarget.js.map +1 -1
- package/dist/target/ObjectTarget.d.ts +11 -10
- package/dist/target/ObjectTarget.d.ts.map +1 -1
- package/dist/target/ObjectTarget.js +20 -13
- package/dist/target/ObjectTarget.js.map +1 -1
- package/dist/timeline/Timeline.d.ts +2 -1
- package/dist/timeline/Timeline.d.ts.map +1 -1
- package/dist/timeline/Timeline.js +4 -4
- package/dist/timeline/Timeline.js.map +1 -1
- package/dist/timeline/public.d.ts +11 -4
- package/dist/timeline/public.d.ts.map +1 -1
- package/dist/timeline/public.js +19 -11
- package/dist/timeline/public.js.map +1 -1
- package/dist/timing/AnimationFrameTicker.d.ts.map +1 -1
- package/dist/timing/AnimationFrameTicker.js +5 -1
- package/dist/timing/AnimationFrameTicker.js.map +1 -1
- package/dist/timing/TestTicker.d.ts.map +1 -1
- package/dist/timing/TestTicker.js +5 -1
- package/dist/timing/TestTicker.js.map +1 -1
- package/dist/timing/Ticker.d.ts +4 -2
- package/dist/timing/Ticker.d.ts.map +1 -1
- package/dist/timing/Timing.d.ts +47 -2
- package/dist/timing/Timing.d.ts.map +1 -1
- package/dist/timing/Timing.js +118 -84
- package/dist/timing/Timing.js.map +1 -1
- package/dist/utils/Events.js +1 -1
- package/dist/utils/Events.js.map +1 -1
- package/dist/utils/motionpath.js +5 -5
- package/dist/utils/motionpath.js.map +1 -1
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +0 -8
- package/dist/utils/utils.js.map +1 -1
- package/dist/values/ColorValue.js +1 -0
- package/dist/values/ColorValue.js.map +1 -1
- package/dist/values/Value.js +1 -1
- package/dist/values/Value.js.map +1 -1
- package/eslint.config.js +23 -0
- package/package.json +10 -3
- package/src/animation/Animation.ts +6 -3
- package/src/animator/Animator.ts +130 -0
- package/src/animator/index.ts +56 -0
- package/src/animator/public.ts +66 -0
- package/src/chnobli.ts +3 -2
- package/src/property/ClassProperty.ts +1 -1
- package/src/property/TransformXYProperty.ts +1 -1
- package/src/scroll/Scroll.ts +202 -51
- package/src/scroll/ScrollEvent.ts +1 -0
- package/src/scroll/index.ts +3 -0
- package/src/scroll/public.ts +35 -2
- package/src/target/DomTarget.ts +1 -1
- package/src/target/ObjectTarget.ts +19 -13
- package/src/tests/properties.test.ts +44 -3
- package/src/tests/timeline.test.ts +1 -1
- package/src/tests/timing.test.ts +32 -0
- package/src/tests/usecases.test.ts +40 -2
- package/src/tests/values.test.ts +9 -9
- package/src/timeline/Timeline.ts +5 -4
- package/src/timeline/public.ts +34 -12
- package/src/timing/AnimationFrameTicker.ts +6 -1
- package/src/timing/TestTicker.ts +7 -1
- package/src/timing/Ticker.ts +5 -1
- package/src/timing/Timing.ts +127 -91
- package/src/utils/Events.ts +1 -1
- package/src/utils/motionpath.ts +5 -5
- package/src/utils/utils.ts +0 -10
- package/src/values/ColorValue.ts +1 -0
- package/src/values/Value.ts +1 -1
- package/.eslintrc.cjs +0 -39
- package/src/animator.js +0 -187
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import Property, { newProperty } from '../property/Property.js';
|
|
2
|
+
import { Target } from '../target/Target.js';
|
|
3
|
+
import Ticker from '../timing/Ticker.js';
|
|
4
|
+
import Timing, { STATE_AFTER } from '../timing/Timing.js';
|
|
5
|
+
|
|
6
|
+
export type Settings = {
|
|
7
|
+
duration: number;
|
|
8
|
+
ease?: (t: number) => number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
|
|
13
|
+
const opacity = animator(obj, {
|
|
14
|
+
duration: 200,
|
|
15
|
+
ease: sinInOut
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
opacity.set({ opacity: 0 });
|
|
19
|
+
|
|
20
|
+
opacity.to({ opacity: 1 });
|
|
21
|
+
opacity.to({ opacity: 2 }, 1000);
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export default class Animator {
|
|
25
|
+
setts: Settings;
|
|
26
|
+
target: Target;
|
|
27
|
+
private props: Map<string, PropertyAnimator<any>>;
|
|
28
|
+
|
|
29
|
+
private ticker: Ticker;
|
|
30
|
+
|
|
31
|
+
constructor(target: any, ticker: Ticker, setts: Settings) {
|
|
32
|
+
this.setts = setts;
|
|
33
|
+
this.target = ticker.registerTarget(target);
|
|
34
|
+
|
|
35
|
+
this.ticker = ticker;
|
|
36
|
+
|
|
37
|
+
this.props = new Map();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private getProp(name: string): PropertyAnimator<any> {
|
|
41
|
+
const prop = this.props.get(name);
|
|
42
|
+
if (prop) return prop;
|
|
43
|
+
|
|
44
|
+
const newProp = new PropertyAnimator(name, this.setts, this.target);
|
|
45
|
+
this.props.set(name, newProp);
|
|
46
|
+
return newProp;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set(props: Record<string, any>) {
|
|
50
|
+
for (const prop in props) {
|
|
51
|
+
const propAni = this.getProp(prop);
|
|
52
|
+
console.log('got prop ani', propAni);
|
|
53
|
+
propAni.set(this.target, props[prop]);
|
|
54
|
+
console.log('got prop ani', propAni);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
to(props: Record<string, any>, duration?: number) {
|
|
59
|
+
for (const prop in props) {
|
|
60
|
+
const propAni = this.getProp(prop);
|
|
61
|
+
propAni.to(this.target, props[prop], duration);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// returns if the ticker should keep running
|
|
66
|
+
_tick(change: number): boolean {
|
|
67
|
+
let shouldStop = true;
|
|
68
|
+
|
|
69
|
+
for (const prop of this.props.values()) {
|
|
70
|
+
prop.advance(change);
|
|
71
|
+
prop.render(this.target);
|
|
72
|
+
|
|
73
|
+
if (prop.timing.state < STATE_AFTER) {
|
|
74
|
+
shouldStop = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return !shouldStop;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class PropertyAnimator<V> {
|
|
83
|
+
prop: Property<V>;
|
|
84
|
+
timing: Timing;
|
|
85
|
+
|
|
86
|
+
private initialized: boolean;
|
|
87
|
+
|
|
88
|
+
constructor(prop: string, setts: Settings, target: Target) {
|
|
89
|
+
this.prop = newProperty(prop, target.type());
|
|
90
|
+
|
|
91
|
+
this.timing = new Timing(setts);
|
|
92
|
+
this.initialized = false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
set(target: Target, value: any) {
|
|
96
|
+
const val = this.prop.parseValue(value);
|
|
97
|
+
|
|
98
|
+
this.prop.init(target, val, val);
|
|
99
|
+
|
|
100
|
+
this.timing.seek(2);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
to(target: Target, value: any, duration?: number) {
|
|
104
|
+
const val = this.prop.parseValue(value);
|
|
105
|
+
|
|
106
|
+
let current = null;
|
|
107
|
+
if (this.initialized) {
|
|
108
|
+
current = this.prop.interpolate(this.timing.position);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.prop.init(target, current, val);
|
|
112
|
+
this.initialized = true;
|
|
113
|
+
|
|
114
|
+
this.timing.seek(0);
|
|
115
|
+
if (typeof duration === 'number') {
|
|
116
|
+
this.timing.setDuration(duration);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
advance(change: number) {
|
|
121
|
+
this.timing.advance(change);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// call advance first
|
|
125
|
+
render(target: Target) {
|
|
126
|
+
const pos = this.timing.position;
|
|
127
|
+
|
|
128
|
+
this.prop.setValue(target, this.prop.interpolate(pos));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
const obj = { opacity: 1 };
|
|
4
|
+
// maybe we could even have stagger?
|
|
5
|
+
const opacity = animator(obj, {
|
|
6
|
+
// initial value
|
|
7
|
+
// might also be able to be set with stagger?
|
|
8
|
+
// for example
|
|
9
|
+
opacity: 1,
|
|
10
|
+
|
|
11
|
+
duration: 200,
|
|
12
|
+
ease: sineInOut
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// or maybe instead
|
|
16
|
+
|
|
17
|
+
const animator = animator();
|
|
18
|
+
animator.add(obj, {
|
|
19
|
+
opacity: 1,
|
|
20
|
+
duration: 200,
|
|
21
|
+
ease: sineInOut
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// what is the reason why we would wan't one animator with
|
|
25
|
+
// multiple objects with different timings
|
|
26
|
+
// could we not just use multiple animators
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
opacity.set(obj, { opacity: 0 });
|
|
30
|
+
|
|
31
|
+
let's make the most basic animator first
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const opacity = animator(obj, {
|
|
35
|
+
duration: 200,
|
|
36
|
+
ease: sinInOut
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
opacity.set({ opacity: 0 });
|
|
40
|
+
|
|
41
|
+
opacity.to({ opacity: 1 });
|
|
42
|
+
opacity.to({ opacity: 2 }, 1000);
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import Animator, { AnimatorProps } from './public.js';
|
|
50
|
+
|
|
51
|
+
export { Animator };
|
|
52
|
+
export type { AnimatorProps };
|
|
53
|
+
|
|
54
|
+
export function animator(target: any, props?: AnimatorProps): Animator {
|
|
55
|
+
return new Animator(target, props);
|
|
56
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
set,
|
|
3
|
+
to,
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import AnimationFrameTicker from '../timing/AnimationFrameTicker.js';
|
|
7
|
+
import Ticker from '../timing/Ticker.js';
|
|
8
|
+
import Events from '../utils/Events.js';
|
|
9
|
+
import Animator from './Animator.js';
|
|
10
|
+
|
|
11
|
+
export type AnimatorProps = {
|
|
12
|
+
duration?: number;
|
|
13
|
+
ease?: (t: number) => number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default class PublicAnimator {
|
|
17
|
+
private ticker: Ticker;
|
|
18
|
+
private inner: Animator;
|
|
19
|
+
private runningTicker: any;
|
|
20
|
+
private events: Events;
|
|
21
|
+
|
|
22
|
+
constructor(target: any, props: AnimatorProps = {}) {
|
|
23
|
+
this.ticker = AnimationFrameTicker.global();
|
|
24
|
+
this.inner = new Animator(target, this.ticker, {
|
|
25
|
+
duration: props.duration ?? 1000,
|
|
26
|
+
ease: props.ease,
|
|
27
|
+
});
|
|
28
|
+
this.events = new Events();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set(props: Record<string, any>): this {
|
|
32
|
+
this.inner.set(props);
|
|
33
|
+
this.startTicker();
|
|
34
|
+
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
to(props: Record<string, any>, duration?: number): this {
|
|
39
|
+
this.inner.to(props, duration);
|
|
40
|
+
this.startTicker();
|
|
41
|
+
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// on: update
|
|
46
|
+
on(ev: string, fn: (...args: any[]) => void): () => void {
|
|
47
|
+
return this.events.add(ev, fn);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private startTicker() {
|
|
51
|
+
if (this.runningTicker) return;
|
|
52
|
+
|
|
53
|
+
this.runningTicker = this.ticker.add((change, opts) => {
|
|
54
|
+
const keepRunning = this.inner._tick(change);
|
|
55
|
+
|
|
56
|
+
opts.onApplied(() => {
|
|
57
|
+
this.events.trigger('update');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!keepRunning) {
|
|
61
|
+
opts.remove();
|
|
62
|
+
this.runningTicker = null;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/chnobli.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import Animation, { AnimationProps } from './animation/public.js';
|
|
2
|
-
import Timeline from './timeline/public.js';
|
|
2
|
+
import Timeline, { TimelineProps } from './timeline/public.js';
|
|
3
3
|
import Stagger, { type StaggerValue } from './stagger/stagger.js';
|
|
4
4
|
import type { Targets, AnimationTarget } from './target/Target.js';
|
|
5
5
|
|
|
6
6
|
export type {
|
|
7
7
|
Animation,
|
|
8
8
|
Timeline,
|
|
9
|
+
TimelineProps,
|
|
9
10
|
Stagger,
|
|
10
11
|
Targets,
|
|
11
12
|
AnimationTarget,
|
|
@@ -28,7 +29,7 @@ export function animate(
|
|
|
28
29
|
return new Animation(targets, props);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export function timeline(props:
|
|
32
|
+
export function timeline(props: TimelineProps = {}): Timeline {
|
|
32
33
|
return new Timeline(props);
|
|
33
34
|
}
|
|
34
35
|
|
package/src/scroll/Scroll.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ResponsiveEvent from '../responsive/ResponsiveEvent.js';
|
|
2
|
+
import PublicTimeline from '../timeline/public.js';
|
|
3
|
+
import Events from '../utils/Events.js';
|
|
2
4
|
import { pageOffset } from '../utils/utils.js';
|
|
3
5
|
import ScrollEvent from './ScrollEvent.js';
|
|
4
6
|
|
|
7
|
+
export type DestroyOptions = {
|
|
8
|
+
/// defaults to true
|
|
9
|
+
timelines?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEF_DESTROY_OPTS: DestroyOptions = {
|
|
13
|
+
timelines: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
5
16
|
/*
|
|
6
17
|
run timelines based on the scroll position
|
|
7
18
|
|
|
@@ -54,11 +65,13 @@ export type ScrollTrigger =
|
|
|
54
65
|
|
|
55
66
|
export type OffsetValue = 'top' | 'center' | 'bottom' | number;
|
|
56
67
|
|
|
68
|
+
export type State = 'before' | 'in' | 'after';
|
|
69
|
+
|
|
57
70
|
export type ParsedScrollTrigger = {
|
|
58
71
|
target: HTMLElement;
|
|
59
72
|
offset: number;
|
|
60
73
|
view: number;
|
|
61
|
-
position: Position;
|
|
74
|
+
position: Position | undefined;
|
|
62
75
|
};
|
|
63
76
|
|
|
64
77
|
export type ScrollOffset = {
|
|
@@ -72,35 +85,45 @@ export type ScrollOffset = {
|
|
|
72
85
|
|
|
73
86
|
export type Position = number | string;
|
|
74
87
|
|
|
88
|
+
type LastSeek = {
|
|
89
|
+
pos: number;
|
|
90
|
+
state: State;
|
|
91
|
+
};
|
|
92
|
+
|
|
75
93
|
export default class ScrollTimeline {
|
|
76
94
|
private _initialized: boolean;
|
|
77
|
-
private _globalEvent: ScrollEvent;
|
|
78
|
-
private
|
|
95
|
+
private _globalEvent: ScrollEvent | null;
|
|
96
|
+
private _scrollEvent: () => void;
|
|
97
|
+
private _responsiveEvent: any;
|
|
79
98
|
|
|
80
99
|
private _triggers: ParsedScrollTrigger[];
|
|
100
|
+
private _events: Events;
|
|
101
|
+
private lastEvPos: number;
|
|
81
102
|
private timelines: PublicTimeline[];
|
|
103
|
+
private state: State;
|
|
104
|
+
private lastSeek: LastSeek | undefined;
|
|
82
105
|
offsets: ScrollOffset[];
|
|
83
106
|
|
|
107
|
+
private debugMarkers: HTMLElement[];
|
|
108
|
+
private debugPrefix?: string;
|
|
109
|
+
|
|
84
110
|
constructor() {
|
|
85
111
|
this._initialized = false;
|
|
86
112
|
this._globalEvent = ScrollEvent.global();
|
|
87
|
-
this.
|
|
113
|
+
this._scrollEvent =
|
|
114
|
+
this._globalEvent?.add(y => this._onScroll(y)).remove ?? (() => {});
|
|
115
|
+
this._responsiveEvent = ResponsiveEvent.global()?.add((a, b) => {
|
|
116
|
+
this._onResponsive(a, b);
|
|
117
|
+
});
|
|
88
118
|
|
|
89
119
|
this._triggers = [];
|
|
120
|
+
this._events = new Events();
|
|
121
|
+
this.lastEvPos = NaN;
|
|
90
122
|
this.timelines = [];
|
|
123
|
+
this.state = 'in';
|
|
91
124
|
this.offsets = [];
|
|
92
125
|
|
|
93
|
-
|
|
94
|
-
// this._triggers = triggers.map(parseTrigger);
|
|
95
|
-
// this.offsets = this._triggers.map(t => {
|
|
96
|
-
// return {
|
|
97
|
-
// y: 0,
|
|
98
|
-
// view: t.view,
|
|
99
|
-
// };
|
|
100
|
-
// });
|
|
101
|
-
|
|
102
|
-
// if (this.offsets.length < 2)
|
|
103
|
-
// throw new Error('scroll timeline needs at least two triggers');
|
|
126
|
+
this.debugMarkers = [];
|
|
104
127
|
}
|
|
105
128
|
|
|
106
129
|
/**
|
|
@@ -120,21 +143,38 @@ export default class ScrollTimeline {
|
|
|
120
143
|
this.timelines.push(timeline);
|
|
121
144
|
}
|
|
122
145
|
|
|
123
|
-
|
|
146
|
+
on(event: string, fn: (...args: any[]) => void): () => void {
|
|
147
|
+
return this._events.add(event, fn);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// Returns true if the init function was executed
|
|
151
|
+
init(): boolean {
|
|
152
|
+
if (this._initialized) return false;
|
|
153
|
+
|
|
154
|
+
this._initialized = true;
|
|
155
|
+
|
|
124
156
|
// todo we need to calculate the position for each timeline
|
|
125
157
|
//
|
|
126
158
|
// calculate the positions and offsets
|
|
127
159
|
// if we are missing one extend them
|
|
128
160
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.offsets = this._triggers.map(t => {
|
|
132
|
-
const offset = pageOffset(t.target);
|
|
161
|
+
if (this._triggers.length < 1)
|
|
162
|
+
throw new Error('scroll timeline needs at least one trigger');
|
|
133
163
|
|
|
134
|
-
|
|
164
|
+
if (this._triggers.length === 1) {
|
|
165
|
+
this._triggers.push({
|
|
166
|
+
target: this._triggers[0].target,
|
|
167
|
+
offset: 1,
|
|
168
|
+
view: 1,
|
|
169
|
+
position: 1,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
135
172
|
|
|
136
|
-
|
|
173
|
+
const positionStep = 1 / (this._triggers.length - 1);
|
|
137
174
|
|
|
175
|
+
// first calculate position and view
|
|
176
|
+
let prevPos = 0;
|
|
177
|
+
this.offsets = this._triggers.map((t, i) => {
|
|
138
178
|
let position;
|
|
139
179
|
if (typeof t.position === 'string') {
|
|
140
180
|
if (this.timelines.length !== 1)
|
|
@@ -143,59 +183,170 @@ export default class ScrollTimeline {
|
|
|
143
183
|
);
|
|
144
184
|
|
|
145
185
|
position = this.timelines[0].labelPosition(t.position);
|
|
146
|
-
} else if (
|
|
186
|
+
} else if (typeof t.position === 'number') {
|
|
147
187
|
position = t.position;
|
|
148
188
|
} else {
|
|
149
|
-
|
|
189
|
+
position = i * positionStep;
|
|
150
190
|
}
|
|
151
191
|
|
|
192
|
+
if (position < prevPos)
|
|
193
|
+
throw new Error('positions are not in order');
|
|
194
|
+
prevPos = position;
|
|
195
|
+
|
|
152
196
|
return {
|
|
153
|
-
y,
|
|
197
|
+
y: 0,
|
|
154
198
|
view: t.view,
|
|
155
199
|
position,
|
|
156
200
|
};
|
|
157
201
|
});
|
|
158
202
|
|
|
159
|
-
|
|
160
|
-
throw new Error('scroll timeline needs at least one trigger');
|
|
203
|
+
this.initY();
|
|
161
204
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private initY() {
|
|
209
|
+
let previousY = -1;
|
|
210
|
+
this.offsets.forEach((offset, i) => {
|
|
211
|
+
const t = this._triggers[i];
|
|
212
|
+
|
|
213
|
+
const pOffset = pageOffset(t.target);
|
|
214
|
+
const y = pOffset.top + pOffset.height * t.offset;
|
|
215
|
+
|
|
216
|
+
if (y < previousY) throw new Error('offsets are not in order');
|
|
217
|
+
previousY = y;
|
|
218
|
+
|
|
219
|
+
offset.y = y;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
update() {
|
|
224
|
+
// execute initY to check if elements have moved
|
|
225
|
+
if (!this.init()) this.initY();
|
|
226
|
+
|
|
227
|
+
// todo this position should come from the GlobalEvent
|
|
228
|
+
this._onScroll({
|
|
229
|
+
y: window.scrollY,
|
|
230
|
+
height: window.innerHeight,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
destroy(opts: DestroyOptions = {}) {
|
|
235
|
+
opts = { ...DEF_DESTROY_OPTS, ...opts };
|
|
236
|
+
|
|
237
|
+
this._scrollEvent();
|
|
238
|
+
this._events.destroy();
|
|
239
|
+
this._responsiveEvent?.remove();
|
|
240
|
+
|
|
241
|
+
if (opts.timelines) {
|
|
242
|
+
for (const timeline of this.timelines) {
|
|
243
|
+
timeline.destroy();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const marker of this.debugMarkers) {
|
|
248
|
+
marker.remove();
|
|
170
249
|
}
|
|
250
|
+
this.debugMarkers = [];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
debug(prefix?: string) {
|
|
254
|
+
this.init();
|
|
255
|
+
|
|
256
|
+
this.debugPrefix = prefix;
|
|
257
|
+
|
|
258
|
+
const height = window.innerHeight;
|
|
259
|
+
|
|
260
|
+
this.offsets.forEach((offset, i) => {
|
|
261
|
+
const y = offset.y - height * offset.view;
|
|
262
|
+
|
|
263
|
+
const marker = document.createElement('span');
|
|
264
|
+
marker.textContent =
|
|
265
|
+
(prefix ? prefix + ' ' : '') + i + ': ' + offset.position;
|
|
266
|
+
marker.style.position = 'absolute';
|
|
267
|
+
marker.style.display = 'block';
|
|
268
|
+
marker.style.right = '0';
|
|
269
|
+
marker.style.top = y + 'px';
|
|
270
|
+
marker.style.minWidth = '50px';
|
|
271
|
+
marker.style.height = '2px';
|
|
272
|
+
marker.style.paddingRight = '10px';
|
|
273
|
+
marker.style.backgroundColor = 'red';
|
|
274
|
+
marker.style.zIndex = '100000';
|
|
275
|
+
|
|
276
|
+
document.body.appendChild(marker);
|
|
277
|
+
this.debugMarkers.push(marker);
|
|
278
|
+
});
|
|
171
279
|
}
|
|
172
280
|
|
|
173
281
|
private _onScroll({ y, height }: { y: number; height: number }) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
282
|
+
this.init();
|
|
283
|
+
|
|
284
|
+
const pos = this.calcPosition({ y, height });
|
|
285
|
+
const clampPos = Math.min(1, Math.max(0, pos));
|
|
286
|
+
const state = pos < 0 ? 'before' : pos > 1 ? 'after' : 'in';
|
|
287
|
+
|
|
288
|
+
// we need to trigger once
|
|
289
|
+
// if the pos is not the same and but pos can have 3 states
|
|
290
|
+
// under (<0), in (0-1) or over (>1)
|
|
291
|
+
// each state should be triggered once
|
|
292
|
+
if (this.lastSeek?.pos === clampPos && this.lastSeek?.state === state) {
|
|
293
|
+
return;
|
|
177
294
|
}
|
|
178
295
|
|
|
296
|
+
this.lastSeek = { pos: clampPos, state };
|
|
297
|
+
|
|
298
|
+
this._events.trigger('update', clampPos, state);
|
|
299
|
+
|
|
300
|
+
for (const timeline of this.timelines) {
|
|
301
|
+
timeline.seek(pos);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @ignore
|
|
307
|
+
*/
|
|
308
|
+
private _onResponsive(
|
|
309
|
+
_a: { width: number; height: number },
|
|
310
|
+
_b: { remove: () => void },
|
|
311
|
+
) {
|
|
312
|
+
this.update();
|
|
313
|
+
|
|
314
|
+
// reposition debug Markers
|
|
315
|
+
if (this.debugMarkers.length) {
|
|
316
|
+
for (const marker of this.debugMarkers) {
|
|
317
|
+
marker.remove();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.debug(this.debugPrefix);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private calcPosition({ y, height }: { y: number; height: number }): number {
|
|
325
|
+
// check if it's outside of the bounds
|
|
326
|
+
const first = this.offsets[0];
|
|
327
|
+
if (y < first.y - height * first.view) return -1;
|
|
328
|
+
|
|
329
|
+
const last = this.offsets[this.offsets.length - 1];
|
|
330
|
+
if (y > last.y - height * last.view) return 2;
|
|
331
|
+
|
|
179
332
|
// find the matching offset
|
|
180
333
|
// rolling window
|
|
181
334
|
for (let i = 1; i < this.offsets.length; i++) {
|
|
182
335
|
const start = this.offsets[i - 1];
|
|
183
336
|
const end = this.offsets[i];
|
|
184
337
|
|
|
185
|
-
const startY = start.y
|
|
186
|
-
const endY = end.y
|
|
187
|
-
|
|
188
|
-
const dif = endY - startY;
|
|
189
|
-
const x = 1 - (endY - y) / dif;
|
|
338
|
+
const startY = start.y - height * start.view;
|
|
339
|
+
const endY = end.y - height * end.view;
|
|
190
340
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
start.position + (end.position - start.position) * x;
|
|
341
|
+
if (y >= startY && y < endY) {
|
|
342
|
+
const dif = endY - startY;
|
|
343
|
+
const x = 1 - (endY - y) / dif;
|
|
195
344
|
|
|
196
|
-
|
|
345
|
+
return start.position + (end.position - start.position) * x;
|
|
197
346
|
}
|
|
198
347
|
}
|
|
348
|
+
|
|
349
|
+
return -1;
|
|
199
350
|
}
|
|
200
351
|
}
|
|
201
352
|
|
|
@@ -239,7 +390,7 @@ function parseTrigger(
|
|
|
239
390
|
target: element,
|
|
240
391
|
offset: parseOffset(offset),
|
|
241
392
|
view: parseOffset(view),
|
|
242
|
-
position
|
|
393
|
+
position,
|
|
243
394
|
};
|
|
244
395
|
}
|
|
245
396
|
|
|
@@ -261,7 +412,7 @@ function parseOffset(val: OffsetValue): number {
|
|
|
261
412
|
throw new Error('unknown view value ' + val);
|
|
262
413
|
}
|
|
263
414
|
|
|
264
|
-
if (typeof val !== 'number'
|
|
415
|
+
if (typeof val !== 'number') {
|
|
265
416
|
throw new Error(
|
|
266
417
|
'view expecting to be a top/center/bottom or a percent value',
|
|
267
418
|
);
|