canvasframework 0.5.57 → 0.5.58

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.
@@ -374,7 +374,12 @@ class CanvasFramework {
374
374
  };
375
375
  this._firstRenderDone = false;
376
376
  this._startupStartTime = startTime;
377
+ // Dans le constructeur, après this.scrollFriction = 0.95;
378
+ this.scrollFriction = 0.95;
377
379
 
380
+ // ✅ AJOUTER
381
+ this.overscrollDistance = 0;
382
+ this.isOverscrollAnimating = false;
378
383
  // ✅ Créer automatiquement le canvas
379
384
  this.canvas = document.createElement('canvas');
380
385
  this.canvas.id = canvasId || `canvas-${Date.now()}`;
@@ -620,6 +625,7 @@ class CanvasFramework {
620
625
  if (this.optimizations.useSpatialPartitioning) {
621
626
  this._initSpatialPartitioning();
622
627
  }
628
+
623
629
  }
624
630
 
625
631
  /**
@@ -688,7 +694,7 @@ class CanvasFramework {
688
694
  * Crée le Worker pour le calcul du scroll
689
695
  */
690
696
  createScrollWorker() {
691
- const workerCode = `
697
+ const workerCode = `
692
698
  let state = {
693
699
  scrollOffset: 0,
694
700
  scrollVelocity: 0,
@@ -697,7 +703,10 @@ class CanvasFramework {
697
703
  maxScroll: 0,
698
704
  height: 0,
699
705
  lastTouchY: 0,
700
- components: []
706
+ components: [],
707
+ overscrollDistance: 0,
708
+ maxOverscroll: 150, // ✅ Limite maximale
709
+ overscrollResistance: 0.3
701
710
  };
702
711
 
703
712
  const FIXED_COMPONENT_TYPES = [
@@ -708,13 +717,11 @@ class CanvasFramework {
708
717
 
709
718
  const calculateMaxScroll = () => {
710
719
  let maxY = 0;
711
-
712
720
  for (const comp of state.components) {
713
721
  if (FIXED_COMPONENT_TYPES.includes(comp.type) || !comp.visible) continue;
714
722
  const bottom = comp.y + comp.height;
715
723
  if (bottom > maxY) maxY = bottom;
716
724
  }
717
-
718
725
  return Math.max(0, maxY - state.height + 50);
719
726
  };
720
727
 
@@ -722,7 +729,29 @@ class CanvasFramework {
722
729
  if (Math.abs(state.scrollVelocity) > 0.1 && !state.isDragging) {
723
730
  state.scrollOffset += state.scrollVelocity;
724
731
  state.maxScroll = calculateMaxScroll();
725
- state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
732
+
733
+ // ✅ CORRIGER: Limiter l'overscroll pendant l'inertie
734
+ if (state.scrollOffset > 0) {
735
+ // Limiter à maxOverscroll
736
+ if (state.scrollOffset > state.maxOverscroll) {
737
+ state.scrollOffset = state.maxOverscroll;
738
+ state.scrollVelocity = 0;
739
+ }
740
+ state.overscrollDistance = state.scrollOffset;
741
+ state.scrollVelocity *= 0.85;
742
+ } else if (state.scrollOffset < -state.maxScroll) {
743
+ const excess = Math.abs(state.scrollOffset + state.maxScroll);
744
+ // Limiter à maxOverscroll
745
+ if (excess > state.maxOverscroll) {
746
+ state.scrollOffset = -state.maxScroll - state.maxOverscroll;
747
+ state.scrollVelocity = 0;
748
+ }
749
+ state.overscrollDistance = state.scrollOffset + state.maxScroll;
750
+ state.scrollVelocity *= 0.85;
751
+ } else {
752
+ state.overscrollDistance = 0;
753
+ }
754
+
726
755
  state.scrollVelocity *= state.scrollFriction;
727
756
  } else {
728
757
  state.scrollVelocity = 0;
@@ -731,20 +760,82 @@ class CanvasFramework {
731
760
  return {
732
761
  scrollOffset: state.scrollOffset,
733
762
  scrollVelocity: state.scrollVelocity,
734
- maxScroll: state.maxScroll
763
+ maxScroll: state.maxScroll,
764
+ overscrollDistance: state.overscrollDistance
735
765
  };
736
766
  };
737
767
 
738
768
  const handleTouchMove = (deltaY) => {
739
769
  if (state.isDragging) {
740
- state.scrollOffset += deltaY;
741
770
  state.maxScroll = calculateMaxScroll();
742
- state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
771
+ const wouldBeOffset = state.scrollOffset + deltaY;
772
+
773
+ let actualDelta = deltaY;
774
+
775
+ // ✅ CORRIGER: Appliquer les limites d'overscroll
776
+ if (wouldBeOffset > 0) {
777
+ // Overscroll en haut
778
+ const currentOverscroll = state.scrollOffset > 0 ? state.scrollOffset : 0;
779
+
780
+ // Si on a déjà atteint la limite, ne plus bouger
781
+ if (currentOverscroll >= state.maxOverscroll) {
782
+ actualDelta = 0;
783
+ state.overscrollDistance = state.maxOverscroll;
784
+ } else {
785
+ // Calculer la résistance progressive
786
+ const overscrollRatio = currentOverscroll / state.maxOverscroll;
787
+ const resistance = state.overscrollResistance * (1 - overscrollRatio * 0.7);
788
+
789
+ actualDelta = deltaY * resistance;
790
+
791
+ // S'assurer qu'on ne dépasse pas la limite
792
+ const newOverscroll = currentOverscroll + actualDelta;
793
+ if (newOverscroll > state.maxOverscroll) {
794
+ actualDelta = state.maxOverscroll - currentOverscroll;
795
+ }
796
+
797
+ state.overscrollDistance = currentOverscroll + actualDelta;
798
+ }
799
+ } else if (wouldBeOffset < -state.maxScroll) {
800
+ // Overscroll en bas
801
+ const currentOverscroll = state.scrollOffset < -state.maxScroll
802
+ ? Math.abs(state.scrollOffset + state.maxScroll)
803
+ : 0;
804
+
805
+ // Si on a déjà atteint la limite, ne plus bouger
806
+ if (currentOverscroll >= state.maxOverscroll) {
807
+ actualDelta = 0;
808
+ state.overscrollDistance = -state.maxOverscroll;
809
+ } else {
810
+ // Calculer la résistance progressive
811
+ const overscrollRatio = currentOverscroll / state.maxOverscroll;
812
+ const resistance = state.overscrollResistance * (1 - overscrollRatio * 0.7);
813
+
814
+ actualDelta = deltaY * resistance;
815
+
816
+ // S'assurer qu'on ne dépasse pas la limite
817
+ const newOverscroll = currentOverscroll + Math.abs(actualDelta);
818
+ if (newOverscroll > state.maxOverscroll) {
819
+ actualDelta = deltaY > 0
820
+ ? state.maxOverscroll - currentOverscroll
821
+ : -(state.maxOverscroll - currentOverscroll);
822
+ }
823
+
824
+ state.overscrollDistance = -(currentOverscroll + Math.abs(actualDelta));
825
+ }
826
+ } else {
827
+ // Scroll normal
828
+ state.overscrollDistance = 0;
829
+ }
830
+
831
+ state.scrollOffset += actualDelta;
743
832
  state.scrollVelocity = deltaY;
833
+
744
834
  return {
745
835
  scrollOffset: state.scrollOffset,
746
836
  scrollVelocity: state.scrollVelocity,
747
- maxScroll: state.maxScroll
837
+ maxScroll: state.maxScroll,
838
+ overscrollDistance: state.overscrollDistance
748
839
  };
749
840
  }
750
841
  return null;
@@ -755,16 +846,14 @@ class CanvasFramework {
755
846
 
756
847
  switch (type) {
757
848
  case 'INIT':
758
- state = {
759
- ...state,
760
- ...payload
761
- };
849
+ state = { ...state, ...payload };
762
850
  state.maxScroll = calculateMaxScroll();
763
851
  self.postMessage({
764
852
  type: 'INITIALIZED',
765
853
  payload: {
766
854
  scrollOffset: state.scrollOffset,
767
- maxScroll: state.maxScroll
855
+ maxScroll: state.maxScroll,
856
+ overscrollDistance: 0
768
857
  }
769
858
  });
770
859
  break;
@@ -817,12 +906,43 @@ class CanvasFramework {
817
906
  case 'SET_SCROLL_OFFSET':
818
907
  state.scrollOffset = payload.scrollOffset;
819
908
  state.maxScroll = calculateMaxScroll();
820
- state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
909
+ state.overscrollDistance = 0;
910
+ self.postMessage({
911
+ type: 'SCROLL_UPDATED',
912
+ payload: {
913
+ scrollOffset: state.scrollOffset,
914
+ maxScroll: state.maxScroll,
915
+ overscrollDistance: 0
916
+ }
917
+ });
918
+ break;
919
+
920
+ case 'ANIMATE_RETURN':
921
+ state.maxScroll = calculateMaxScroll();
922
+
923
+ if (state.scrollOffset > 0) {
924
+ state.scrollOffset *= 0.75;
925
+ state.overscrollDistance = state.scrollOffset;
926
+ } else if (state.scrollOffset < -state.maxScroll) {
927
+ const diff = state.scrollOffset + state.maxScroll;
928
+ state.scrollOffset = -state.maxScroll + (diff * 0.75);
929
+ state.overscrollDistance = diff * 0.75;
930
+ }
931
+
932
+ const shouldContinue = Math.abs(state.overscrollDistance) > 1;
933
+
934
+ if (!shouldContinue) {
935
+ state.scrollOffset = Math.max(Math.min(state.scrollOffset, 0), -state.maxScroll);
936
+ state.overscrollDistance = 0;
937
+ }
938
+
821
939
  self.postMessage({
822
940
  type: 'SCROLL_UPDATED',
823
941
  payload: {
824
942
  scrollOffset: state.scrollOffset,
825
- maxScroll: state.maxScroll
943
+ maxScroll: state.maxScroll,
944
+ overscrollDistance: state.overscrollDistance,
945
+ shouldContinue
826
946
  }
827
947
  });
828
948
  break;
@@ -834,7 +954,8 @@ class CanvasFramework {
834
954
  scrollOffset: state.scrollOffset,
835
955
  scrollVelocity: state.scrollVelocity,
836
956
  maxScroll: state.maxScroll,
837
- isDragging: state.isDragging
957
+ isDragging: state.isDragging,
958
+ overscrollDistance: state.overscrollDistance
838
959
  }
839
960
  });
840
961
  break;
@@ -842,64 +963,71 @@ class CanvasFramework {
842
963
  };
843
964
  `;
844
965
 
845
- const blob = new Blob([workerCode], {
846
- type: 'application/javascript'
847
- });
848
- return new Worker(URL.createObjectURL(blob));
849
- }
966
+ const blob = new Blob([workerCode], { type: 'application/javascript' });
967
+ return new Worker(URL.createObjectURL(blob));
968
+ }
850
969
 
851
970
  /**
852
971
  * Gère les messages du Scroll Worker
853
972
  */
854
973
  handleScrollWorkerMessage(e) {
855
- const {
856
- type,
857
- payload
858
- } = e.data;
974
+ const { type, payload } = e.data;
859
975
 
860
- switch (type) {
861
- case 'SCROLL_UPDATED':
862
- this.scrollOffset = payload.scrollOffset;
863
- this.scrollVelocity = payload.scrollVelocity;
864
- // ✅ CORRECTION IMPORTANTE : Vider le cache dirty pendant le scroll
865
- if (Math.abs(payload.scrollVelocity) > 0.5) {
866
- this.dirtyComponents.clear();
867
- }
868
- // Mettre à jour le cache
869
- this._cachedMaxScroll = payload.maxScroll;
870
- this._maxScrollDirty = false;
871
-
872
- // Marquer les composants comme sales pour mise à jour visuelle
873
- if (Math.abs(payload.scrollVelocity) > 0) {
874
- this.components.forEach(comp => {
875
- if (!this.isFixedComponent(comp)) {
876
- this.markComponentDirty(comp);
877
- }
878
- });
879
- }
880
- break;
976
+ switch (type) {
977
+ case 'SCROLL_UPDATED':
978
+ this.scrollOffset = payload.scrollOffset;
979
+ this.scrollVelocity = payload.scrollVelocity;
980
+
981
+ // AJOUTER
982
+ this.overscrollDistance = payload.overscrollDistance || 0;
983
+
984
+ if (Math.abs(payload.scrollVelocity) > 0.5) {
985
+ this.dirtyComponents.clear();
986
+ }
987
+
988
+ this._cachedMaxScroll = payload.maxScroll;
989
+ this._maxScrollDirty = false;
881
990
 
882
- case 'MAX_SCROLL_UPDATED':
883
- this._cachedMaxScroll = payload.maxScroll;
884
- this._maxScrollDirty = false;
885
- break;
991
+ if (Math.abs(payload.scrollVelocity) > 0) {
992
+ this.components.forEach(comp => {
993
+ if (!this.isFixedComponent(comp)) {
994
+ this.markComponentDirty(comp);
995
+ }
996
+ });
997
+ }
998
+
999
+ // ✅ AJOUTER: Continuer l'animation de retour si nécessaire
1000
+ if (payload.shouldContinue !== undefined && payload.shouldContinue) {
1001
+ requestAnimationFrame(() => {
1002
+ this.scrollWorker.postMessage({ type: 'ANIMATE_RETURN' });
1003
+ });
1004
+ } else if (payload.shouldContinue === false) {
1005
+ this.isOverscrollAnimating = false;
1006
+ }
1007
+ break;
886
1008
 
887
- case 'INITIALIZED':
888
- this.scrollOffset = payload.scrollOffset;
889
- this._cachedMaxScroll = payload.maxScroll;
890
- this._maxScrollDirty = false;
891
- break;
1009
+ case 'MAX_SCROLL_UPDATED':
1010
+ this._cachedMaxScroll = payload.maxScroll;
1011
+ this._maxScrollDirty = false;
1012
+ break;
892
1013
 
893
- case 'STATE':
894
- // Synchroniser l'état local
895
- this.scrollOffset = payload.scrollOffset;
896
- this.scrollVelocity = payload.scrollVelocity;
897
- this.isDragging = payload.isDragging;
898
- this._cachedMaxScroll = payload.maxScroll;
899
- this._maxScrollDirty = false;
900
- break;
901
- }
1014
+ case 'INITIALIZED':
1015
+ this.scrollOffset = payload.scrollOffset;
1016
+ this._cachedMaxScroll = payload.maxScroll;
1017
+ this._maxScrollDirty = false;
1018
+ this.overscrollDistance = 0; // ✅ AJOUTER
1019
+ break;
1020
+
1021
+ case 'STATE':
1022
+ this.scrollOffset = payload.scrollOffset;
1023
+ this.scrollVelocity = payload.scrollVelocity;
1024
+ this.isDragging = payload.isDragging;
1025
+ this._cachedMaxScroll = payload.maxScroll;
1026
+ this._maxScrollDirty = false;
1027
+ this.overscrollDistance = payload.overscrollDistance || 0; // ✅ AJOUTER
1028
+ break;
902
1029
  }
1030
+ }
903
1031
 
904
1032
  /**
905
1033
  * Initialise le Scroll Worker avec les données actuelles
@@ -2311,23 +2439,29 @@ class CanvasFramework {
2311
2439
  }
2312
2440
 
2313
2441
  handleTouchEnd(e) {
2314
- e.preventDefault();
2315
- const touch = e.changedTouches[0];
2316
- const pos = this.getTouchPos(touch);
2442
+ e.preventDefault();
2443
+ const touch = e.changedTouches[0];
2444
+ const pos = this.getTouchPos(touch);
2317
2445
 
2318
- if (!this.isDragging) {
2319
- this.checkComponentsAtPosition(pos.x, pos.y, 'end');
2320
- } else {
2321
- this.isDragging = false;
2322
- this.scrollWorker.postMessage({
2323
- type: 'SET_DRAGGING',
2324
- payload: {
2325
- isDragging: false,
2326
- lastVelocity: this.scrollVelocity
2327
- }
2328
- });
2329
- }
2330
- }
2446
+ if (!this.isDragging) {
2447
+ this.checkComponentsAtPosition(pos.x, pos.y, 'end');
2448
+ } else {
2449
+ this.isDragging = false;
2450
+ this.scrollWorker.postMessage({
2451
+ type: 'SET_DRAGGING',
2452
+ payload: {
2453
+ isDragging: false,
2454
+ lastVelocity: this.scrollVelocity
2455
+ }
2456
+ });
2457
+
2458
+ // ✅ AJOUTER: Démarrer l'animation de retour
2459
+ if (!this.isOverscrollAnimating) {
2460
+ this.isOverscrollAnimating = true;
2461
+ this.scrollWorker.postMessage({ type: 'ANIMATE_RETURN' });
2462
+ }
2463
+ }
2464
+ }
2331
2465
 
2332
2466
  handleMouseDown(e) {
2333
2467
  this.isDragging = false;
@@ -2375,21 +2509,26 @@ class CanvasFramework {
2375
2509
  }
2376
2510
  }
2377
2511
 
2378
- handleMouseUp(e) {
2379
- if (!this.isDragging) {
2380
- this.checkComponentsAtPosition(e.clientX, e.clientY, 'end');
2381
- } else {
2382
- this.isDragging = false;
2383
- this.scrollWorker.postMessage({
2384
- type: 'SET_DRAGGING',
2385
- payload: {
2386
- isDragging: false,
2387
- lastVelocity: this.scrollVelocity
2388
- }
2389
- });
2512
+ handleMouseUp(e) {
2513
+ if (!this.isDragging) {
2514
+ this.checkComponentsAtPosition(e.clientX, e.clientY, 'end');
2515
+ } else {
2516
+ this.isDragging = false;
2517
+ this.scrollWorker.postMessage({
2518
+ type: 'SET_DRAGGING',
2519
+ payload: {
2520
+ isDragging: false,
2521
+ lastVelocity: this.scrollVelocity
2522
+ }
2523
+ });
2524
+
2525
+ // ✅ AJOUTER: Démarrer l'animation de retour
2526
+ if (!this.isOverscrollAnimating) {
2527
+ this.isOverscrollAnimating = true;
2528
+ this.scrollWorker.postMessage({ type: 'ANIMATE_RETURN' });
2390
2529
  }
2391
2530
  }
2392
-
2531
+ }
2393
2532
 
2394
2533
  getTouchPos(touch) {
2395
2534
  const rect = this.canvas.getBoundingClientRect();
@@ -2988,6 +3127,46 @@ class CanvasFramework {
2988
3127
  this.renderFull();
2989
3128
  }
2990
3129
  }
3130
+
3131
+ /**
3132
+ * Dessine l'effet d'overscroll (overlay gris)
3133
+ */
3134
+ drawOverscrollEffect() {console.log('dessine');
3135
+ if (Math.abs(this.overscrollDistance) < 1) return;
3136
+
3137
+ const ctx = this.ctx;
3138
+ ctx.save();
3139
+
3140
+ // Calculer l'opacité (max 0.4 pour Android)
3141
+ const maxOverscroll = 150;
3142
+ const opacity = Math.min(Math.abs(this.overscrollDistance) / maxOverscroll, 1) * 0.4;
3143
+
3144
+ // Hauteur de l'overlay
3145
+ const overlayHeight = Math.min(Math.abs(this.overscrollDistance) * 1.2, 250);
3146
+
3147
+ let gradient;
3148
+
3149
+ // Overscroll en haut
3150
+ if (this.overscrollDistance > 0) {
3151
+ gradient = ctx.createLinearGradient(0, 0, 0, overlayHeight);
3152
+ gradient.addColorStop(0, `rgba(100, 100, 100, ${opacity})`);
3153
+ gradient.addColorStop(1, 'rgba(100, 100, 100, 0)');
3154
+
3155
+ ctx.fillStyle = gradient;
3156
+ ctx.fillRect(0, 0, this.width, overlayHeight);
3157
+ }
3158
+ // Overscroll en bas
3159
+ else if (this.overscrollDistance < 0) {
3160
+ gradient = ctx.createLinearGradient(0, this.height - overlayHeight, 0, this.height);
3161
+ gradient.addColorStop(0, 'rgba(100, 100, 100, 0)');
3162
+ gradient.addColorStop(1, `rgba(100, 100, 100, ${opacity})`);
3163
+
3164
+ ctx.fillStyle = gradient;
3165
+ ctx.fillRect(0, this.height - overlayHeight, this.width, overlayHeight);
3166
+ }
3167
+
3168
+ ctx.restore();
3169
+ }
2991
3170
 
2992
3171
  /**
2993
3172
  * Rendu normal (sans transition)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.5.57",
3
+ "version": "0.5.58",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/beyons/CanvasFramework.git"