chnobli 0.1.0 → 0.1.2
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/README.md +3 -10
- package/package.json +9 -3
- package/src/animation/animation.js +203 -0
- package/src/animation/property.js +191 -0
- package/src/animation/public.js +53 -0
- package/src/animator.js +185 -0
- package/src/chnobli.js +64 -0
- package/src/scroll/event.js +41 -0
- package/src/scroll/public.js +17 -0
- package/src/scroll/scroll.js +169 -0
- package/src/stagger/stagger.js +45 -0
- package/src/timeline/public.js +154 -0
- package/src/timeline/timeline.js +250 -0
- package/src/timing/ease.js +16 -0
- package/src/timing/easing.js +292 -0
- package/src/timing/ticker.js +114 -0
- package/src/timing/timing.js +195 -0
- package/src/utils/events.js +35 -0
- package/src/utils/internal.js +7 -0
- package/src/utils/motionpath.js +44 -0
- package/src/utils/target.js +164 -0
- package/src/utils/utils.js +55 -0
- package/src/utils/value.js +38 -0
- package/LICENSE-APACHE +0 -201
- /package/{LICENSE-MIT → LICENSE.md} +0 -0
package/src/chnobli.js
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Animation from './animation/public.js';
|
|
2
|
+
import Timeline from './timeline/public.js';
|
|
3
|
+
import Scroll from './scroll/public.js';
|
|
4
|
+
import { stagger as _stagger } from './stagger/stagger.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// todo maybe add, to, from and fromTo to the animate function
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export function animate(targets, props = {}) {
|
|
11
|
+
return new Animation(targets, props);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function timeline(props = {}) {
|
|
15
|
+
return new Timeline(props);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function stagger(value) {
|
|
19
|
+
return _stagger(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function scroll(props = {}) {
|
|
23
|
+
return new Scroll(props);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
|
|
28
|
+
animate(target, {
|
|
29
|
+
|
|
30
|
+
// special
|
|
31
|
+
// translate
|
|
32
|
+
x
|
|
33
|
+
y
|
|
34
|
+
scale
|
|
35
|
+
scaleX
|
|
36
|
+
scaleY
|
|
37
|
+
rotation
|
|
38
|
+
skew
|
|
39
|
+
skewX
|
|
40
|
+
skewY
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// stagger
|
|
45
|
+
repeatDelay
|
|
46
|
+
stagger: {
|
|
47
|
+
// wrap advanced options in an object
|
|
48
|
+
each: 0.1,
|
|
49
|
+
from: "center",
|
|
50
|
+
grid: "auto",
|
|
51
|
+
ease: "power2.inOut",
|
|
52
|
+
repeat: -1, // Repeats immediately, not waiting for the other staggered animations to finish
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
ease,
|
|
57
|
+
duration,
|
|
58
|
+
repeat, (-1?)
|
|
59
|
+
repeatDelay?
|
|
60
|
+
delay,
|
|
61
|
+
alternate
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
*/
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export default class ScrollEvent {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._listeners = new Set;
|
|
4
|
+
|
|
5
|
+
window.addEventListener('scroll', e => this._onScroll(e));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static global() {
|
|
9
|
+
if (typeof window === 'undefined')
|
|
10
|
+
return null;
|
|
11
|
+
|
|
12
|
+
if (typeof window.chnobliScroll === 'undefined')
|
|
13
|
+
window.chnobliScroll = new ScrollEvent;
|
|
14
|
+
return window.chnobliScroll;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
add(fn) {
|
|
18
|
+
this._listeners.add(fn);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
remove: () => {
|
|
22
|
+
this.remove(fn);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
remove(fn) {
|
|
28
|
+
this._listeners.delete(fn);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_onScroll(e) {
|
|
32
|
+
for (const fn of this._listeners) {
|
|
33
|
+
fn({
|
|
34
|
+
y: window.scrollY,
|
|
35
|
+
height: window.innerHeight
|
|
36
|
+
}, {
|
|
37
|
+
remove: () => this.remove(fn)
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Scroll from './scroll.js';
|
|
2
|
+
|
|
3
|
+
export default class PublicScroll {
|
|
4
|
+
/*
|
|
5
|
+
{
|
|
6
|
+
start: el | px
|
|
7
|
+
startView: 'top'
|
|
8
|
+
}
|
|
9
|
+
*/
|
|
10
|
+
constructor(props = {}) {
|
|
11
|
+
this._inner = new Scroll(props);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
add(timeline) {
|
|
15
|
+
this._inner.addTimeline(timeline);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { takeProp } from '../utils/internal.js';
|
|
2
|
+
import { pageOffset } from '../utils/utils.js';
|
|
3
|
+
import ScrollEvent from './event.js';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
run animation when in view (start stop resume when different events happen)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
specify when trigger starts: start: "top(triggerEl) center(viewport)"
|
|
11
|
+
end: bottom top
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
startTriger
|
|
15
|
+
|
|
16
|
+
endTrigger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
skew on scroll
|
|
20
|
+
|
|
21
|
+
scroll pin (once in trigger zone, elements keeps it position relative to start of trigger)
|
|
22
|
+
|
|
23
|
+
parallax
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
export default class Scroll {
|
|
28
|
+
constructor(props = {}) {
|
|
29
|
+
// { target, offset }
|
|
30
|
+
this._start = parseStart(takeProp(props, 'start', null));
|
|
31
|
+
// { target, offset }
|
|
32
|
+
this._end = parseEnd(takeProp(props, 'end', null));
|
|
33
|
+
this._globalEvent = ScrollEvent.global();
|
|
34
|
+
|
|
35
|
+
this.start = {
|
|
36
|
+
// px
|
|
37
|
+
y: 0,
|
|
38
|
+
// percentage
|
|
39
|
+
view: parseOffset(takeProp(props, 'startView', 'top'))
|
|
40
|
+
};
|
|
41
|
+
this.end = {
|
|
42
|
+
// px
|
|
43
|
+
y: 0,
|
|
44
|
+
// percentage
|
|
45
|
+
view: parseOffset(takeProp(props, 'endView', 'bottom'))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.timelines = [];
|
|
49
|
+
|
|
50
|
+
this._rmEvent = this._globalEvent.add(y => this._onScroll(y)).remove;
|
|
51
|
+
this._initialized = false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
addTimeline(timeline) {
|
|
55
|
+
this.timelines.push(timeline);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
init() {
|
|
59
|
+
const startOffset = pageOffset(this._start.target);
|
|
60
|
+
this.start.y = startOffset.top +
|
|
61
|
+
startOffset.height * this._start.offset;
|
|
62
|
+
|
|
63
|
+
if (this._end) {
|
|
64
|
+
const offset = pageOffset(this._end.target);
|
|
65
|
+
this.end.y = offset.top + offset.height * this._end.offset;
|
|
66
|
+
} else {
|
|
67
|
+
this.end.y = this.start.y + pageOffset(this._start.target).height;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_onScroll({ y, height }) {
|
|
72
|
+
if (!this._initialized) {
|
|
73
|
+
this.init();
|
|
74
|
+
this._initialized = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const start = this.start.y + height * this.start.view;
|
|
78
|
+
const end = this.end.y + height * this.end.view;
|
|
79
|
+
|
|
80
|
+
const dif = end - start;
|
|
81
|
+
const x = 1 - (end - y) / dif;
|
|
82
|
+
|
|
83
|
+
for (const timeline of this.timelines) {
|
|
84
|
+
timeline.seek(x);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseStart(val) {
|
|
90
|
+
// might be an object { target, offset }
|
|
91
|
+
if (!val) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'unknown start property use an html element or an object'
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let element = null;
|
|
98
|
+
let offset = 'top';
|
|
99
|
+
|
|
100
|
+
if (typeof val === 'object') {
|
|
101
|
+
if (val instanceof HTMLElement)
|
|
102
|
+
element = val;
|
|
103
|
+
else if ('target' in val) {
|
|
104
|
+
element = val.target;
|
|
105
|
+
if (typeof val.offset !== 'undefined')
|
|
106
|
+
offset = val.offset;
|
|
107
|
+
} else {
|
|
108
|
+
throw new Error('start unknown ' + val);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error('unknown start val');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
target: element,
|
|
116
|
+
offset: parseOffset(offset)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseEnd(val) {
|
|
121
|
+
// might be an object { target, offset }
|
|
122
|
+
if (!val)
|
|
123
|
+
return null;
|
|
124
|
+
|
|
125
|
+
let element = null;
|
|
126
|
+
let offset = 'top';
|
|
127
|
+
|
|
128
|
+
if (typeof val === 'object') {
|
|
129
|
+
if (val instanceof HTMLElement)
|
|
130
|
+
element = val;
|
|
131
|
+
else if ('target' in val) {
|
|
132
|
+
element = val.target;
|
|
133
|
+
if (typeof val.offset !== 'undefined')
|
|
134
|
+
offset = val.offset;
|
|
135
|
+
} else {
|
|
136
|
+
throw new Error('start unknown ' + val);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
throw new Error('unknown start val');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
target: element,
|
|
144
|
+
offset: parseOffset(offset)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parseOffset(val) {
|
|
149
|
+
if (typeof val === 'string') {
|
|
150
|
+
if (val === 'top')
|
|
151
|
+
return 0;
|
|
152
|
+
|
|
153
|
+
if (val === 'center')
|
|
154
|
+
return .5;
|
|
155
|
+
|
|
156
|
+
if (val === 'bottom')
|
|
157
|
+
return 1;
|
|
158
|
+
|
|
159
|
+
throw new Error('unknown view value ' + val);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!(typeof val === 'number')) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
'view expecting to be a top/center/bottom or a percent value'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return val;
|
|
169
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// export class StaggerFunction {
|
|
2
|
+
|
|
3
|
+
// }
|
|
4
|
+
|
|
5
|
+
function isStagger(value) {
|
|
6
|
+
return typeof value === 'object' &&
|
|
7
|
+
value !== null &&
|
|
8
|
+
typeof value.__isStagger__ === 'function';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function callStagger(value, i, l) {
|
|
12
|
+
if (isStagger(value))
|
|
13
|
+
return value.call(i, l);
|
|
14
|
+
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function staggerMap(value, fn) {
|
|
19
|
+
if (isStagger(value))
|
|
20
|
+
return new Stagger((...args) => fn(value.call(...args)));
|
|
21
|
+
|
|
22
|
+
return fn(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function stagger(value) {
|
|
26
|
+
return new Stagger(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class Stagger {
|
|
30
|
+
constructor(value) {
|
|
31
|
+
if (typeof value === 'function') {
|
|
32
|
+
this.fn = value;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// if (value === )
|
|
37
|
+
throw new Error('unknown stagger value ' + value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
call(...args) {
|
|
41
|
+
return this.fn(...args);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
__isStagger__() {}
|
|
45
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { takeProp } from '../utils/internal.js';
|
|
2
|
+
import Timeline from './timeline.js';
|
|
3
|
+
import { STATE_BEFORE, STATE_AFTER } from '../timing/timing.js';
|
|
4
|
+
import { callStagger } from '../stagger/stagger.js';
|
|
5
|
+
import Events from '../utils/events.js';
|
|
6
|
+
|
|
7
|
+
const STATE_PAUSED = 0;
|
|
8
|
+
const STATE_RENDER_ONCE = 1;
|
|
9
|
+
const STATE_PLAYING = 2;
|
|
10
|
+
|
|
11
|
+
export default class PublicTimeline {
|
|
12
|
+
constructor(props = {}) {
|
|
13
|
+
this._defaults = takeProp(props, 'defaults', {});
|
|
14
|
+
this._inner = new Timeline(props);
|
|
15
|
+
|
|
16
|
+
// 'start', 'end'
|
|
17
|
+
this._events = new Events;
|
|
18
|
+
|
|
19
|
+
this._state = STATE_PAUSED;
|
|
20
|
+
this._runningTicker = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set(targets, props, offset = null) {
|
|
24
|
+
// for the moment let's just add as usual but set the duration to 0
|
|
25
|
+
return this.add(targets, { ...props, duration: 0 }, offset);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
add(targets, props, offset = null) {
|
|
29
|
+
if (Array.from(targets).length === 0)
|
|
30
|
+
targets = [targets];
|
|
31
|
+
else
|
|
32
|
+
targets = Array.from(targets);
|
|
33
|
+
|
|
34
|
+
let i = -1;
|
|
35
|
+
for (const target of targets) {
|
|
36
|
+
i++;
|
|
37
|
+
|
|
38
|
+
const nProps = {
|
|
39
|
+
...this._defaults,
|
|
40
|
+
...props
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for (const prop in nProps) {
|
|
44
|
+
nProps[prop] = callStagger(nProps[prop], i, targets.length);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nOffset = callStagger(offset, i);
|
|
48
|
+
|
|
49
|
+
this._inner.add(target, nProps, nOffset, i);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
label(label, offset = null) {
|
|
56
|
+
this._inner.label(label, offset);
|
|
57
|
+
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
play() {
|
|
62
|
+
if (this._state === STATE_PLAYING)
|
|
63
|
+
return;
|
|
64
|
+
|
|
65
|
+
this._state = STATE_PLAYING;
|
|
66
|
+
|
|
67
|
+
this._startTicker();
|
|
68
|
+
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pause() {
|
|
73
|
+
if (this._state === STATE_PAUSED)
|
|
74
|
+
return;
|
|
75
|
+
|
|
76
|
+
this._state = STATE_PAUSED;
|
|
77
|
+
|
|
78
|
+
this._stopTicker();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 0-1
|
|
82
|
+
seek(pos) {
|
|
83
|
+
this._inner.init();
|
|
84
|
+
|
|
85
|
+
this._inner.seek(pos);
|
|
86
|
+
|
|
87
|
+
if (this._state !== STATE_PLAYING)
|
|
88
|
+
this._state = STATE_RENDER_ONCE;
|
|
89
|
+
|
|
90
|
+
this._startTicker();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
reset() {
|
|
94
|
+
this._inner.seek(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
reverse() {
|
|
98
|
+
this._inner.timing.reverse();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// start, update, end -> () => // remove Event
|
|
102
|
+
on(event, fn) {
|
|
103
|
+
return this._events.add(event, fn);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onPromise(event) {
|
|
107
|
+
return this._events.wait(event);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_startTicker() {
|
|
111
|
+
if (this._runningTicker)
|
|
112
|
+
return;
|
|
113
|
+
|
|
114
|
+
this._inner.init();
|
|
115
|
+
|
|
116
|
+
this._runningTicker = this._inner.ticker.add(change => {
|
|
117
|
+
if (this._inner.timing.state === STATE_BEFORE) {
|
|
118
|
+
this._events.trigger('start');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this._inner.timing.state === STATE_AFTER) {
|
|
122
|
+
this._stopTicker();
|
|
123
|
+
this._events.trigger('end');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (this._state === STATE_PLAYING)
|
|
128
|
+
this._inner.advance(change);
|
|
129
|
+
|
|
130
|
+
// todo when we add smooth seeks we need to update this
|
|
131
|
+
|
|
132
|
+
this._inner.render();
|
|
133
|
+
|
|
134
|
+
// we rendered once let's stop
|
|
135
|
+
if (this._state === STATE_RENDER_ONCE) {
|
|
136
|
+
this._state = STATE_PAUSED;
|
|
137
|
+
this._stopTicker();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_stopTicker() {
|
|
143
|
+
if (!this._runningTicker)
|
|
144
|
+
return;
|
|
145
|
+
|
|
146
|
+
this._runningTicker.remove();
|
|
147
|
+
this._runningTicker = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// update() {
|
|
151
|
+
// console.log('render');
|
|
152
|
+
// this._inner.render();
|
|
153
|
+
// }
|
|
154
|
+
}
|