chnobli 0.1.0 → 0.1.1
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 +200 -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 +145 -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/README.md
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
##
|
|
3
|
-
This contains various helpers and types.
|
|
2
|
+
## Chnobli js
|
|
4
3
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
{
|
|
8
|
-
"dependencies": {
|
|
9
|
-
"fire": "npm:fire-lib-js@^0.2"
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
```
|
|
4
|
+
## Version 0.1 unstable
|
|
5
|
+
Version 0.1 is unstable the api might change at any version number
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chnobli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"author": "Sören Meier <info@soerenmeier.ch>",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -12,13 +12,19 @@
|
|
|
12
12
|
"type": "git",
|
|
13
13
|
"url": "https://github.com/soerenmeier/chnobli.git"
|
|
14
14
|
},
|
|
15
|
-
"license": "MIT
|
|
15
|
+
"license": "MIT",
|
|
16
16
|
"exports": {
|
|
17
|
-
"
|
|
17
|
+
"./easing": "./src/timing/easing.js",
|
|
18
|
+
"./utils": "./src/utils/utils.js",
|
|
19
|
+
".": "./src/chnobli.js"
|
|
18
20
|
},
|
|
19
21
|
"devDependencies": {
|
|
20
22
|
"eslint": "^8.45.0",
|
|
21
23
|
"jsdoc": "^4.0.2",
|
|
22
24
|
"typescript": "^5.1.6"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"bezier-easing": "^2.1.0",
|
|
28
|
+
"fire": "npm:fire-lib-js@0.2.17"
|
|
23
29
|
}
|
|
24
30
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { takeProp } from '../utils/internal.js';
|
|
2
|
+
import Value from '../utils/value.js';
|
|
3
|
+
// import Ease from '../easing/ease.js';
|
|
4
|
+
import Timing, {
|
|
5
|
+
newTiming, STATE_BEFORE, STATE_AFTER
|
|
6
|
+
} from '../timing/timing.js';
|
|
7
|
+
|
|
8
|
+
import { newProperty } from './property.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export default class Animation {
|
|
12
|
+
// targets;
|
|
13
|
+
// #timing;
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
props
|
|
17
|
+
*/
|
|
18
|
+
constructor(target, ticker, props = {}) {
|
|
19
|
+
this.timing = newTiming(props);
|
|
20
|
+
this._ticker = ticker;
|
|
21
|
+
this.target = ticker._targets.register(target);
|
|
22
|
+
|
|
23
|
+
this._prevTimingState = this.timing.state;
|
|
24
|
+
this._props = [];
|
|
25
|
+
this._initialized = false;
|
|
26
|
+
|
|
27
|
+
for (const prop in props) {
|
|
28
|
+
if (prop in this)
|
|
29
|
+
throw new Error('prop: ' + prop + ' is not allowed');
|
|
30
|
+
|
|
31
|
+
this[prop] = new PropertyAnimation(
|
|
32
|
+
prop,
|
|
33
|
+
props[prop],
|
|
34
|
+
this
|
|
35
|
+
);
|
|
36
|
+
this._props.push(this[prop]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
advance(change) {
|
|
41
|
+
this._prevTimingState = this.timing.state;
|
|
42
|
+
this.timing.advance(change);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
seek(pos) {
|
|
46
|
+
this._prevTimingState = this.timing.state;
|
|
47
|
+
this.timing.seek(pos);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isFinite() {
|
|
51
|
+
return this.timing.isFinite();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get duration() {
|
|
55
|
+
return this.timing.duration;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
init() {
|
|
59
|
+
if (this._initialized)
|
|
60
|
+
return;
|
|
61
|
+
this._initialized = true;
|
|
62
|
+
|
|
63
|
+
for (const prop of this._props) {
|
|
64
|
+
prop.init(this.target);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
render() {
|
|
69
|
+
if (this.timing.state === STATE_BEFORE) {
|
|
70
|
+
// console.log('render before');
|
|
71
|
+
|
|
72
|
+
for (const prop of this._props) {
|
|
73
|
+
prop.restoreBefore(this.target);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// to render before we need to remove
|
|
77
|
+
|
|
78
|
+
return;
|
|
79
|
+
} else if (this.timing.state === STATE_AFTER) {
|
|
80
|
+
// console.log('render after');
|
|
81
|
+
// return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const pos = this.timing.position;
|
|
85
|
+
|
|
86
|
+
// now render
|
|
87
|
+
for (const prop of this._props) {
|
|
88
|
+
prop.render(pos, this.target);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class PropertyAnimation {
|
|
94
|
+
constructor(prop, value, animation) {
|
|
95
|
+
this.prop = newProperty(prop, animation.target.type());
|
|
96
|
+
// we will get the from the targets
|
|
97
|
+
|
|
98
|
+
this.valueFn = null;
|
|
99
|
+
|
|
100
|
+
this.iniFrom = null;
|
|
101
|
+
this.iniTo = null;
|
|
102
|
+
|
|
103
|
+
this.from = null;
|
|
104
|
+
this.to = null;
|
|
105
|
+
|
|
106
|
+
if (typeof value === 'object') {
|
|
107
|
+
if ('from' in value)
|
|
108
|
+
this.iniFrom = this.prop.parseValue(value.from);
|
|
109
|
+
if ('to' in value)
|
|
110
|
+
this.iniTo = this.prop.parseValue(value.to);
|
|
111
|
+
|
|
112
|
+
if (!this.iniFrom && !this.iniTo)
|
|
113
|
+
throw new Error('from or to expected');
|
|
114
|
+
} else if (typeof value === 'function') {
|
|
115
|
+
this.valueFn = value;
|
|
116
|
+
} else {
|
|
117
|
+
this.iniTo = this.prop.parseValue(value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// calculate from and to position
|
|
122
|
+
init(target) {
|
|
123
|
+
if (this.valueFn)
|
|
124
|
+
return;
|
|
125
|
+
|
|
126
|
+
this.from = this.iniFrom;
|
|
127
|
+
if (!this.from)
|
|
128
|
+
this.from = this.prop.getValue(target);
|
|
129
|
+
|
|
130
|
+
this.to = this.iniTo;
|
|
131
|
+
if (!this.to)
|
|
132
|
+
this.to = this.prop.getValue(target);
|
|
133
|
+
|
|
134
|
+
if (this.to.unit !== this.from.unit)
|
|
135
|
+
throw new Error(this.from.unit + ' != ' + this.to.unit);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
restoreBefore(target) {
|
|
139
|
+
this.prop.removeValue(target);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
render(pos, target) {
|
|
143
|
+
if (this.valueFn) {
|
|
144
|
+
this.prop.setValue(target, this.prop.parseValue(this.valueFn(pos)));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const dif = this.to.num - this.from.num;
|
|
149
|
+
|
|
150
|
+
this.prop.setValue(target, this.from.cloneAdd(pos * dif));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
|
|
156
|
+
animate(target, {
|
|
157
|
+
|
|
158
|
+
// special
|
|
159
|
+
// translate
|
|
160
|
+
x
|
|
161
|
+
y
|
|
162
|
+
scale
|
|
163
|
+
scaleX
|
|
164
|
+
scaleY
|
|
165
|
+
rotation
|
|
166
|
+
skew
|
|
167
|
+
skewX
|
|
168
|
+
skewY
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// stagger
|
|
173
|
+
repeatDelay
|
|
174
|
+
stagger: {
|
|
175
|
+
// wrap advanced options in an object
|
|
176
|
+
each: 0.1,
|
|
177
|
+
from: "center",
|
|
178
|
+
grid: "auto",
|
|
179
|
+
ease: "power2.inOut",
|
|
180
|
+
repeat: -1, // Repeats immediately, not waiting for the other staggered animations to finish
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
ease,
|
|
185
|
+
duration,
|
|
186
|
+
repeat, true|false|0-x
|
|
187
|
+
repeatDelay?
|
|
188
|
+
delay,
|
|
189
|
+
alternate
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
const tl new Timeline;
|
|
194
|
+
|
|
195
|
+
tl.add([
|
|
196
|
+
animation()
|
|
197
|
+
], 100)
|
|
198
|
+
|
|
199
|
+
tl.addGroup()
|
|
200
|
+
|
|
201
|
+
tl.add()
|
|
202
|
+
|
|
203
|
+
*/
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import Value from '../utils/value.js';
|
|
2
|
+
|
|
3
|
+
export default class Property {
|
|
4
|
+
constructor(name) {
|
|
5
|
+
// this is the property name
|
|
6
|
+
this.name = name;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
parseValue(val) {
|
|
10
|
+
return Value.parse(val);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
defaultValue() {
|
|
14
|
+
throw new Error('default value for ' + this.name + ' not known');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// try to access the value from the target
|
|
18
|
+
getValue(target) {
|
|
19
|
+
const val = target.getValue(this.name);
|
|
20
|
+
if (typeof val === 'undefined' || val === null)
|
|
21
|
+
return this.defaultValue();
|
|
22
|
+
|
|
23
|
+
return val;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setValue(target, val) {
|
|
27
|
+
return target.setValue(this.name, val);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
removeValue(target) {
|
|
31
|
+
// todo remove
|
|
32
|
+
return target.removeValue(this.name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const TRANSFORM_PROPS = ['x', 'y', 'scale', 'scaleX', 'scaleY', 'rotate'];
|
|
37
|
+
|
|
38
|
+
export class Transform extends Property {
|
|
39
|
+
constructor(name) {
|
|
40
|
+
super(name);
|
|
41
|
+
|
|
42
|
+
if (name === 'x' || name === 'y') {
|
|
43
|
+
this._defUnit = 'px';
|
|
44
|
+
this._defVal = 0;
|
|
45
|
+
this._transformFunction = 'translate' + name.toUpperCase();
|
|
46
|
+
// } else if (name === 'rotation') {
|
|
47
|
+
// this.defUnit = 'deg';
|
|
48
|
+
// this.defVal = 0;
|
|
49
|
+
} else if (name.startsWith('scale')) {
|
|
50
|
+
this._defUnit = null;
|
|
51
|
+
this._defVal = 1;
|
|
52
|
+
this._transformFunction = name;
|
|
53
|
+
} else if (name === 'rotate') {
|
|
54
|
+
this._defUnit = 'deg';
|
|
55
|
+
this._defVal = 0;
|
|
56
|
+
this._transformFunction = name;
|
|
57
|
+
} else {
|
|
58
|
+
throw new Error('unknown prop ' + name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
parseValue(val) {
|
|
63
|
+
return Value.parse(val, this._defUnit);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
defaultValue() {
|
|
67
|
+
return new Value(this._defVal, this._defUnit);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getValue(target) {
|
|
71
|
+
const val = target.getTransformValue(this._transformFunction);
|
|
72
|
+
if (typeof val === 'undefined' || val === null)
|
|
73
|
+
return this.defaultValue();
|
|
74
|
+
|
|
75
|
+
return val;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setValue(target, val) {
|
|
79
|
+
return target.setTransformValue(this._transformFunction, val);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
removeValue(target) {
|
|
83
|
+
// todo remove
|
|
84
|
+
return target.removeTransformValue(this._transformFunction);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class TransformXY extends Property {
|
|
89
|
+
constructor(name) {
|
|
90
|
+
super(name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
parseValue(val) {
|
|
94
|
+
if (Array.isArray(val))
|
|
95
|
+
val = { x: val[0], y: val[1] };
|
|
96
|
+
|
|
97
|
+
const { x, y } = val;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
x: Value.parse(val.x, 'px'),
|
|
101
|
+
y: Value.parse(val.y, 'px')
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
defaultValue() {
|
|
106
|
+
return {
|
|
107
|
+
x: new Value(0, 'px'),
|
|
108
|
+
y: new Value(0, 'px')
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getValue(target) {
|
|
113
|
+
const x = target.getTransformValue('translateX');
|
|
114
|
+
const y = target.getTransformValue('translateY');
|
|
115
|
+
|
|
116
|
+
const def = this.defaultValue();
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
x: x ? x : def.x,
|
|
120
|
+
y: y ? y : def.y
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setValue(target, val) {
|
|
125
|
+
target.setTransformValue('translateX', val.x);
|
|
126
|
+
target.setTransformValue('translateY', val.y);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
removeValue(target) {
|
|
130
|
+
target.removeTransformValue('translateX');
|
|
131
|
+
target.removeTransformValue('translateY');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const STYLE_PROPS = {
|
|
136
|
+
'width': 'px',
|
|
137
|
+
'height': 'px',
|
|
138
|
+
'top': 'px',
|
|
139
|
+
'left': 'px',
|
|
140
|
+
'right': 'px',
|
|
141
|
+
'bottom': 'px',
|
|
142
|
+
'opacity': null,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class StyleProp extends Property {
|
|
146
|
+
constructor(name) {
|
|
147
|
+
super(name);
|
|
148
|
+
|
|
149
|
+
this.unit = STYLE_PROPS[name];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
parseValue(val) {
|
|
153
|
+
// maybe keep it unitless as long as possible, maybe until we need to
|
|
154
|
+
// write it
|
|
155
|
+
return Value.parse(val, this.unit);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
defaultValue() {
|
|
159
|
+
return new Value(0, this.unit);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getValue(target) {
|
|
163
|
+
if (target.type() !== 'dom')
|
|
164
|
+
throw new Error('error not dom');
|
|
165
|
+
|
|
166
|
+
const style = getComputedStyle(target.target);
|
|
167
|
+
|
|
168
|
+
return Value.parse(style[this.name]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getValue(target) {
|
|
172
|
+
const val = target.getStyleValue(this.name);
|
|
173
|
+
if (!val)
|
|
174
|
+
return this.defaultValue();
|
|
175
|
+
|
|
176
|
+
return val;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setValue(target, val) {
|
|
180
|
+
return target.setStyleValue(this.name, val);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
removeValue(target) {
|
|
184
|
+
return target.removeStyleValue(this.name);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function newProperty(prop, targetType = '') {
|
|
189
|
+
if (targetType !== 'dom')
|
|
190
|
+
throw new Error('only dom is supported as target');
|
|
191
|
+
|
|
192
|
+
if (TRANSFORM_PROPS.includes(prop))
|
|
193
|
+
return new Transform(prop);
|
|
194
|
+
if (prop === 'xy')
|
|
195
|
+
return new TransformXY(prop);
|
|
196
|
+
if (prop in STYLE_PROPS)
|
|
197
|
+
return new StyleProp(prop);
|
|
198
|
+
|
|
199
|
+
throw new Error('unknown prop ' + prop);
|
|
200
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { takeProp } from '../utils/internal.js';
|
|
2
|
+
import Timeline from '../timeline/public.js';
|
|
3
|
+
import { parseDelay } from '../timing/timing.js';
|
|
4
|
+
import { staggerMap } from '../stagger/stagger.js';
|
|
5
|
+
|
|
6
|
+
export default class PublicAnimation {
|
|
7
|
+
constructor(targets, props = {}) {
|
|
8
|
+
const autoplay = parseAutoplay(takeProp(props, 'autoplay', true));
|
|
9
|
+
const delay = takeProp(props, 'delay', 0);
|
|
10
|
+
|
|
11
|
+
// we don't wan't the ease and duration on the timeline
|
|
12
|
+
const ease = takeProp(props, 'ease', null);
|
|
13
|
+
const duration = takeProp(props, 'duration', null);
|
|
14
|
+
|
|
15
|
+
this._tl = new Timeline(props);
|
|
16
|
+
|
|
17
|
+
props.ease = ease;
|
|
18
|
+
props.duration = duration;
|
|
19
|
+
|
|
20
|
+
this._tl.add(targets, props, staggerMap(delay, v => '+=' + v));
|
|
21
|
+
|
|
22
|
+
if (autoplay)
|
|
23
|
+
this._tl.play();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
play() {
|
|
27
|
+
this._tl.play();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pause() {
|
|
31
|
+
this._tl.pause();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 0-1
|
|
35
|
+
seek(pos) {
|
|
36
|
+
this._tl.seek(pos);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
reset() {
|
|
40
|
+
this._tl.reset();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
reverse() {
|
|
44
|
+
this._tl.reverse();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseAutoplay(autoplay) {
|
|
49
|
+
if (autoplay !== true && autoplay !== false)
|
|
50
|
+
throw new Error('autoplay needs to be true|false');
|
|
51
|
+
|
|
52
|
+
return autoplay;
|
|
53
|
+
}
|
package/src/animator.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { quadInOut, quadOut } from './easing/index.js';
|
|
2
|
+
import { range } from 'fire/util.js';
|
|
3
|
+
|
|
4
|
+
const START = 0;
|
|
5
|
+
const RUNNING = 1;
|
|
6
|
+
const ENDING = 2;
|
|
7
|
+
const ENDED = 3;
|
|
8
|
+
|
|
9
|
+
export default class Animator {
|
|
10
|
+
constructor(el, props) {
|
|
11
|
+
this.el = el;
|
|
12
|
+
|
|
13
|
+
this.running = false;
|
|
14
|
+
|
|
15
|
+
this.props = [];
|
|
16
|
+
|
|
17
|
+
this.prevTime = null;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
for (const propName in props) {
|
|
21
|
+
const prop = new PropertyAnimator(propName, props[propName], el);
|
|
22
|
+
this.props.push(prop);
|
|
23
|
+
this[propName] = prop;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// if the animation loop has not started starts it
|
|
28
|
+
run() {
|
|
29
|
+
// if running
|
|
30
|
+
if (this.prevTime !== null)
|
|
31
|
+
return;
|
|
32
|
+
|
|
33
|
+
requestAnimationFrame(a => this._tick(a));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_tick(elapsed) {
|
|
37
|
+
if (this.prevTime === null)
|
|
38
|
+
this.prevTime = elapsed;
|
|
39
|
+
|
|
40
|
+
const change = elapsed - this.prevTime;
|
|
41
|
+
this.prevTime = elapsed;
|
|
42
|
+
|
|
43
|
+
const transforms = [];
|
|
44
|
+
const cssVars = [];
|
|
45
|
+
|
|
46
|
+
let stillRunning = false;
|
|
47
|
+
|
|
48
|
+
for (const prop of this.props) {
|
|
49
|
+
prop._tick(change);
|
|
50
|
+
|
|
51
|
+
if (prop.state < ENDED)
|
|
52
|
+
stillRunning = true;
|
|
53
|
+
|
|
54
|
+
if (prop.name === 'translateY' || prop.name === 'scale')
|
|
55
|
+
transforms.push(prop);
|
|
56
|
+
else if (prop.name.startsWith('--'))
|
|
57
|
+
cssVars.push(prop);
|
|
58
|
+
else
|
|
59
|
+
throw new Error('unknown prop ' + prop.name);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!stillRunning) {
|
|
63
|
+
this.prevTime = null;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (transforms.some(t => t.state < ENDED)) {
|
|
68
|
+
this.el.style.transform = transforms.map(t => {
|
|
69
|
+
const v = t._toTransformValue();
|
|
70
|
+
return v;
|
|
71
|
+
}).join(' ');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const prop of cssVars) {
|
|
75
|
+
if (prop.state < ENDED)
|
|
76
|
+
this.el.style.setProperty(prop.name, prop._toCssValue());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
requestAnimationFrame(a => this._tick(a));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class PropertyAnimator {
|
|
85
|
+
constructor(name, type, el) {
|
|
86
|
+
this.name = name;
|
|
87
|
+
this.type = type;
|
|
88
|
+
|
|
89
|
+
this.fromV = null;
|
|
90
|
+
this.currentV = null;
|
|
91
|
+
this.toV = null;
|
|
92
|
+
|
|
93
|
+
this.easing = quadInOut;
|
|
94
|
+
|
|
95
|
+
this.elapsed = 0;
|
|
96
|
+
this.duration = 0;
|
|
97
|
+
this.state = ENDED;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
set(val) {
|
|
101
|
+
this.fromV = val;
|
|
102
|
+
this.currentV = val;
|
|
103
|
+
this.toV = val;
|
|
104
|
+
|
|
105
|
+
this.elapsed = 0;
|
|
106
|
+
this.duration = 0;
|
|
107
|
+
this.state = START;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
to(val, dur = null) {
|
|
111
|
+
this.fromV = this.currentV;
|
|
112
|
+
this.toV = val;
|
|
113
|
+
|
|
114
|
+
if (this.state === RUNNING)
|
|
115
|
+
this.easing = quadOut;
|
|
116
|
+
else
|
|
117
|
+
this.easing = quadInOut;
|
|
118
|
+
|
|
119
|
+
this.elapsed = 0;
|
|
120
|
+
this.duration = dur ?? 500;
|
|
121
|
+
this.state = START;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_tick(change) {
|
|
125
|
+
if (this.state >= ENDING)
|
|
126
|
+
return this.state = ENDED;
|
|
127
|
+
|
|
128
|
+
this.elapsed += change;
|
|
129
|
+
|
|
130
|
+
const dur = Math.max(this.duration, 1);
|
|
131
|
+
const t = Math.min(this.elapsed / dur, 1);
|
|
132
|
+
if (t >= 1)
|
|
133
|
+
this.state = ENDED;
|
|
134
|
+
else
|
|
135
|
+
this.state = RUNNING;
|
|
136
|
+
|
|
137
|
+
const et = this.easing(t);
|
|
138
|
+
|
|
139
|
+
if (this.type === 'num') {
|
|
140
|
+
const dif = this.toV - this.fromV;
|
|
141
|
+
this.currentV = this.fromV + dif * et;
|
|
142
|
+
} else if (this.type === 'hex') {
|
|
143
|
+
if (!Array.isArray(this.fromV))
|
|
144
|
+
this.fromV = hexToRgb(this.fromV);
|
|
145
|
+
if (!Array.isArray(this.toV))
|
|
146
|
+
this.toV = hexToRgb(this.toV);
|
|
147
|
+
|
|
148
|
+
// now animate
|
|
149
|
+
this.currentV = this.fromV.map((from, i) => {
|
|
150
|
+
return from + (this.toV[i] - from) * et;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_toTransformValue() {
|
|
156
|
+
let v = this.currentV;
|
|
157
|
+
if (this.name === 'translateY')
|
|
158
|
+
v += 'px';
|
|
159
|
+
|
|
160
|
+
return `${this.name}(${v})`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_toCssValue() {
|
|
164
|
+
if (this.type === 'hex')
|
|
165
|
+
return `rgba(${this.currentV.map(v => Math.round(v)).join()}, 1)`;
|
|
166
|
+
throw new Error('to css value');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
function hexToRgb(val) {
|
|
172
|
+
if (!val.startsWith('#') || val.length !== 7)
|
|
173
|
+
throw new Error('expected a hex value with 6 characters');
|
|
174
|
+
|
|
175
|
+
const hex = val.substring(1);
|
|
176
|
+
|
|
177
|
+
const values = range(0, 3)
|
|
178
|
+
.map(i => parseInt(hex.substring(i * 2, i * 2 + 2), 16));
|
|
179
|
+
|
|
180
|
+
return values;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// function rgbToHex(rgb) {
|
|
184
|
+
// return
|
|
185
|
+
// }
|