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 CHANGED
@@ -1,12 +1,5 @@
1
1
 
2
- ## Fire Js
3
- This contains various helpers and types.
2
+ ## Chnobli js
4
3
 
5
- ## Usage
6
- ```json
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.0",
3
+ "version": "0.1.2",
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 or Apache-2.0",
15
+ "license": "MIT",
16
16
  "exports": {
17
- "./*": "./src/*"
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,191 @@
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
+ const val = target.getStyleValue(this.name);
164
+ if (!val)
165
+ return this.defaultValue();
166
+
167
+ return val;
168
+ }
169
+
170
+ setValue(target, val) {
171
+ return target.setStyleValue(this.name, val);
172
+ }
173
+
174
+ removeValue(target) {
175
+ return target.removeStyleValue(this.name);
176
+ }
177
+ }
178
+
179
+ export function newProperty(prop, targetType = '') {
180
+ if (targetType !== 'dom')
181
+ throw new Error('only dom is supported as target');
182
+
183
+ if (TRANSFORM_PROPS.includes(prop))
184
+ return new Transform(prop);
185
+ if (prop === 'xy')
186
+ return new TransformXY(prop);
187
+ if (prop in STYLE_PROPS)
188
+ return new StyleProp(prop);
189
+
190
+ throw new Error('unknown prop ' + prop);
191
+ }
@@ -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
+ }
@@ -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
+ // }