canvasframework 0.6.1 → 0.6.3

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.
@@ -381,6 +381,7 @@ class CanvasFramework {
381
381
  };
382
382
  this._firstRenderDone = false;
383
383
  this._startupStartTime = startTime;
384
+ this._needsFullRender = true; // Flag de rendu global (remplace forEach sur scroll)
384
385
  // Dans le constructeur, après this.scrollFriction = 0.95;
385
386
  this.scrollFriction = 0.95;
386
387
 
@@ -613,23 +614,14 @@ class CanvasFramework {
613
614
  // ✅ AJOUTER: Mesurer le temps d'init
614
615
  const initTime = performance.now() - startTime;
615
616
 
616
- // ✅ AJOUTER: Stocker les métriques
617
- this.metrics = {
618
- initTime: initTime,
619
- firstRenderTime: null,
620
- firstInteractionTime: null,
621
- totalStartupTime: null
622
- };
617
+ // ✅ Mettre à jour les métriques (initialisées plus haut)
618
+ this.metrics.initTime = initTime;
623
619
 
624
620
  // ✅ AJOUTER: Logger si debug
625
621
  if (options.debug || options.showMetrics) {
626
622
  console.log(`⚡ Framework initialisé en ${initTime.toFixed(2)}ms`);
627
623
  }
628
624
 
629
- // ✅ AJOUTER: Marquer le premier rendu
630
- this._firstRenderDone = false;
631
- this._startupStartTime = startTime;
632
-
633
625
  // ✅ OPTIMISATION OPTION 5: Partition spatiale pour le culling (optionnel)
634
626
  if (this.optimizations.useSpatialPartitioning) {
635
627
  this._initSpatialPartitioning();
@@ -980,7 +972,10 @@ class CanvasFramework {
980
972
  `;
981
973
 
982
974
  const blob = new Blob([workerCode], { type: 'application/javascript' });
983
- return new Worker(URL.createObjectURL(blob));
975
+ const _scrollWorkerUrl = URL.createObjectURL(blob);
976
+ const _scrollWorker = new Worker(_scrollWorkerUrl);
977
+ URL.revokeObjectURL(_scrollWorkerUrl); // Libérer la mémoire du blob
978
+ return _scrollWorker;
984
979
  }
985
980
 
986
981
  /**
@@ -1004,12 +999,9 @@ class CanvasFramework {
1004
999
  this._cachedMaxScroll = payload.maxScroll;
1005
1000
  this._maxScrollDirty = false;
1006
1001
 
1002
+ // ✅ OPTIMISATION : Un simple flag au lieu d'un forEach sur tous les composants
1007
1003
  if (Math.abs(payload.scrollVelocity) > 0) {
1008
- this.components.forEach(comp => {
1009
- if (!this.isFixedComponent(comp)) {
1010
- this.markComponentDirty(comp);
1011
- }
1012
- });
1004
+ this._needsFullRender = true;
1013
1005
  }
1014
1006
 
1015
1007
  // ✅ AJOUTER: Continuer l'animation de retour si nécessaire
@@ -1197,6 +1189,13 @@ class CanvasFramework {
1197
1189
  const key = `${text}_${font}_${color}`;
1198
1190
 
1199
1191
  if (!this.textCache.has(key)) {
1192
+ // ✅ Limite LRU : éviter une croissance mémoire infinie
1193
+ if (this.textCache.size >= 500) {
1194
+ // Supprimer la première entrée (la plus ancienne)
1195
+ const firstKey = this.textCache.keys().next().value;
1196
+ this.textCache.delete(firstKey);
1197
+ }
1198
+
1200
1199
  // Rendu dans un canvas temporaire
1201
1200
  const temp = document.createElement('canvas');
1202
1201
  const tempCtx = temp.getContext('2d', {
@@ -1448,6 +1447,7 @@ class CanvasFramework {
1448
1447
  }
1449
1448
 
1450
1449
  // Marquer tous les composants comme sales pour forcer un redessin complet
1450
+ this._needsFullRender = true;
1451
1451
  this.components.forEach(comp => this.markComponentDirty(comp));
1452
1452
  }
1453
1453
  }
@@ -1477,6 +1477,14 @@ class CanvasFramework {
1477
1477
  logoImage.src = opts.logo;
1478
1478
  }
1479
1479
 
1480
+ // ✅ OPTIMISATION : Créer le gradient UNE seule fois avant la boucle
1481
+ let splashGradient = null;
1482
+ if (Array.isArray(opts.backgroundColor) && opts.backgroundColor.length >= 2) {
1483
+ splashGradient = this.ctx.createLinearGradient(0, 0, this.width, this.height);
1484
+ splashGradient.addColorStop(0, opts.backgroundColor[0]);
1485
+ splashGradient.addColorStop(1, opts.backgroundColor[1]);
1486
+ }
1487
+
1480
1488
  const animate = () => {
1481
1489
  const elapsed = performance.now() - startTime;
1482
1490
  const progress = Math.min(elapsed / opts.duration, 1);
@@ -1484,17 +1492,8 @@ class CanvasFramework {
1484
1492
  // Clear
1485
1493
  this.ctx.clearRect(0, 0, this.width, this.height);
1486
1494
 
1487
- // ✅ Background (gradient ou couleur unie)
1488
- if (Array.isArray(opts.backgroundColor) && opts.backgroundColor.length >= 2) {
1489
- // Gradient
1490
- const gradient = this.ctx.createLinearGradient(0, 0, this.width, this.height);
1491
- gradient.addColorStop(0, opts.backgroundColor[0]);
1492
- gradient.addColorStop(1, opts.backgroundColor[1]);
1493
- this.ctx.fillStyle = gradient;
1494
- } else {
1495
- // Couleur unie
1496
- this.ctx.fillStyle = opts.backgroundColor;
1497
- }
1495
+ // ✅ Background (gradient pré-calculé ou couleur unie)
1496
+ this.ctx.fillStyle = splashGradient || opts.backgroundColor;
1498
1497
  this.ctx.fillRect(0, 0, this.width, this.height);
1499
1498
 
1500
1499
  const centerX = this.width / 2;
@@ -1568,6 +1567,14 @@ class CanvasFramework {
1568
1567
  const duration = opts.fadeOutDuration;
1569
1568
  const startTime = performance.now();
1570
1569
 
1570
+ // ✅ OPTIMISATION : Créer le gradient UNE seule fois avant la boucle
1571
+ let fadeGradient = null;
1572
+ if (Array.isArray(opts.backgroundColor) && opts.backgroundColor.length >= 2) {
1573
+ fadeGradient = this.ctx.createLinearGradient(0, 0, this.width, this.height);
1574
+ fadeGradient.addColorStop(0, opts.backgroundColor[0]);
1575
+ fadeGradient.addColorStop(1, opts.backgroundColor[1]);
1576
+ }
1577
+
1571
1578
  const fade = () => {
1572
1579
  const elapsed = performance.now() - startTime;
1573
1580
  const progress = elapsed / duration;
@@ -1577,15 +1584,8 @@ class CanvasFramework {
1577
1584
  this.ctx.clearRect(0, 0, this.width, this.height);
1578
1585
  this.ctx.globalAlpha = alpha;
1579
1586
 
1580
- // Redessiner le background
1581
- if (Array.isArray(opts.backgroundColor) && opts.backgroundColor.length >= 2) {
1582
- const gradient = this.ctx.createLinearGradient(0, 0, this.width, this.height);
1583
- gradient.addColorStop(0, opts.backgroundColor[0]);
1584
- gradient.addColorStop(1, opts.backgroundColor[1]);
1585
- this.ctx.fillStyle = gradient;
1586
- } else {
1587
- this.ctx.fillStyle = opts.backgroundColor;
1588
- }
1587
+ // Redessiner le background avec le gradient pré-calculé
1588
+ this.ctx.fillStyle = fadeGradient || opts.backgroundColor;
1589
1589
  this.ctx.fillRect(0, 0, this.width, this.height);
1590
1590
 
1591
1591
  // Spinner pendant le fade
@@ -1603,17 +1603,18 @@ class CanvasFramework {
1603
1603
  requestAnimationFrame(fade);
1604
1604
  } else {
1605
1605
  this._splashFinished = true;
1606
- // ✅ AJOUTER : Réinitialiser complètement le contexte
1606
+ // ✅ Réinitialiser complètement le contexte
1607
1607
  this.ctx.clearRect(0, 0, this.width, this.height);
1608
1608
  this.ctx.globalAlpha = 1;
1609
- this.ctx.textAlign = 'start'; // ← IMPORTANT
1610
- this.ctx.textBaseline = 'alphabetic'; // ← IMPORTANT
1611
- this.ctx.font = '10px sans-serif'; // Valeur par défaut
1609
+ this.ctx.textAlign = 'start';
1610
+ this.ctx.textBaseline = 'alphabetic';
1611
+ this.ctx.font = '10px sans-serif';
1612
1612
  this.ctx.fillStyle = '#000000';
1613
1613
  this.ctx.strokeStyle = '#000000';
1614
1614
  this.ctx.lineWidth = 1;
1615
1615
  this.ctx.lineCap = 'butt';
1616
1616
  this.ctx.lineJoin = 'miter';
1617
+ this._needsFullRender = true;
1617
1618
  }
1618
1619
  };
1619
1620
 
@@ -1773,19 +1774,14 @@ class CanvasFramework {
1773
1774
  }
1774
1775
  }
1775
1776
 
1777
+ /**
1778
+ * Méthode réservée pour les futures personnalisations de contexte liées au thème.
1779
+ * L'ancienne implémentation modifiait Object.defineProperty sur le prototype global,
1780
+ * ce qui pouvait causer des effets de bord. Désactivée intentionnellement.
1781
+ */
1776
1782
  wrapContext(ctx, theme) {
1777
- const originalFillStyle = Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, 'fillStyle');
1778
- Object.defineProperty(ctx, 'fillStyle', {
1779
- set: (value) => {
1780
- // Si value est blanc/noir ou une couleur “neutre”, tu remplaces par theme
1781
- if (value === '#FFFFFF' || value === '#000000') {
1782
- originalFillStyle.set.call(ctx, value);
1783
- } else {
1784
- originalFillStyle.set.call(ctx, value);
1785
- }
1786
- },
1787
- get: () => originalFillStyle.get.call(ctx)
1788
- });
1783
+ // Intentionnellement vide : les deux branches produisaient le même effet.
1784
+ // À ré-implémenter proprement si un remplacement de couleur basé sur le thème est requis.
1789
1785
  }
1790
1786
 
1791
1787
  createCanvasWorker() {
@@ -1840,9 +1836,10 @@ class CanvasFramework {
1840
1836
 
1841
1837
  // Protège la boucle
1842
1838
  if (this.components && Array.isArray(this.components)) {
1839
+ this._needsFullRender = true;
1843
1840
  this.components.forEach(comp => comp.markDirty());
1844
1841
  } else {
1845
- console.warn('[setTheme] components pas encore initialisé');
1842
+ if (this.debbug) console.warn('[setTheme] components pas encore initialisé');
1846
1843
  }
1847
1844
  }
1848
1845
 
@@ -1957,13 +1954,22 @@ class CanvasFramework {
1957
1954
  }
1958
1955
 
1959
1956
  setupEventListeners() {
1960
- this.canvas.addEventListener('touchstart', this.handleTouchStart.bind(this));
1961
- this.canvas.addEventListener('touchmove', this.handleTouchMove.bind(this));
1962
- this.canvas.addEventListener('touchend', this.handleTouchEnd.bind(this));
1963
- this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
1964
- this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
1965
- this.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
1966
- window.addEventListener('resize', this.handleResize.bind(this));
1957
+ // Sauvegarder les références bound pour pouvoir les retirer dans destroy()
1958
+ this._boundHandleTouchStart = this.handleTouchStart.bind(this);
1959
+ this._boundHandleTouchMove = this.handleTouchMove.bind(this);
1960
+ this._boundHandleTouchEnd = this.handleTouchEnd.bind(this);
1961
+ this._boundHandleMouseDown = this.handleMouseDown.bind(this);
1962
+ this._boundHandleMouseMove = this.handleMouseMove.bind(this);
1963
+ this._boundHandleMouseUp = this.handleMouseUp.bind(this);
1964
+ this._boundHandleResize = this.handleResize.bind(this);
1965
+
1966
+ this.canvas.addEventListener('touchstart', this._boundHandleTouchStart, { passive: false });
1967
+ this.canvas.addEventListener('touchmove', this._boundHandleTouchMove, { passive: false });
1968
+ this.canvas.addEventListener('touchend', this._boundHandleTouchEnd, { passive: false });
1969
+ this.canvas.addEventListener('mousedown', this._boundHandleMouseDown);
1970
+ this.canvas.addEventListener('mousemove', this._boundHandleMouseMove);
1971
+ this.canvas.addEventListener('mouseup', this._boundHandleMouseUp);
1972
+ window.addEventListener('resize', this._boundHandleResize);
1967
1973
  }
1968
1974
 
1969
1975
  /**
@@ -2250,15 +2256,7 @@ class CanvasFramework {
2250
2256
  }
2251
2257
 
2252
2258
  this._maxScrollDirty = true;
2253
-
2254
- // Historique navigateur : seulement si ce n'est pas un retour interne
2255
- if (!options._internal) {
2256
- if (!replace) {
2257
- window.history.pushState({ route: path, _app: true }, '', path);
2258
- } else {
2259
- window.history.replaceState({ route: path, _app: true }, '', path);
2260
- }
2261
- }
2259
+ this._needsFullRender = true; // Forcer un rendu complet après navigation
2262
2260
  }
2263
2261
 
2264
2262
  // Alias pour usage interne (sans toucher window.history)
@@ -2754,16 +2752,6 @@ class CanvasFramework {
2754
2752
  return Math.max(0, maxY - this.height + 50);
2755
2753
  }
2756
2754
 
2757
- /*getMaxScroll() {
2758
- let maxY = 0;
2759
- for (let comp of this.components) {
2760
- if (!this.isFixedComponent(comp)) {
2761
- maxY = Math.max(maxY, comp.y + comp.height);
2762
- }
2763
- }
2764
- return Math.max(0, maxY - this.height + 50);
2765
- }*/
2766
-
2767
2755
  handleResize() {
2768
2756
  if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
2769
2757
 
@@ -2774,18 +2762,19 @@ class CanvasFramework {
2774
2762
  // Règle clé : on resize UNIQUEMENT si la largeur a vraiment changé
2775
2763
  // (rotation, split-screen, vraie fenêtre changée)
2776
2764
  // À la fermeture du clavier → largeur = identique → on skip
2777
- if (Math.abs(newWidth - this.width) <= 8) { // tolérance pixels (scrollbar, bordures...)
2778
- console.log("[resize] Largeur identique → probablement clavier (open/close) → ignoré");
2765
+ if (Math.abs(newWidth - this.width) <= 8) {
2766
+ if (this.debbug) console.log("[resize] Largeur identique → probablement clavier (open/close) → ignoré");
2779
2767
  return;
2780
2768
  }
2781
2769
 
2782
- console.log("[resize] Largeur changée → vrai resize");
2770
+ if (this.debbug) console.log("[resize] Largeur changée → vrai resize");
2783
2771
 
2784
2772
  this.width = newWidth;
2785
2773
  this.height = newHeight;
2786
2774
 
2787
2775
  this.setupCanvas(); // ← change canvas.width/height + DPR + style
2788
2776
  this._maxScrollDirty = true;
2777
+ this._needsFullRender = true; // Forcer un rendu complet après resize
2789
2778
 
2790
2779
  if (this.scrollWorker) {
2791
2780
  this.scrollWorker.postMessage({
@@ -2822,6 +2811,7 @@ class CanvasFramework {
2822
2811
  this.components.push(component);
2823
2812
  component._mount();
2824
2813
  this.updateScrollWorkerComponents();
2814
+ this._needsFullRender = true; // Forcer un rendu complet après ajout
2825
2815
  return component;
2826
2816
  }
2827
2817
 
@@ -2831,6 +2821,7 @@ class CanvasFramework {
2831
2821
  component._unmount();
2832
2822
  this.components.splice(index, 1);
2833
2823
  this.updateScrollWorkerComponents();
2824
+ this._needsFullRender = true; // Forcer un rendu complet après suppression
2834
2825
  }
2835
2826
  }
2836
2827
 
@@ -2839,10 +2830,12 @@ class CanvasFramework {
2839
2830
  // Vérifications basiques
2840
2831
  if (!component || !component.visible) return;
2841
2832
 
2842
- // ✅ TOUJOURS ajouter au set des composants sales
2843
- // Les conditions de rendu seront vérifiées dans _renderDirtyComponents
2844
2833
  if (this.optimizationEnabled) {
2834
+ // ✅ Ajouter au set des composants sales pour rendu partiel
2845
2835
  this.dirtyComponents.add(component);
2836
+ } else {
2837
+ // Sans optimisation : forcer un rendu complet
2838
+ this._needsFullRender = true;
2846
2839
  }
2847
2840
 
2848
2841
  // ✅ Optionnel : Marquer aussi le composant lui-même
@@ -3016,8 +3009,7 @@ class CanvasFramework {
3016
3009
  }
3017
3010
 
3018
3011
  startRenderLoop() {
3019
- let lastScrollOffset = this.scrollOffset;
3020
- this._needsRender = true; // ← AJOUTER
3012
+ this._needsRender = true;
3021
3013
 
3022
3014
  const render = () => {
3023
3015
  if (!this._splashFinished) {
@@ -3031,25 +3023,22 @@ class CanvasFramework {
3031
3023
 
3032
3024
  // Si une transition est en cours
3033
3025
  if (this.transitionState.isTransitioning) {
3034
- // Mettre à jour la progression
3035
3026
  const elapsed = Date.now() - this.transitionState.startTime;
3036
3027
  this.transitionState.progress = Math.min(elapsed / this.transitionState.duration, 1);
3037
3028
 
3038
- // Rendu spécial pour la transition
3039
3029
  this.renderSimpleTransition();
3040
3030
 
3041
- // Si la transition est terminée
3042
3031
  if (this.transitionState.progress >= 1) {
3043
3032
  this.transitionState.isTransitioning = false;
3044
3033
  this.transitionState.oldComponents = [];
3045
3034
  }
3046
- }
3047
- // Sinon, rendu normal
3035
+ }
3036
+ // Rendu normal — toujours dessiner pour garantir l'affichage
3048
3037
  else {
3049
3038
  this.renderFull();
3050
3039
  }
3051
3040
 
3052
- // Calcul FPS (optionnel)
3041
+ // Calcul FPS
3053
3042
  this._frames++;
3054
3043
  const now = performance.now();
3055
3044
  const elapsed = now - this._lastFpsTime;
@@ -3226,26 +3215,6 @@ class CanvasFramework {
3226
3215
  ctx.restore();
3227
3216
  }
3228
3217
 
3229
- /**
3230
- * Mettre à jour la progression de la transition
3231
- * @private
3232
- */
3233
- updateTransition() {
3234
- if (!this.transitionState.isTransitioning) return;
3235
-
3236
- const elapsed = Date.now() - this.transitionState.startTime;
3237
- this.transitionState.progress = Math.min(elapsed / this.transitionState.duration, 1);
3238
-
3239
- // Si la transition est terminée
3240
- if (this.transitionState.progress >= 1) {
3241
- this.transitionState.isTransitioning = false;
3242
-
3243
- // Marquer tous les nouveaux composants comme sales pour le prochain rendu
3244
- this.transitionState.newComponents.forEach(comp => {
3245
- this.markComponentDirty(comp);
3246
- });
3247
- }
3248
- }
3249
3218
 
3250
3219
  /**
3251
3220
  * Rendu complet normal (sans transition)
@@ -3368,14 +3337,18 @@ class CanvasFramework {
3368
3337
  this.ctx.destroy();
3369
3338
  }
3370
3339
 
3371
- // Nettoyer les écouteurs d'événements
3372
- this.canvas.removeEventListener('touchstart', this.handleTouchStart);
3373
- this.canvas.removeEventListener('touchmove', this.handleTouchMove);
3374
- this.canvas.removeEventListener('touchend', this.handleTouchEnd);
3375
- this.canvas.removeEventListener('mousedown', this.handleMouseDown);
3376
- this.canvas.removeEventListener('mousemove', this.handleMouseMove);
3377
- this.canvas.removeEventListener('mouseup', this.handleMouseUp);
3378
- window.removeEventListener('resize', this.handleResize);
3340
+ // Nettoyer les écouteurs d'événements (références bound sauvegardées)
3341
+ this.canvas.removeEventListener('touchstart', this._boundHandleTouchStart);
3342
+ this.canvas.removeEventListener('touchmove', this._boundHandleTouchMove);
3343
+ this.canvas.removeEventListener('touchend', this._boundHandleTouchEnd);
3344
+ this.canvas.removeEventListener('mousedown', this._boundHandleMouseDown);
3345
+ this.canvas.removeEventListener('mousemove', this._boundHandleMouseMove);
3346
+ this.canvas.removeEventListener('mouseup', this._boundHandleMouseUp);
3347
+ window.removeEventListener('resize', this._boundHandleResize);
3348
+
3349
+ // Vider le textCache pour libérer la mémoire
3350
+ if (this.textCache) this.textCache.clear();
3351
+ if (this.imageCache) this.imageCache.clear();
3379
3352
  }
3380
3353
 
3381
3354
  // ✅ AJOUTER: Afficher les métriques à l'écran