qarl 1.0.0 → 1.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/LICENSE +21 -21
- package/README.md +57 -1
- package/index.js +8 -7
- package/package.json +1 -1
- package/src/controllers/Curve.js +123 -0
- package/{controllers → src/controllers}/FromTo.js +13 -15
- package/src/core/Core.js +372 -0
- package/{core → src/core}/defaults.js +1 -0
- package/src/emitter/Notifier.js +97 -0
- package/core/Core.js +0 -371
- package/emitter/Notifier.js +0 -97
- /package/{behaviors → src/behaviors}/easings.js +0 -0
- /package/{behaviors → src/behaviors}/modes.js +0 -0
- /package/{core → src/core}/events.js +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 ThreePixDroid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# QARL (Quantum Animation Remixable Library) 😎
|
|
2
2
|
|
|
3
|
+
*Description generated by GPT while we were busy doing more important things.*
|
|
4
|
+
|
|
3
5
|
QARL is an animation library for my game engine.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
⚠️ **Attention!** ⚠️
|
|
8
|
+
|
|
9
|
+
New project! 💡 It surely has some bugs, unfinished features, and odd solutions, but I've put a lot of heart and effort into it. Thanks for stopping by—I hope you'll enjoy it! 🙏✨
|
|
10
|
+
|
|
11
|
+
## Why QARL? 🏎️💨
|
|
12
|
+
|
|
13
|
+
Looking for performance? QARL's FromTo animation computes so fast, it feels like it bends space-time! 💫 No more waiting for slow calculations—just smooth, seamless transitions that keep up with the pace of your game.
|
|
6
14
|
|
|
7
15
|
## Installation
|
|
8
16
|
|
|
@@ -10,3 +18,51 @@ Install the library via npm:
|
|
|
10
18
|
|
|
11
19
|
```bash
|
|
12
20
|
npm install qarl
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## FromTo example
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const cube3 = new THREE.Mesh(
|
|
27
|
+
new THREE.BoxGeometry(),
|
|
28
|
+
new THREE.MeshBasicMaterial({ color: 0x00ffff })
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
new QARL.FromTo({
|
|
32
|
+
target: cube3,
|
|
33
|
+
dynamic: true,
|
|
34
|
+
loop: true,
|
|
35
|
+
time: 3000,
|
|
36
|
+
mode: QARL.modes.pingPong, // bounce, yoyo, pingPong
|
|
37
|
+
easing: QARL.easings.outQuad,
|
|
38
|
+
from: { rotation: { x: 1, y: 2 }, position: { x: 2, z: 2 }, scale: { x: .01, y: 1, z: 1 } },
|
|
39
|
+
to: { rotation: { x: 3, y: -5 }, position: { x: -2, z: -2 }, scale: { x: 3, y: .5, z: .5 } },
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Curve example
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
const cube3 = new THREE.Mesh(
|
|
47
|
+
new THREE.BoxGeometry(),
|
|
48
|
+
new THREE.MeshBasicMaterial({ color: 0xff00ff })
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
new QARL.Curve({
|
|
52
|
+
target: cube3,
|
|
53
|
+
loop: true,
|
|
54
|
+
time: 10000,
|
|
55
|
+
mode: QARL.modes.yoyo,
|
|
56
|
+
easing: QARL.easings.inOutBack,
|
|
57
|
+
smoothing: 10,
|
|
58
|
+
properties: ['position.x', 'position.y', 'position.z', 'scale.x', 'scale.y', 'scale.z'],
|
|
59
|
+
points: [
|
|
60
|
+
[-2, -2, 0, .1, .5, .5],
|
|
61
|
+
[ 2, -2, 0, .5, .2, .2],
|
|
62
|
+
[ 2, 0, 1, .1, .5, .5],
|
|
63
|
+
[-2, 0, 1, .5, .2, .2],
|
|
64
|
+
[-2, 2, 0, .1, .5, .5],
|
|
65
|
+
[ 2, 2, 0, .5, .2, .2],
|
|
66
|
+
],
|
|
67
|
+
})
|
|
68
|
+
```
|
package/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { Core } from './core/Core'
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export { Notifier } from './emitter/Notifier'
|
|
5
|
-
export {
|
|
6
|
-
export { easings } from './behaviors/easings'
|
|
7
|
-
export {
|
|
1
|
+
export { Core } from './src/core/Core'
|
|
2
|
+
export { Curve } from './src/controllers/Curve'
|
|
3
|
+
export { FromTo } from './src/controllers/FromTo'
|
|
4
|
+
export { Notifier } from './src/emitter/Notifier'
|
|
5
|
+
export { DEFAULTS } from './src/core/defaults'
|
|
6
|
+
export { easings } from './src/behaviors/easings'
|
|
7
|
+
export { EVENTS } from './src/core/events'
|
|
8
|
+
export { modes } from './src/behaviors/modes'
|
package/package.json
CHANGED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Core } from "../core/Core"
|
|
2
|
+
|
|
3
|
+
export class Curve extends Core {
|
|
4
|
+
static DEFAULTS = {
|
|
5
|
+
...Core.DEFAULTS,
|
|
6
|
+
properties: [],
|
|
7
|
+
points: [],
|
|
8
|
+
smoothing: 20,
|
|
9
|
+
// useLerp: true,
|
|
10
|
+
// path: [],
|
|
11
|
+
// speed: 1,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
_preparePropertySetters() {
|
|
15
|
+
this.propertySetters = this.settings.properties.map(property => {
|
|
16
|
+
const keys = property.split('.')
|
|
17
|
+
const lastKey = keys.pop()
|
|
18
|
+
|
|
19
|
+
return (value) => {
|
|
20
|
+
let obj = this.target
|
|
21
|
+
for (let i = 0; i < keys.length; i++) {
|
|
22
|
+
obj = obj[keys[i]]
|
|
23
|
+
}
|
|
24
|
+
obj[lastKey] = value
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_generatePath() {
|
|
30
|
+
const points = this.settings.points
|
|
31
|
+
const smoothing = Math.max(this.settings.smoothing, 1)
|
|
32
|
+
|
|
33
|
+
const result = []
|
|
34
|
+
this.totalLength = 0
|
|
35
|
+
|
|
36
|
+
function interpolate(p0, p1, p2, p3, t) {
|
|
37
|
+
const t2 = t * t
|
|
38
|
+
const t3 = t2 * t
|
|
39
|
+
|
|
40
|
+
return p0.map((_, i) =>
|
|
41
|
+
0.5 * (
|
|
42
|
+
(2 * p1[i]) +
|
|
43
|
+
(-p0[i] + p2[i]) * t +
|
|
44
|
+
(2 * p0[i] - 5 * p1[i] + 4 * p2[i] - p3[i]) * t2 +
|
|
45
|
+
(-p0[i] + 3 * p1[i] - 3 * p2[i] + p3[i]) * t3
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
51
|
+
const p0 = points[i === 0 ? i : i - 1]
|
|
52
|
+
const p1 = points[i]
|
|
53
|
+
const p2 = points[i + 1]
|
|
54
|
+
const p3 = points[i + 2 < points.length ? i + 2 : i + 1]
|
|
55
|
+
|
|
56
|
+
for (let t = 0; t < smoothing; t++) {
|
|
57
|
+
const pt = interpolate(p0, p1, p2, p3, t / smoothing)
|
|
58
|
+
result.push(pt)
|
|
59
|
+
|
|
60
|
+
if (result.length > 1) {
|
|
61
|
+
this.totalLength += this._calculateDistance(result[result.length - 2], pt)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
result.push(points[points.length - 1])
|
|
67
|
+
|
|
68
|
+
return result
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_calculateDistance(p1, p2) {
|
|
72
|
+
return Math.sqrt(p1.reduce((acc, _, i) => acc + Math.pow(p2[i] - p1[i], 2), 0))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_refreshDynamicProps() {
|
|
76
|
+
super._refreshDynamicProps()
|
|
77
|
+
this.path = this._generatePath()
|
|
78
|
+
|
|
79
|
+
if (!this.target || this.path.length === 0 || this.settings.properties.length === 0) {
|
|
80
|
+
this._setTargetProperties = Core._noop
|
|
81
|
+
} else {
|
|
82
|
+
this._preparePropertySetters()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_setTargetProperties(values) {
|
|
87
|
+
for (let i = 0; i < this.propertySetters.length; i++) {
|
|
88
|
+
this.propertySetters[i](values[i])
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_clamp(value, min, max) {
|
|
93
|
+
return Math.max(min, Math.min(value, max))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_lerp(a, b, t) {
|
|
97
|
+
return a + (b - a) * t
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_getInterpolatedPosition() {
|
|
101
|
+
const maxIndex = this.path.length - 1
|
|
102
|
+
const exactIndex = this.easeValue * maxIndex
|
|
103
|
+
const clampedExactIndex = this._clamp(exactIndex, 0, maxIndex)
|
|
104
|
+
|
|
105
|
+
// Determine the indices of the two neighboring points
|
|
106
|
+
const lowerIndex = clampedExactIndex >= maxIndex ? maxIndex - 1 : Math.floor(clampedExactIndex)
|
|
107
|
+
const upperIndex = Math.min(lowerIndex + 1, maxIndex)
|
|
108
|
+
|
|
109
|
+
const interpolationFactor = exactIndex - lowerIndex
|
|
110
|
+
|
|
111
|
+
const lowerPoint = this.path[lowerIndex]
|
|
112
|
+
const upperPoint = this.path[upperIndex]
|
|
113
|
+
|
|
114
|
+
return lowerPoint.map((coord, i) =>
|
|
115
|
+
this._lerp(coord, upperPoint[i], interpolationFactor)
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_update() {
|
|
120
|
+
super._update()
|
|
121
|
+
this._setTargetProperties(this._getInterpolatedPosition())
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -5,7 +5,6 @@ export class FromTo extends Core {
|
|
|
5
5
|
static DEFAULTS = {
|
|
6
6
|
...Core.DEFAULTS,
|
|
7
7
|
dynamic: false, // в динамическом состоянии можно обновлять значения from и to во время анимации
|
|
8
|
-
target: null,
|
|
9
8
|
from: null,
|
|
10
9
|
to: null
|
|
11
10
|
}
|
|
@@ -16,23 +15,24 @@ export class FromTo extends Core {
|
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
_processFromTo() {
|
|
19
|
-
this.target = this.settings.target
|
|
20
|
-
|
|
21
|
-
this._from = this.settings.from || this._createState(this.settings.to || {}, this.target)
|
|
22
|
-
this._to = this.settings.to || this._createState(this.settings.from || {}, this.target)
|
|
23
|
-
|
|
24
18
|
if (!this.target) {
|
|
25
19
|
this._updateFromTo = Core._noop
|
|
26
|
-
|
|
27
|
-
this.
|
|
20
|
+
this._from = {}
|
|
21
|
+
this._to = {}
|
|
28
22
|
} else {
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
|
|
23
|
+
this._from = this.settings.from || this._createState(this.settings.to || {}, this.target)
|
|
24
|
+
this._to = this.settings.to || this._createState(this.settings.from || {}, this.target)
|
|
25
|
+
|
|
26
|
+
if (this.settings.dynamic) {
|
|
27
|
+
this._updateFromTo = this._updateDynamic.bind(this)
|
|
28
|
+
} else {
|
|
29
|
+
this._lerps = []
|
|
30
|
+
this._createLerps(this.target, this._from, this._to)
|
|
31
|
+
this._updateFromTo = this._updateStatic.bind(this)
|
|
32
|
+
}
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
36
|
_createState(origPattern = {}, origSource = {}, origTarget = {}) {
|
|
37
37
|
const _copyProperty = (pattern, source, target) => {
|
|
38
38
|
for (const key in pattern) {
|
|
@@ -67,7 +67,7 @@ export class FromTo extends Core {
|
|
|
67
67
|
this._updateFromTo()
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
_updateStatic() {
|
|
71
71
|
this._lerps.forEach(lerpStep => lerpStep(this.easeValue))
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -103,6 +103,4 @@ export class FromTo extends Core {
|
|
|
103
103
|
|
|
104
104
|
return this
|
|
105
105
|
}
|
|
106
|
-
|
|
107
106
|
}
|
|
108
|
-
|
package/src/core/Core.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { Notifier } from "../emitter/Notifier"
|
|
2
|
+
import { DEFAULTS } from "./defaults"
|
|
3
|
+
import { EVENTS } from "./events"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Core - Main class for managing animations.
|
|
7
|
+
*
|
|
8
|
+
* @class Core
|
|
9
|
+
* @param {Object} [overrides={}] - Object for overriding default settings.
|
|
10
|
+
* @param {Object} [EmitterClass=null] - Class for managing animation events.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const animation = new Core({ time: 1000, easing: easings.inOutQuad });
|
|
14
|
+
* animation.play();
|
|
15
|
+
*
|
|
16
|
+
* @property {Object} DEFAULTS - Default settings.
|
|
17
|
+
* @property {number} DEFAULTS.time - Animation duration.
|
|
18
|
+
* @property {boolean} DEFAULTS.loop - Flag for infinite looping.
|
|
19
|
+
* @property {function} DEFAULTS.easing - Function for controlling the animation effect.
|
|
20
|
+
* @property {boolean} DEFAULTS.reversed - Flag for playing the animation in reverse.
|
|
21
|
+
* @property {number} DEFAULTS.repeat - Number of animation repetitions.
|
|
22
|
+
* @property {number} DEFAULTS.delay - Delay before the animation starts.
|
|
23
|
+
*
|
|
24
|
+
* @property {Object} settings - Current animation settings.
|
|
25
|
+
* @property {number} progress - Current animation progress.
|
|
26
|
+
* @property {boolean} reversed - Indicates if the animation is played in reverse.
|
|
27
|
+
*/
|
|
28
|
+
export class Core {
|
|
29
|
+
static DEFAULTS = DEFAULTS
|
|
30
|
+
|
|
31
|
+
static _noop() { }
|
|
32
|
+
|
|
33
|
+
static mergeConfigs(target, source) {
|
|
34
|
+
for (let key in source) {
|
|
35
|
+
if (typeof source[key] === 'object' && source[key] !== null) {
|
|
36
|
+
if (!target[key] || typeof target[key] !== 'object') {
|
|
37
|
+
target[key] = {}
|
|
38
|
+
}
|
|
39
|
+
Core.mergeConfigs(target[key], source[key])
|
|
40
|
+
} else {
|
|
41
|
+
target[key] = source[key]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
constructor(overrides = {}, EmitterClass = Notifier) {
|
|
47
|
+
this.settings = {}
|
|
48
|
+
this._emitter = EmitterClass ? new EmitterClass() : null
|
|
49
|
+
this.reset(overrides)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Sets internal animation states.
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
_processState() {
|
|
57
|
+
this.step = Core._noop
|
|
58
|
+
this.progress = 0
|
|
59
|
+
this.easeValue = 0
|
|
60
|
+
this.elapsedTime = 0
|
|
61
|
+
this.promise = null
|
|
62
|
+
this._resolve = Core._noop
|
|
63
|
+
this._refreshDynamicProps()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Updates dynamic animation states.
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
_refreshDynamicProps() {
|
|
71
|
+
this.target = this.settings.target
|
|
72
|
+
|
|
73
|
+
this.time = Math.max(this.settings.time, 0)
|
|
74
|
+
this.repeat = this.settings.repeat > 0 ? this.settings.repeat : this.settings.loop ? Infinity : 0
|
|
75
|
+
this.reversed = this.settings.reversed
|
|
76
|
+
this.remainingDelay = this.settings.delay
|
|
77
|
+
|
|
78
|
+
this._emitEvent = this._emitEvent || Core._noop
|
|
79
|
+
this._emitUpdate = this._emitUpdate || Core._noop
|
|
80
|
+
|
|
81
|
+
this._processEasing()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets the easing function for the animation.
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
_processEasing() {
|
|
89
|
+
this._easing = this.reversed ? this._reversedEasing : this.settings.easing
|
|
90
|
+
this._calculateEasing = this.settings.mode ? this.settings.mode.bind(this) : this._easing
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculates the reversed easing value.
|
|
95
|
+
* @param {number} t - Progress time.
|
|
96
|
+
* @returns {number} - Easing result for the reversed direction.
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
_reversedEasing(t) {
|
|
100
|
+
return this.settings.easing(1 - t)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Updates animation progress and triggers the update event.
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
_update() {
|
|
108
|
+
this.progress = this.elapsedTime / this.time
|
|
109
|
+
this.easeValue = this._calculateEasing(this.progress)
|
|
110
|
+
this._emitUpdate(EVENTS.UPDATE, { progress: this.progress, ease: this.easeValue })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Handles the delay before the animation starts.
|
|
115
|
+
* @param {number} deltaTime - Time passed since the last step.
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
_stepDelay(deltaTime) {
|
|
119
|
+
this.remainingDelay -= deltaTime
|
|
120
|
+
|
|
121
|
+
if (this.remainingDelay > 0) return
|
|
122
|
+
|
|
123
|
+
this.step = this._stepTime
|
|
124
|
+
this.step(Math.abs(this.remainingDelay))
|
|
125
|
+
|
|
126
|
+
this.remainingDelay = this.settings.delay
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handles animation time.
|
|
131
|
+
* @param {number} [deltaTime=0] - Time passed since the last step.
|
|
132
|
+
* @private
|
|
133
|
+
*/
|
|
134
|
+
_stepTime(deltaTime = 0) {
|
|
135
|
+
this.elapsedTime += deltaTime
|
|
136
|
+
|
|
137
|
+
if (this.elapsedTime >= this.time) {
|
|
138
|
+
this.elapsedTime = this.time
|
|
139
|
+
this._update()
|
|
140
|
+
this._complete()
|
|
141
|
+
} else {
|
|
142
|
+
this._update()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Called when the animation is complete.
|
|
148
|
+
* @private
|
|
149
|
+
*/
|
|
150
|
+
_complete() {
|
|
151
|
+
if (this.repeat-- > 0) {
|
|
152
|
+
this._repeat()
|
|
153
|
+
} else {
|
|
154
|
+
this._resolve()
|
|
155
|
+
this.stop(false)
|
|
156
|
+
this._emitEvent(EVENTS.COMPLETE)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Repeats the animation if repetitions are set.
|
|
162
|
+
* @param {boolean} [withEvent=true] - Flag for triggering the repeat event.
|
|
163
|
+
* @private
|
|
164
|
+
*/
|
|
165
|
+
_repeat(withEvent = true) {
|
|
166
|
+
this.remainingDelay = this.settings.repeatDelay
|
|
167
|
+
this.elapsedTime = 0
|
|
168
|
+
|
|
169
|
+
if (this.remainingDelay > 0) {
|
|
170
|
+
this.step = this._stepDelay
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this._update()
|
|
174
|
+
withEvent && this._emitEvent(EVENTS.REPEAT)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Logs a warning if the event emitter is not defined.
|
|
179
|
+
* @returns {Core} The current instance for chaining.
|
|
180
|
+
* @private
|
|
181
|
+
*/
|
|
182
|
+
_noEmitter() {
|
|
183
|
+
console.warn('Event emitter is not defined')
|
|
184
|
+
return this
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Subscribes a handler to the specified event.
|
|
189
|
+
* @param {string} event - Event name.
|
|
190
|
+
* @param {function} handler - Event handler.
|
|
191
|
+
* @param {boolean} [once=false] - Flag for one-time subscription.
|
|
192
|
+
* @returns {Core} The current instance for chaining.
|
|
193
|
+
*/
|
|
194
|
+
on(event, handler, once = false) {
|
|
195
|
+
if (!this._emitter) return this._noEmitter()
|
|
196
|
+
|
|
197
|
+
if (event === EVENTS.UPDATE) {
|
|
198
|
+
this._emitUpdate = this._emitter.emit(event, handler)
|
|
199
|
+
} else {
|
|
200
|
+
this._emit = this._emitter.emit(event, handler)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
once
|
|
204
|
+
? this._emitter.once(event, handler)
|
|
205
|
+
: this._emitter.on(event, handler)
|
|
206
|
+
|
|
207
|
+
return this
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Subscribes a handler to the specified event once.
|
|
212
|
+
* @param {string} event - Event name.
|
|
213
|
+
* @param {function} handler - Event handler.
|
|
214
|
+
* @returns {Core} The current instance for chaining.
|
|
215
|
+
*/
|
|
216
|
+
once(event, handler) {
|
|
217
|
+
return this.on(event, handler, true)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Unsubscribes a handler from the specified event.
|
|
222
|
+
* @param {string} event - Event name.
|
|
223
|
+
* @param {function} handler - Event handler.
|
|
224
|
+
* @returns {Core} The current instance for chaining.
|
|
225
|
+
*/
|
|
226
|
+
off(event, handler) {
|
|
227
|
+
if (!this._emitter) return this._noEmitter()
|
|
228
|
+
|
|
229
|
+
this._emitter.off(event, handler)
|
|
230
|
+
return this
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Removes all handlers for the specified event.
|
|
235
|
+
* @param {string} event - Event name.
|
|
236
|
+
* @returns {Core} The current instance for chaining.
|
|
237
|
+
*/
|
|
238
|
+
removeEvents(event) {
|
|
239
|
+
if (!this._emitter) return this._noEmitter()
|
|
240
|
+
|
|
241
|
+
this._emitEvent = Core._noop
|
|
242
|
+
this._emitUpdate = Core._noop
|
|
243
|
+
|
|
244
|
+
this._emitter.removeAllListeners(event)
|
|
245
|
+
return this
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Resets settings to default values.
|
|
250
|
+
* @param {Object} [newSettings=Core.DEFAULTS] - New settings.
|
|
251
|
+
* @returns {Core} The current instance for chaining.
|
|
252
|
+
*/
|
|
253
|
+
reset(newSettings = this.constructor.DEFAULTS) {
|
|
254
|
+
this.settings = { ...this.constructor.DEFAULTS, ...newSettings }
|
|
255
|
+
this._processState()
|
|
256
|
+
return this
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Modifies the current animation settings.
|
|
261
|
+
* @param {Object} [newSettings={}] - New settings.
|
|
262
|
+
* @returns {Core} The current instance for chaining.
|
|
263
|
+
*/
|
|
264
|
+
tweak(newSettings = {}) {
|
|
265
|
+
Core.mergeConfigs(this.settings, newSettings)
|
|
266
|
+
this._refreshDynamicProps()
|
|
267
|
+
return this
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Sets the animation time.
|
|
272
|
+
* @param {number} [time=0] - Time to set.
|
|
273
|
+
* @returns {Core} The current instance for chaining.
|
|
274
|
+
*/
|
|
275
|
+
seek(time = 0, callUpdate = true) {
|
|
276
|
+
this.elapsedTime = Math.min(Math.max(time, 0), this.settings.time)
|
|
277
|
+
callUpdate && this._update()
|
|
278
|
+
return this
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Sets the animation progress.
|
|
283
|
+
* @param {number} [progress=0] - Animation progress (from 0 to 1).
|
|
284
|
+
* @returns {Core} The current instance for chaining.
|
|
285
|
+
*/
|
|
286
|
+
setProgress(progress = 0, callUpdate = true) {
|
|
287
|
+
this.seek(this.settings.time * progress, callUpdate)
|
|
288
|
+
return this
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Toggles the animation direction (forward/reverse).
|
|
293
|
+
* @returns {Core} The current instance for chaining.
|
|
294
|
+
*/
|
|
295
|
+
reverse(callUpdate = false) {
|
|
296
|
+
this.reversed = !this.reversed
|
|
297
|
+
this.seek(this.settings.time - this.elapsedTime, callUpdate)
|
|
298
|
+
this._processEasing()
|
|
299
|
+
|
|
300
|
+
return this
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Starts the animation.
|
|
305
|
+
* @param {boolean} [withEvent=true] - Flag for triggering the play event.
|
|
306
|
+
* @returns {Core} The current instance for chaining.
|
|
307
|
+
*/
|
|
308
|
+
play(withEvent = true) {
|
|
309
|
+
if (this.isPlaying) return this
|
|
310
|
+
|
|
311
|
+
withEvent && this._emitEvent(EVENTS.PLAY)
|
|
312
|
+
if (this.remainingDelay > 0) {
|
|
313
|
+
this.step = this._stepDelay
|
|
314
|
+
} else {
|
|
315
|
+
this.step = this._stepTime
|
|
316
|
+
this._emitEvent(EVENTS.BEGIN)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return this
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Starts the animation and returns a Promise.
|
|
324
|
+
* @param {Promise} [promise=this.promise] - Promise object to resolve on completion.
|
|
325
|
+
* @returns {Promise} - A promise that resolves when the animation is complete.
|
|
326
|
+
*/
|
|
327
|
+
playPromise(promise = this.promise) {
|
|
328
|
+
this.play()
|
|
329
|
+
this.promise = promise || new Promise(resolve => this._resolve = resolve)
|
|
330
|
+
return this.promise
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Stops the animation.
|
|
335
|
+
* @param {boolean} [withEvent=true] - Flag for triggering the stop event.
|
|
336
|
+
* @returns {Core} The current instance for chaining.
|
|
337
|
+
*/
|
|
338
|
+
stop(withEvent = true) {
|
|
339
|
+
this._processState()
|
|
340
|
+
withEvent && this._emitEvent(EVENTS.STOP)
|
|
341
|
+
return this
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Pauses the animation.
|
|
346
|
+
* @returns {Core} The current instance for chaining.
|
|
347
|
+
*/
|
|
348
|
+
pause() {
|
|
349
|
+
this.step = Core._noop
|
|
350
|
+
this._emitEvent(EVENTS.PAUSE)
|
|
351
|
+
return this
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Restarts the animation from the beginning.
|
|
356
|
+
* @param {boolean} [withEvent=true] - Flag for triggering the replay event.
|
|
357
|
+
* @returns {Core} The current instance for chaining.
|
|
358
|
+
*/
|
|
359
|
+
replay(withEvent = true) {
|
|
360
|
+
this.stop(withEvent)
|
|
361
|
+
this.play(withEvent)
|
|
362
|
+
return this
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Returns whether the animation is currently playing.
|
|
367
|
+
* @returns {boolean} - true if the animation is playing, otherwise false.
|
|
368
|
+
*/
|
|
369
|
+
get isPlaying() {
|
|
370
|
+
return this.step !== Core._noop
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export class Notifier {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.events = {}
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Subscribe to an event
|
|
8
|
+
* @param {string} eventName - Name of the event
|
|
9
|
+
* @param {function} listener - Event handler function
|
|
10
|
+
*/
|
|
11
|
+
on(eventName, listener) {
|
|
12
|
+
if (!this.events[eventName]) {
|
|
13
|
+
this.events[eventName] = []
|
|
14
|
+
}
|
|
15
|
+
this.events[eventName].push(listener)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Unsubscribe from an event
|
|
20
|
+
* @param {string} eventName - Name of the event
|
|
21
|
+
* @param {function} listener - Event handler function
|
|
22
|
+
*/
|
|
23
|
+
off(eventName, listener) {
|
|
24
|
+
if (!this.events[eventName]) return
|
|
25
|
+
this.events[eventName] = this.events[eventName].filter(l => l !== listener)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Emit an event
|
|
30
|
+
* @param {string} eventName - Name of the event
|
|
31
|
+
* @param {...any} args - Arguments passed to the event handler
|
|
32
|
+
*/
|
|
33
|
+
emit(eventName, ...args) {
|
|
34
|
+
if (!this.events[eventName]) return
|
|
35
|
+
this.events[eventName].forEach(listener => listener(...args))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to an event once
|
|
40
|
+
* @param {string} eventName - Name of the event
|
|
41
|
+
* @param {function} listener - Event handler function
|
|
42
|
+
*/
|
|
43
|
+
once(eventName, listener) {
|
|
44
|
+
const onceListener = (...args) => {
|
|
45
|
+
this.off(eventName, onceListener)
|
|
46
|
+
listener(...args)
|
|
47
|
+
}
|
|
48
|
+
this.on(eventName, onceListener)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the list of event listeners
|
|
53
|
+
* @param {string} eventName - Name of the event
|
|
54
|
+
* @returns {function[]} List of event handlers
|
|
55
|
+
*/
|
|
56
|
+
getListeners(eventName) {
|
|
57
|
+
return this.events[eventName] || []
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the number of event listeners
|
|
62
|
+
* @param {string} eventName - Name of the event
|
|
63
|
+
* @returns {number} Number of event listeners
|
|
64
|
+
*/
|
|
65
|
+
getListenerCount(eventName) {
|
|
66
|
+
return this.events[eventName] ? this.events[eventName].length : 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Remove all listeners or listeners of a specific event type
|
|
71
|
+
* @param {string} [eventName] - Name of the event (optional)
|
|
72
|
+
*/
|
|
73
|
+
removeAllListeners(eventName) {
|
|
74
|
+
if (eventName) {
|
|
75
|
+
delete this.events[eventName]
|
|
76
|
+
} else {
|
|
77
|
+
this.events = {}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get the list of all event names
|
|
83
|
+
* @returns {string[]} List of event names
|
|
84
|
+
*/
|
|
85
|
+
getEventNames() {
|
|
86
|
+
return Object.keys(this.events)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if there are any listeners for an event
|
|
91
|
+
* @param {string} eventName - Name of the event
|
|
92
|
+
* @returns {boolean} True if there are listeners, otherwise False
|
|
93
|
+
*/
|
|
94
|
+
hasListeners(eventName) {
|
|
95
|
+
return this.getListenerCount(eventName) > 0
|
|
96
|
+
}
|
|
97
|
+
}
|
package/core/Core.js
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
import { Notifier } from "../emitter/Notifier"
|
|
2
|
-
import { DEFAULTS } from "../core/defaults"
|
|
3
|
-
import { EVENTS } from "../core/events"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Core - Основной класс для управления анимациями.
|
|
8
|
-
*
|
|
9
|
-
* @class Core
|
|
10
|
-
* @param {Object} [overrides={}] - Объект для переопределения настроек по умолчанию.
|
|
11
|
-
* @param {Object} [EmitterClass=null] - Класс для управления событиями анимации.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* const animation = new Core({ time: 1000, easing: easings.inOutQuad });
|
|
15
|
-
* animation.play();
|
|
16
|
-
*
|
|
17
|
-
* @property {Object} DEFAULTS - Настройки по умолчанию.
|
|
18
|
-
* @property {number} DEFAULTS.time - Время анимации.
|
|
19
|
-
* @property {boolean} DEFAULTS.loop - Флаг бесконечного повторения анимации.
|
|
20
|
-
* @property {function} DEFAULTS.easing - Функция для управления эффектом анимации.
|
|
21
|
-
* @property {boolean} DEFAULTS.reversed - Флаг обратного проигрывания анимации.
|
|
22
|
-
* @property {number} DEFAULTS.repeat - Количество повторений анимации.
|
|
23
|
-
* @property {number} DEFAULTS.delay - Задержка перед началом анимации.
|
|
24
|
-
*
|
|
25
|
-
* @property {Object} settings - Текущие настройки анимации.
|
|
26
|
-
* @property {number} progress - Текущий прогресс анимации.
|
|
27
|
-
* @property {boolean} reversed - Указывает, проигрывается ли анимация в обратном направлении.
|
|
28
|
-
*/
|
|
29
|
-
export class Core {
|
|
30
|
-
static DEFAULTS = DEFAULTS
|
|
31
|
-
|
|
32
|
-
static _noop() { }
|
|
33
|
-
|
|
34
|
-
static mergeConfigs(target, source) {
|
|
35
|
-
for (let key in source) {
|
|
36
|
-
if (typeof source[key] === 'object' && source[key] !== null) {
|
|
37
|
-
if (!target[key] || typeof target[key] !== 'object') {
|
|
38
|
-
target[key] = {}
|
|
39
|
-
}
|
|
40
|
-
Core.mergeConfigs(target[key], source[key])
|
|
41
|
-
} else {
|
|
42
|
-
target[key] = source[key]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
constructor(overrides = {}, EmitterClass = Notifier) {
|
|
48
|
-
this.settings = {}
|
|
49
|
-
this._emitter = EmitterClass ? new EmitterClass() : null
|
|
50
|
-
this.reset(overrides)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Устанавливает внутренние состояния анимации.
|
|
55
|
-
* @private
|
|
56
|
-
*/
|
|
57
|
-
_processState() {
|
|
58
|
-
this.step = Core._noop
|
|
59
|
-
this.progress = 0
|
|
60
|
-
this.easeValue = 0
|
|
61
|
-
this.elapsedTime = 0
|
|
62
|
-
this.promise = null
|
|
63
|
-
this._resolve = Core._noop
|
|
64
|
-
this._refreshDynamicProps()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Обновляет динамические состояния анимации.
|
|
69
|
-
* @private
|
|
70
|
-
*/
|
|
71
|
-
_refreshDynamicProps() {
|
|
72
|
-
this.time = Math.max(this.settings.time, 0)
|
|
73
|
-
this.repeat = this.settings.repeat > 0 ? this.settings.repeat : this.settings.loop ? Infinity : 0
|
|
74
|
-
this.reversed = this.settings.reversed
|
|
75
|
-
this.remainingDelay = this.settings.delay
|
|
76
|
-
|
|
77
|
-
this._emitEvent = this._emitEvent || Core._noop
|
|
78
|
-
this._emitUpdate = this._emitUpdate || Core._noop
|
|
79
|
-
|
|
80
|
-
this._processEasing()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Устанавливает функцию easing для анимации.
|
|
85
|
-
* @private
|
|
86
|
-
*/
|
|
87
|
-
_processEasing() {
|
|
88
|
-
this._easing = this.reversed ? this._reversedEasing : this.settings.easing
|
|
89
|
-
this._calculateEasing = this.settings.mode ? this.settings.mode.bind(this) : this._easing
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Рассчитывает обратное значение easing.
|
|
94
|
-
* @param {number} t - Время прогресса.
|
|
95
|
-
* @returns {number} - Результат функции easing для обратного направления.
|
|
96
|
-
* @private
|
|
97
|
-
*/
|
|
98
|
-
_reversedEasing(t) {
|
|
99
|
-
return this.settings.easing(1 - t)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Обновляет прогресс анимации и вызывает событие обновления.
|
|
104
|
-
* @private
|
|
105
|
-
*/
|
|
106
|
-
_update() {
|
|
107
|
-
this.progress = this.elapsedTime / this.time
|
|
108
|
-
this.easeValue = this._calculateEasing(this.progress)
|
|
109
|
-
this._emitUpdate(EVENTS.UPDATE, { progress: this.progress, ease: this.easeValue })
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Обрабатывает задержку перед началом анимации.
|
|
114
|
-
* @param {number} deltaTime - Время, прошедшее с предыдущего шага.
|
|
115
|
-
* @private
|
|
116
|
-
*/
|
|
117
|
-
_stepDelay(deltaTime) {
|
|
118
|
-
this.remainingDelay -= deltaTime
|
|
119
|
-
|
|
120
|
-
if (this.remainingDelay > 0) return
|
|
121
|
-
|
|
122
|
-
this.step = this._stepTime
|
|
123
|
-
this.step(Math.abs(this.remainingDelay))
|
|
124
|
-
|
|
125
|
-
this.remainingDelay = this.settings.delay
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Обрабатывает время анимации.
|
|
130
|
-
* @param {number} [deltaTime=0] - Время, прошедшее с предыдущего шага.
|
|
131
|
-
* @private
|
|
132
|
-
*/
|
|
133
|
-
_stepTime(deltaTime = 0) {
|
|
134
|
-
this.elapsedTime += deltaTime
|
|
135
|
-
|
|
136
|
-
if (this.elapsedTime >= this.time) {
|
|
137
|
-
this.elapsedTime = this.time
|
|
138
|
-
this._update()
|
|
139
|
-
this._complete()
|
|
140
|
-
} else {
|
|
141
|
-
this._update()
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Вызывается при завершении анимации.
|
|
147
|
-
* @private
|
|
148
|
-
*/
|
|
149
|
-
_complete() {
|
|
150
|
-
if (this.repeat-- > 0) {
|
|
151
|
-
this._repeat()
|
|
152
|
-
} else {
|
|
153
|
-
this._resolve()
|
|
154
|
-
this.stop(false)
|
|
155
|
-
this._emitEvent(EVENTS.COMPLETE)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Повторяет анимацию, если установлены повторения.
|
|
161
|
-
* @param {boolean} [withEvent=true] - Флаг для вызова события повторения.
|
|
162
|
-
* @private
|
|
163
|
-
*/
|
|
164
|
-
_repeat(withEvent = true) {
|
|
165
|
-
this.remainingDelay = this.settings.repeatDelay
|
|
166
|
-
this.elapsedTime = 0
|
|
167
|
-
|
|
168
|
-
if (this.remainingDelay > 0) {
|
|
169
|
-
this.step = this._stepDelay
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
this._update()
|
|
173
|
-
withEvent && this._emitEvent(EVENTS.REPEAT)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Логирует предупреждение, если эмиттер событий не определен.
|
|
178
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
179
|
-
* @private
|
|
180
|
-
*/
|
|
181
|
-
_noEmitter() {
|
|
182
|
-
console.warn('Event emitter is not defined')
|
|
183
|
-
return this
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Подписывает обработчик на указанное событие.
|
|
188
|
-
* @param {string} event - Имя события.
|
|
189
|
-
* @param {function} handler - Обработчик события.
|
|
190
|
-
* @param {boolean} [once=false] - Флаг для одноразовой подписки.
|
|
191
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
192
|
-
*/
|
|
193
|
-
on(event, handler, once = false) {
|
|
194
|
-
if (!this._emitter) return this._noEmitter()
|
|
195
|
-
|
|
196
|
-
if (event === EVENTS.UPDATE) {
|
|
197
|
-
this._emitUpdate = this._emitter.emit(event, handler)
|
|
198
|
-
} else {
|
|
199
|
-
this._emit = this._emitter.emit(event, handler)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
once
|
|
203
|
-
? this._emitter.once(event, handler)
|
|
204
|
-
: this._emitter.on(event, handler)
|
|
205
|
-
|
|
206
|
-
return this
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Подписывает одноразовый обработчик на указанное событие.
|
|
211
|
-
* @param {string} event - Имя события.
|
|
212
|
-
* @param {function} handler - Обработчик события.
|
|
213
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
214
|
-
*/
|
|
215
|
-
once(event, handler) {
|
|
216
|
-
return this.on(event, handler, true)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Отписывает обработчик от указанного события.
|
|
221
|
-
* @param {string} event - Имя события.
|
|
222
|
-
* @param {function} handler - Обработчик события.
|
|
223
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
224
|
-
*/
|
|
225
|
-
off(event, handler) {
|
|
226
|
-
if (!this._emitter) return this._noEmitter()
|
|
227
|
-
|
|
228
|
-
this._emitter.off(event, handler)
|
|
229
|
-
return this
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Удаляет все обработчики для указанного события.
|
|
234
|
-
* @param {string} event - Имя события.
|
|
235
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
236
|
-
*/
|
|
237
|
-
removeEvents(event) {
|
|
238
|
-
if (!this._emitter) return this._noEmitter()
|
|
239
|
-
|
|
240
|
-
this._emitEvent = Core._noop
|
|
241
|
-
this._emitUpdate = Core._noop
|
|
242
|
-
|
|
243
|
-
this._emitter.removeAllListeners(event)
|
|
244
|
-
return this
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Сбрасывает настройки к значениям по умолчанию.
|
|
249
|
-
* @param {Object} [newSettings=Core.DEFAULTS] - Новые настройки.
|
|
250
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
251
|
-
*/
|
|
252
|
-
reset(newSettings = this.constructor.DEFAULTS) {
|
|
253
|
-
this.settings = { ...this.constructor.DEFAULTS, ...newSettings }
|
|
254
|
-
this._processState()
|
|
255
|
-
return this
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Изменяет текущие настройки анимации.
|
|
260
|
-
* @param {Object} [newSettings={}] - Новые настройки.
|
|
261
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
262
|
-
*/
|
|
263
|
-
tweak(newSettings = {}) {
|
|
264
|
-
Core.mergeConfigs(this.settings, newSettings)
|
|
265
|
-
this._refreshDynamicProps()
|
|
266
|
-
return this
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Устанавливает время анимации.
|
|
271
|
-
* @param {number} [time=0] - Время для установки.
|
|
272
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
273
|
-
*/
|
|
274
|
-
seek(time = 0, callUpdate = true) {
|
|
275
|
-
this.elapsedTime = Math.min(Math.max(time, 0), this.settings.time)
|
|
276
|
-
callUpdate && this._update()
|
|
277
|
-
return this
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Устанавливает прогресс анимации.
|
|
282
|
-
* @param {number} [progress=0] - Прогресс анимации (от 0 до 1).
|
|
283
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
284
|
-
*/
|
|
285
|
-
setProgress(progress = 0, callUpdate = true) {
|
|
286
|
-
this.seek(this.settings.time * progress, callUpdate)
|
|
287
|
-
return this
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Меняет направление анимации (прямое/обратное).
|
|
292
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
293
|
-
*/
|
|
294
|
-
reverse(callUpdate = false) {
|
|
295
|
-
this.reversed = !this.reversed
|
|
296
|
-
this.seek(this.settings.time - this.elapsedTime, callUpdate)
|
|
297
|
-
this._processEasing()
|
|
298
|
-
|
|
299
|
-
return this
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Запускает анимацию.
|
|
304
|
-
* @param {boolean} [withEvent=true] - Флаг для вызова события при запуске.
|
|
305
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
306
|
-
*/
|
|
307
|
-
play(withEvent = true) {
|
|
308
|
-
if (this.isPlaying) return this
|
|
309
|
-
|
|
310
|
-
withEvent && this._emitEvent(EVENTS.PLAY)
|
|
311
|
-
if (this.remainingDelay > 0) {
|
|
312
|
-
this.step = this._stepDelay
|
|
313
|
-
} else {
|
|
314
|
-
this.step = this._stepTime
|
|
315
|
-
this._emitEvent(EVENTS.BEGIN)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return this
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Запускает анимацию с возвращением Promise.
|
|
323
|
-
* @param {Promise} [promise=this.promise] - Объект Promise для разрешения по завершению.
|
|
324
|
-
* @returns {Promise} - Promise, который разрешится при завершении анимации.
|
|
325
|
-
*/
|
|
326
|
-
playPromise(promise = this.promise) {
|
|
327
|
-
this.play()
|
|
328
|
-
this.promise = promise || new Promise(resolve => this._resolve = resolve)
|
|
329
|
-
return this.promise
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Останавливает анимацию.
|
|
334
|
-
* @param {boolean} [withEvent=true] - Флаг для вызова события при остановке.
|
|
335
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
336
|
-
*/
|
|
337
|
-
stop(withEvent = true) {
|
|
338
|
-
this._processState()
|
|
339
|
-
withEvent && this._emitEvent(EVENTS.STOP)
|
|
340
|
-
return this
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Приостанавливает анимацию.
|
|
345
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
346
|
-
*/
|
|
347
|
-
pause() {
|
|
348
|
-
this.step = Core._noop
|
|
349
|
-
this._emitEvent(EVENTS.PAUSE)
|
|
350
|
-
return this
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Повторно запускает анимацию с начального состояния.
|
|
355
|
-
* @param {boolean} [withEvent=true] - Флаг для вызова события при перезапуске.
|
|
356
|
-
* @returns {Core} Текущий экземпляр для цепочек вызовов.
|
|
357
|
-
*/
|
|
358
|
-
replay(withEvent = true) {
|
|
359
|
-
this.stop(withEvent)
|
|
360
|
-
this.play(withEvent)
|
|
361
|
-
return this
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Возвращает, активна ли анимация.
|
|
366
|
-
* @returns {boolean} - true, если анимация воспроизводится, иначе false.
|
|
367
|
-
*/
|
|
368
|
-
get isPlaying() {
|
|
369
|
-
return this.step !== Core._noop
|
|
370
|
-
}
|
|
371
|
-
}
|
package/emitter/Notifier.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
export class Notifier {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.events = {}
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Подписка на событие
|
|
8
|
-
* @param {string} eventName - Название события
|
|
9
|
-
* @param {function} listener - Функция обработчик события
|
|
10
|
-
*/
|
|
11
|
-
on(eventName, listener) {
|
|
12
|
-
if (!this.events[eventName]) {
|
|
13
|
-
this.events[eventName] = []
|
|
14
|
-
}
|
|
15
|
-
this.events[eventName].push(listener)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Отписка от события
|
|
20
|
-
* @param {string} eventName - Название события
|
|
21
|
-
* @param {function} listener - Функция обработчик события
|
|
22
|
-
*/
|
|
23
|
-
off(eventName, listener) {
|
|
24
|
-
if (!this.events[eventName]) return
|
|
25
|
-
this.events[eventName] = this.events[eventName].filter(l => l !== listener)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Генерация события
|
|
30
|
-
* @param {string} eventName - Название события
|
|
31
|
-
* @param {...any} args - Аргументы, передаваемые обработчику события
|
|
32
|
-
*/
|
|
33
|
-
emit(eventName, ...args) {
|
|
34
|
-
if (!this.events[eventName]) return
|
|
35
|
-
this.events[eventName].forEach(listener => listener(...args))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Разовая подписка на событие
|
|
40
|
-
* @param {string} eventName - Название события
|
|
41
|
-
* @param {function} listener - Функция обработчик события
|
|
42
|
-
*/
|
|
43
|
-
once(eventName, listener) {
|
|
44
|
-
const onceListener = (...args) => {
|
|
45
|
-
this.off(eventName, onceListener)
|
|
46
|
-
listener(...args)
|
|
47
|
-
}
|
|
48
|
-
this.on(eventName, onceListener)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Получить список обработчиков события
|
|
53
|
-
* @param {string} eventName - Название события
|
|
54
|
-
* @returns {function[]} Список обработчиков
|
|
55
|
-
*/
|
|
56
|
-
getListeners(eventName) {
|
|
57
|
-
return this.events[eventName] || []
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Получить количество обработчиков события
|
|
62
|
-
* @param {string} eventName - Название события
|
|
63
|
-
* @returns {number} Количество обработчиков
|
|
64
|
-
*/
|
|
65
|
-
getListenerCount(eventName) {
|
|
66
|
-
return this.events[eventName] ? this.events[eventName].length : 0
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Удаление всех подписок или подписок определенного типа
|
|
71
|
-
* @param {string} [eventName] - Название события (опционально)
|
|
72
|
-
*/
|
|
73
|
-
removeAllListeners(eventName) {
|
|
74
|
-
if (eventName) {
|
|
75
|
-
delete this.events[eventName]
|
|
76
|
-
} else {
|
|
77
|
-
this.events = {}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Получить список всех событий
|
|
83
|
-
* @returns {string[]} Список названий событий
|
|
84
|
-
*/
|
|
85
|
-
getEventNames() {
|
|
86
|
-
return Object.keys(this.events)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Проверить наличие подписчиков на событие
|
|
91
|
-
* @param {string} eventName - Название события
|
|
92
|
-
* @returns {boolean} True, если есть подписчики, иначе False
|
|
93
|
-
*/
|
|
94
|
-
hasListeners(eventName) {
|
|
95
|
-
return this.getListenerCount(eventName) > 0
|
|
96
|
-
}
|
|
97
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|