canvasframework 0.4.11 → 0.5.0
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/core/CanvasFramework.js +785 -250
- package/core/WebGLCanvasAdapter.js +747 -1400
- package/package.json +1 -1
- package/utils/AnimationEngine.js +314 -8
package/package.json
CHANGED
package/utils/AnimationEngine.js
CHANGED
|
@@ -10,6 +10,249 @@ class AnimationEngine {
|
|
|
10
10
|
this.animations = new Map();
|
|
11
11
|
this.isRunning = false;
|
|
12
12
|
this.animationFrameId = null;
|
|
13
|
+
|
|
14
|
+
// Batching
|
|
15
|
+
this.batchQueue = [];
|
|
16
|
+
this.batchScheduled = false;
|
|
17
|
+
|
|
18
|
+
// Worker pour les calculs d'animation
|
|
19
|
+
this.worker = null;
|
|
20
|
+
this.workerEnabled = true;
|
|
21
|
+
this.initWorker();
|
|
22
|
+
|
|
23
|
+
// OffscreenCanvas cache
|
|
24
|
+
this.offscreenCache = new Map();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initialise le Web Worker pour les animations
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
initWorker() {
|
|
32
|
+
if (!window.Worker) {
|
|
33
|
+
this.workerEnabled = false;
|
|
34
|
+
console.warn('Web Workers non supportés, fallback sur le thread principal');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Créer le worker inline
|
|
39
|
+
const workerCode = `
|
|
40
|
+
// Fonctions d'easing dans le Worker
|
|
41
|
+
const easings = {
|
|
42
|
+
linear: t => t,
|
|
43
|
+
easeInQuad: t => t * t,
|
|
44
|
+
easeOutQuad: t => t * (2 - t),
|
|
45
|
+
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
46
|
+
easeInCubic: t => t * t * t,
|
|
47
|
+
easeOutCubic: t => (--t) * t * t + 1,
|
|
48
|
+
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
|
|
49
|
+
easeInQuart: t => t * t * t * t,
|
|
50
|
+
easeOutQuart: t => 1 - (--t) * t * t * t,
|
|
51
|
+
easeInOutQuart: t => t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t,
|
|
52
|
+
easeInElastic: t => {
|
|
53
|
+
const c4 = (2 * Math.PI) / 3;
|
|
54
|
+
return t === 0 ? 0 : t === 1 ? 1 : -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
|
|
55
|
+
},
|
|
56
|
+
easeOutElastic: t => {
|
|
57
|
+
const c4 = (2 * Math.PI) / 3;
|
|
58
|
+
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
|
59
|
+
},
|
|
60
|
+
easeOutBounce: t => {
|
|
61
|
+
const n1 = 7.5625;
|
|
62
|
+
const d1 = 2.75;
|
|
63
|
+
if (t < 1 / d1) return n1 * t * t;
|
|
64
|
+
else if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
65
|
+
else if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
66
|
+
else return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Calculer les valeurs d'animation
|
|
71
|
+
self.onmessage = function(e) {
|
|
72
|
+
const { type, data } = e.data;
|
|
73
|
+
|
|
74
|
+
if (type === 'CALCULATE_BATCH') {
|
|
75
|
+
const { animations, currentTime } = data;
|
|
76
|
+
const results = [];
|
|
77
|
+
|
|
78
|
+
for (let anim of animations) {
|
|
79
|
+
// Gérer le délai
|
|
80
|
+
if (anim.isDelaying) {
|
|
81
|
+
if (!anim.delayStartTime) {
|
|
82
|
+
anim.delayStartTime = currentTime;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (currentTime - anim.delayStartTime >= anim.delay) {
|
|
86
|
+
anim.isDelaying = false;
|
|
87
|
+
anim.startTime = currentTime;
|
|
88
|
+
} else {
|
|
89
|
+
results.push({ id: anim.id, status: 'delaying', animation: anim });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Initialiser le temps de départ
|
|
95
|
+
if (!anim.startTime) {
|
|
96
|
+
anim.startTime = currentTime;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Calculer la progression
|
|
100
|
+
const elapsed = currentTime - anim.startTime;
|
|
101
|
+
let progress = Math.min(elapsed / anim.duration, 1);
|
|
102
|
+
|
|
103
|
+
// Appliquer l'easing
|
|
104
|
+
const easingFunc = easings[anim.easing] || easings.linear;
|
|
105
|
+
const easedProgress = easingFunc(progress);
|
|
106
|
+
|
|
107
|
+
// Inverser si yoyo
|
|
108
|
+
const actualProgress = anim.isReversed ? 1 - easedProgress : easedProgress;
|
|
109
|
+
|
|
110
|
+
// Calculer les nouvelles valeurs
|
|
111
|
+
const values = {};
|
|
112
|
+
for (let prop in anim.to) {
|
|
113
|
+
const from = anim.from[prop];
|
|
114
|
+
const to = anim.to[prop];
|
|
115
|
+
values[prop] = from + (to - from) * actualProgress;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Déterminer le statut
|
|
119
|
+
let status = 'running';
|
|
120
|
+
let newState = { ...anim };
|
|
121
|
+
|
|
122
|
+
if (progress >= 1) {
|
|
123
|
+
if (anim.yoyo && !anim.isReversed) {
|
|
124
|
+
status = 'yoyo';
|
|
125
|
+
newState.isReversed = true;
|
|
126
|
+
newState.startTime = currentTime;
|
|
127
|
+
} else if (anim.loop) {
|
|
128
|
+
status = 'loop';
|
|
129
|
+
newState.startTime = currentTime;
|
|
130
|
+
newState.isReversed = false;
|
|
131
|
+
} else {
|
|
132
|
+
status = 'complete';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
results.push({
|
|
137
|
+
id: anim.id,
|
|
138
|
+
status,
|
|
139
|
+
values,
|
|
140
|
+
actualProgress,
|
|
141
|
+
animation: newState
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
self.postMessage({ type: 'BATCH_RESULT', results });
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
`;
|
|
149
|
+
|
|
150
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
151
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
this.worker = new Worker(workerUrl);
|
|
155
|
+
|
|
156
|
+
this.worker.onmessage = (e) => {
|
|
157
|
+
if (e.data.type === 'BATCH_RESULT') {
|
|
158
|
+
this.applyBatchResults(e.data.results);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
this.worker.onerror = (error) => {
|
|
163
|
+
console.error('Erreur Worker:', error);
|
|
164
|
+
this.workerEnabled = false;
|
|
165
|
+
};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Impossible de créer le Worker:', error);
|
|
168
|
+
this.workerEnabled = false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Applique les résultats calculés par le Worker
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
applyBatchResults(results) {
|
|
177
|
+
const toDelete = [];
|
|
178
|
+
|
|
179
|
+
for (let result of results) {
|
|
180
|
+
const anim = this.animations.get(result.id);
|
|
181
|
+
if (!anim) continue;
|
|
182
|
+
|
|
183
|
+
if (result.status === 'delaying') {
|
|
184
|
+
// Mettre à jour l'état de délai
|
|
185
|
+
Object.assign(anim, result.animation);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Mettre à jour les propriétés du composant
|
|
190
|
+
if (result.values) {
|
|
191
|
+
for (let prop in result.values) {
|
|
192
|
+
anim.component[prop] = result.values[prop];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Callback onUpdate
|
|
197
|
+
if (anim.onUpdate) {
|
|
198
|
+
anim.onUpdate(result.actualProgress);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Gérer les états
|
|
202
|
+
if (result.status === 'yoyo' || result.status === 'loop') {
|
|
203
|
+
Object.assign(anim, result.animation);
|
|
204
|
+
} else if (result.status === 'complete') {
|
|
205
|
+
if (anim.onComplete) {
|
|
206
|
+
anim.onComplete();
|
|
207
|
+
}
|
|
208
|
+
toDelete.push(result.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Nettoyer les animations terminées
|
|
213
|
+
toDelete.forEach(id => this.stop(id));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Crée un OffscreenCanvas pour pré-rendre un composant
|
|
218
|
+
* @param {string} cacheKey - Clé de cache
|
|
219
|
+
* @param {number} width - Largeur
|
|
220
|
+
* @param {number} height - Hauteur
|
|
221
|
+
* @param {Function} renderFn - Fonction de rendu
|
|
222
|
+
*/
|
|
223
|
+
createOffscreenCache(cacheKey, width, height, renderFn) {
|
|
224
|
+
if (!window.OffscreenCanvas) {
|
|
225
|
+
console.warn('OffscreenCanvas non supporté');
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const offscreen = new OffscreenCanvas(width, height);
|
|
230
|
+
const ctx = offscreen.getContext('2d');
|
|
231
|
+
|
|
232
|
+
renderFn(ctx);
|
|
233
|
+
|
|
234
|
+
this.offscreenCache.set(cacheKey, offscreen);
|
|
235
|
+
return offscreen;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Récupère un OffscreenCanvas du cache
|
|
240
|
+
* @param {string} cacheKey - Clé de cache
|
|
241
|
+
*/
|
|
242
|
+
getOffscreenCache(cacheKey) {
|
|
243
|
+
return this.offscreenCache.get(cacheKey);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Nettoie le cache OffscreenCanvas
|
|
248
|
+
* @param {string} cacheKey - Clé de cache (optionnel)
|
|
249
|
+
*/
|
|
250
|
+
clearOffscreenCache(cacheKey = null) {
|
|
251
|
+
if (cacheKey) {
|
|
252
|
+
this.offscreenCache.delete(cacheKey);
|
|
253
|
+
} else {
|
|
254
|
+
this.offscreenCache.clear();
|
|
255
|
+
}
|
|
13
256
|
}
|
|
14
257
|
|
|
15
258
|
/**
|
|
@@ -283,11 +526,69 @@ class AnimationEngine {
|
|
|
283
526
|
}
|
|
284
527
|
|
|
285
528
|
/**
|
|
286
|
-
* Met à jour toutes les animations
|
|
529
|
+
* Met à jour toutes les animations (avec Worker et batching)
|
|
287
530
|
* @private
|
|
288
531
|
*/
|
|
289
532
|
update() {
|
|
290
533
|
const currentTime = performance.now();
|
|
534
|
+
|
|
535
|
+
if (this.workerEnabled && this.worker) {
|
|
536
|
+
// Utiliser le Worker pour les calculs
|
|
537
|
+
this.updateWithWorker(currentTime);
|
|
538
|
+
} else {
|
|
539
|
+
// Fallback sur le thread principal
|
|
540
|
+
this.updateOnMainThread(currentTime);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Continuer l'animation
|
|
544
|
+
if (this.animations.size > 0) {
|
|
545
|
+
this.animationFrameId = requestAnimationFrame(() => this.update());
|
|
546
|
+
} else {
|
|
547
|
+
this.isRunning = false;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Met à jour avec le Worker (batching automatique)
|
|
553
|
+
* @private
|
|
554
|
+
*/
|
|
555
|
+
updateWithWorker(currentTime) {
|
|
556
|
+
const animationsArray = [];
|
|
557
|
+
|
|
558
|
+
for (let [id, anim] of this.animations) {
|
|
559
|
+
// Préparer les données pour le Worker (sans les callbacks et le composant)
|
|
560
|
+
animationsArray.push({
|
|
561
|
+
id: anim.id,
|
|
562
|
+
from: anim.from,
|
|
563
|
+
to: anim.to,
|
|
564
|
+
duration: anim.duration,
|
|
565
|
+
easing: anim.easing,
|
|
566
|
+
delay: anim.delay,
|
|
567
|
+
loop: anim.loop,
|
|
568
|
+
yoyo: anim.yoyo,
|
|
569
|
+
startTime: anim.startTime,
|
|
570
|
+
delayStartTime: anim.delayStartTime,
|
|
571
|
+
isDelaying: anim.isDelaying,
|
|
572
|
+
isReversed: anim.isReversed
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (animationsArray.length > 0) {
|
|
577
|
+
this.worker.postMessage({
|
|
578
|
+
type: 'CALCULATE_BATCH',
|
|
579
|
+
data: {
|
|
580
|
+
animations: animationsArray,
|
|
581
|
+
currentTime: currentTime
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Met à jour sur le thread principal (fallback)
|
|
589
|
+
* @private
|
|
590
|
+
*/
|
|
591
|
+
updateOnMainThread(currentTime) {
|
|
291
592
|
const toDelete = [];
|
|
292
593
|
|
|
293
594
|
for (let [id, anim] of this.animations) {
|
|
@@ -354,13 +655,6 @@ class AnimationEngine {
|
|
|
354
655
|
|
|
355
656
|
// Nettoyer les animations terminées
|
|
356
657
|
toDelete.forEach(id => this.stop(id));
|
|
357
|
-
|
|
358
|
-
// Continuer l'animation
|
|
359
|
-
if (this.animations.size > 0) {
|
|
360
|
-
this.animationFrameId = requestAnimationFrame(() => this.update());
|
|
361
|
-
} else {
|
|
362
|
-
this.isRunning = false;
|
|
363
|
-
}
|
|
364
658
|
}
|
|
365
659
|
|
|
366
660
|
/**
|
|
@@ -422,6 +716,18 @@ class AnimationEngine {
|
|
|
422
716
|
cancelAnimationFrame(this.animationFrameId);
|
|
423
717
|
this.animationFrameId = null;
|
|
424
718
|
}
|
|
719
|
+
this.clearOffscreenCache();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Nettoie le Worker
|
|
724
|
+
*/
|
|
725
|
+
destroy() {
|
|
726
|
+
this.clear();
|
|
727
|
+
if (this.worker) {
|
|
728
|
+
this.worker.terminate();
|
|
729
|
+
this.worker = null;
|
|
730
|
+
}
|
|
425
731
|
}
|
|
426
732
|
}
|
|
427
733
|
|