canvasframework 0.5.1 → 0.5.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.
- package/components/Drawer.js +15 -0
- package/core/CanvasFramework.js +89 -48
- package/features/Skeleton.js +128 -19
- package/index.js +1 -1
- package/package.json +1 -1
package/components/Drawer.js
CHANGED
|
@@ -45,12 +45,27 @@ class Drawer extends Component {
|
|
|
45
45
|
// IMPORTANT: Définir les callbacks
|
|
46
46
|
this.onPress = this.handlePress;
|
|
47
47
|
this.onMove = this.handleMove;
|
|
48
|
+
// ✅ Se mettre automatiquement au-dessus de tous les composants
|
|
49
|
+
this.bringToFront();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Met le drawer au-dessus de tous les composants
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
bringToFront() {
|
|
57
|
+
const index = this.framework.components.indexOf(this);
|
|
58
|
+
if (index > -1) {
|
|
59
|
+
this.framework.components.splice(index, 1);
|
|
60
|
+
this.framework.components.push(this);
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
/**
|
|
51
65
|
* Ouvre le drawer
|
|
52
66
|
*/
|
|
53
67
|
open() {
|
|
68
|
+
this.bringToFront(); // ✅ Se remettre au-dessus à chaque ouverture
|
|
54
69
|
this.visible = true;
|
|
55
70
|
this.targetX = 0;
|
|
56
71
|
this.animate();
|
package/core/CanvasFramework.js
CHANGED
|
@@ -155,6 +155,15 @@ class CanvasFramework {
|
|
|
155
155
|
constructor(canvasId, options = {}) {
|
|
156
156
|
// ✅ AJOUTER: Démarrer le chronomètre
|
|
157
157
|
const startTime = performance.now();
|
|
158
|
+
|
|
159
|
+
this.metrics = {
|
|
160
|
+
initTime: 0,
|
|
161
|
+
firstRenderTime: null,
|
|
162
|
+
firstInteractionTime: null,
|
|
163
|
+
totalStartupTime: null
|
|
164
|
+
};
|
|
165
|
+
this._firstRenderDone = false;
|
|
166
|
+
this._startupStartTime = startTime;
|
|
158
167
|
|
|
159
168
|
// ✅ Créer automatiquement le canvas
|
|
160
169
|
this.canvas = document.createElement('canvas');
|
|
@@ -2649,54 +2658,86 @@ class CanvasFramework {
|
|
|
2649
2658
|
}
|
|
2650
2659
|
}
|
|
2651
2660
|
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2661
|
+
startRenderLoop() {
|
|
2662
|
+
let lastScrollOffset = this.scrollOffset;
|
|
2663
|
+
let lastRenderMode = 'full';
|
|
2664
|
+
|
|
2665
|
+
const render = () => {
|
|
2666
|
+
if (!this._splashFinished) {
|
|
2667
|
+
requestAnimationFrame(render);
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
// Vérifier le scroll
|
|
2672
|
+
const scrollChanged = Math.abs(this.scrollOffset - lastScrollOffset) > 0.1;
|
|
2673
|
+
lastScrollOffset = this.scrollOffset;
|
|
2674
|
+
|
|
2675
|
+
// Décider du mode de rendu
|
|
2676
|
+
let renderMode = 'full';
|
|
2677
|
+
|
|
2678
|
+
if (this.optimizationEnabled &&
|
|
2679
|
+
this.dirtyComponents.size > 0 &&
|
|
2680
|
+
!this.isDragging &&
|
|
2681
|
+
Math.abs(this.scrollVelocity) < 0.5 &&
|
|
2682
|
+
!scrollChanged &&
|
|
2683
|
+
this.dirtyComponents.size < 3) {
|
|
2684
|
+
renderMode = 'dirty';
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
if (renderMode === 'full' || lastRenderMode !== renderMode) {
|
|
2688
|
+
this.ctx.fillStyle = this.backgroundColor || '#ffffff';
|
|
2689
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
2690
|
+
this.renderFull();
|
|
2691
|
+
} else {
|
|
2692
|
+
this._renderDirtyComponents();
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
lastRenderMode = renderMode;
|
|
2696
|
+
|
|
2697
|
+
// ✅ AJOUTER : Calcul et affichage du FPS
|
|
2698
|
+
this._frames++;
|
|
2699
|
+
const now = performance.now();
|
|
2700
|
+
const elapsed = now - this._lastFpsTime;
|
|
2701
|
+
|
|
2702
|
+
if (elapsed >= 1000) {
|
|
2703
|
+
this.fps = Math.round((this._frames * 1000) / elapsed);
|
|
2704
|
+
this._frames = 0;
|
|
2705
|
+
this._lastFpsTime = now;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// ✅ AJOUTER : Afficher le FPS si activé
|
|
2709
|
+
if (this.showFps) {
|
|
2710
|
+
this.ctx.save();
|
|
2711
|
+
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
|
2712
|
+
this.ctx.fillRect(10, 10, 100, 40);
|
|
2713
|
+
this.ctx.fillStyle = '#00ff00';
|
|
2714
|
+
this.ctx.font = 'bold 20px monospace';
|
|
2715
|
+
this.ctx.textAlign = 'left';
|
|
2716
|
+
this.ctx.textBaseline = 'top';
|
|
2717
|
+
this.ctx.fillText(`FPS: ${this.fps}`, 20, 20);
|
|
2718
|
+
this.ctx.restore();
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// ✅ AJOUTER : Indicateurs de débogage si activés
|
|
2722
|
+
if (this.debbug) {
|
|
2723
|
+
this.drawOverflowIndicators();
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// ✅ AJOUTER : Marquer le premier rendu
|
|
2727
|
+
if (!this._firstRenderDone) {
|
|
2728
|
+
this._markFirstRender();
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// ✅ AJOUTER : Mettre à jour l'inertie si nécessaire
|
|
2732
|
+
if (Math.abs(this.scrollVelocity) > 0.1 && !this.isDragging) {
|
|
2733
|
+
this.scrollWorker.postMessage({ type: 'UPDATE_INERTIA' });
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
requestAnimationFrame(render);
|
|
2737
|
+
};
|
|
2738
|
+
|
|
2739
|
+
render();
|
|
2740
|
+
}
|
|
2700
2741
|
|
|
2701
2742
|
// 3. Ajoutez une méthode renderFull() optimisée
|
|
2702
2743
|
renderFull() {
|
package/features/Skeleton.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* Composant Skeleton (placeholder animé pendant le chargement)
|
|
4
5
|
* @class
|
|
@@ -6,7 +7,7 @@ import Component from '../core/Component.js';
|
|
|
6
7
|
* @param {Framework} framework - Instance du framework
|
|
7
8
|
* @param {Object} [options={}] - Options de configuration
|
|
8
9
|
* @param {string} [options.type='text'] - Type de skeleton ('text', 'circle', 'rectangle')
|
|
9
|
-
* @param {number} [options.
|
|
10
|
+
* @param {number} [options.animationDuration=1.5] - Durée de l'animation en secondes
|
|
10
11
|
* @example
|
|
11
12
|
* const skeleton = new Skeleton(framework, {
|
|
12
13
|
* type: 'text',
|
|
@@ -21,11 +22,14 @@ class Skeleton extends Component {
|
|
|
21
22
|
constructor(framework, options = {}) {
|
|
22
23
|
super(framework, options);
|
|
23
24
|
/** @type {string} */
|
|
24
|
-
this.type = options.type || 'text';
|
|
25
|
+
this.type = options.type || 'text';
|
|
26
|
+
/** @type {number} */
|
|
27
|
+
this.animationDuration = (options.animationDuration || 1.5) * 1000; // en ms
|
|
25
28
|
/** @type {number} */
|
|
26
|
-
this.
|
|
29
|
+
this.startTime = Date.now();
|
|
27
30
|
/** @type {number} */
|
|
28
|
-
this.
|
|
31
|
+
this.borderRadius = options.borderRadius || 4;
|
|
32
|
+
|
|
29
33
|
this.startAnimation();
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -35,50 +39,155 @@ class Skeleton extends Component {
|
|
|
35
39
|
*/
|
|
36
40
|
startAnimation() {
|
|
37
41
|
const animate = () => {
|
|
38
|
-
if (!this.visible)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (this.animationProgress > Math.PI * 2) {
|
|
42
|
-
this.animationProgress = 0;
|
|
42
|
+
if (!this.visible) {
|
|
43
|
+
requestAnimationFrame(animate);
|
|
44
|
+
return;
|
|
43
45
|
}
|
|
44
|
-
|
|
45
46
|
requestAnimationFrame(animate);
|
|
46
47
|
};
|
|
47
48
|
animate();
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
|
-
*
|
|
52
|
+
* Calcule la progression de l'animation (0 à 1)
|
|
53
|
+
* @private
|
|
54
|
+
* @returns {number}
|
|
55
|
+
*/
|
|
56
|
+
getAnimationProgress() {
|
|
57
|
+
const elapsed = Date.now() - this.startTime;
|
|
58
|
+
const progress = (elapsed % this.animationDuration) / this.animationDuration;
|
|
59
|
+
return progress;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Crée le gradient shimmer diagonal style Ionic/Material
|
|
64
|
+
* @private
|
|
65
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
66
|
+
* @param {number} x
|
|
67
|
+
* @param {number} y
|
|
68
|
+
* @param {number} width
|
|
69
|
+
* @param {number} height
|
|
70
|
+
* @returns {CanvasGradient}
|
|
71
|
+
*/
|
|
72
|
+
createShimmerGradient(ctx, x, y, width, height) {
|
|
73
|
+
const progress = this.getAnimationProgress();
|
|
74
|
+
|
|
75
|
+
// Calculer la diagonale pour l'effet
|
|
76
|
+
const diagonal = Math.sqrt(width * width + height * height);
|
|
77
|
+
|
|
78
|
+
// Position du gradient qui se déplace
|
|
79
|
+
const offset = diagonal * 2;
|
|
80
|
+
const position = -diagonal + (offset * progress);
|
|
81
|
+
|
|
82
|
+
// Angle à 45 degrés (de haut-gauche vers bas-droite)
|
|
83
|
+
const angle = Math.PI / 4;
|
|
84
|
+
const cos = Math.cos(angle);
|
|
85
|
+
const sin = Math.sin(angle);
|
|
86
|
+
|
|
87
|
+
const gradient = ctx.createLinearGradient(
|
|
88
|
+
x + position * cos,
|
|
89
|
+
y + position * sin,
|
|
90
|
+
x + (position + diagonal) * cos,
|
|
91
|
+
y + (position + diagonal) * sin
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Gradient blanc avec transparence (style Ionic/Material)
|
|
95
|
+
gradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
|
|
96
|
+
gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.4)');
|
|
97
|
+
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
98
|
+
|
|
99
|
+
return gradient;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Dessine un rectangle arrondi
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
roundRect(ctx, x, y, width, height, radius) {
|
|
107
|
+
if (width < 2 * radius) radius = width / 2;
|
|
108
|
+
if (height < 2 * radius) radius = height / 2;
|
|
109
|
+
|
|
110
|
+
ctx.beginPath();
|
|
111
|
+
ctx.moveTo(x + radius, y);
|
|
112
|
+
ctx.arcTo(x + width, y, x + width, y + height, radius);
|
|
113
|
+
ctx.arcTo(x + width, y + height, x, y + height, radius);
|
|
114
|
+
ctx.arcTo(x, y + height, x, y, radius);
|
|
115
|
+
ctx.arcTo(x, y, x + width, y, radius);
|
|
116
|
+
ctx.closePath();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Dessine le skeleton avec l'effet shimmer Ionic/Material
|
|
52
121
|
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
53
122
|
*/
|
|
54
123
|
draw(ctx) {
|
|
55
124
|
ctx.save();
|
|
56
125
|
|
|
57
|
-
|
|
58
|
-
|
|
126
|
+
// Couleur de fond (gris clair Material)
|
|
127
|
+
const backgroundColor = '#e0e0e0';
|
|
59
128
|
|
|
60
129
|
switch(this.type) {
|
|
61
130
|
case 'circle':
|
|
131
|
+
// Dessiner le cercle de base
|
|
132
|
+
ctx.fillStyle = backgroundColor;
|
|
62
133
|
ctx.beginPath();
|
|
63
134
|
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2);
|
|
64
135
|
ctx.fill();
|
|
136
|
+
|
|
137
|
+
// Créer un clipping path pour le shimmer
|
|
138
|
+
ctx.beginPath();
|
|
139
|
+
ctx.arc(this.x + this.width/2, this.y + this.height/2, this.width/2, 0, Math.PI * 2);
|
|
140
|
+
ctx.clip();
|
|
141
|
+
|
|
142
|
+
// Appliquer le shimmer
|
|
143
|
+
ctx.fillStyle = this.createShimmerGradient(ctx, this.x, this.y, this.width, this.height);
|
|
144
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
65
145
|
break;
|
|
146
|
+
|
|
66
147
|
case 'text':
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const
|
|
148
|
+
const lineHeight = 16;
|
|
149
|
+
const lineSpacing = 10;
|
|
150
|
+
const totalLineHeight = lineHeight + lineSpacing;
|
|
151
|
+
const lines = Math.floor(this.height / totalLineHeight);
|
|
70
152
|
|
|
71
153
|
for (let i = 0; i < lines; i++) {
|
|
72
154
|
const lineWidth = i === lines - 1 ? this.width * 0.6 : this.width;
|
|
73
|
-
|
|
155
|
+
const lineY = this.y + i * totalLineHeight;
|
|
156
|
+
|
|
157
|
+
// Fond de la ligne
|
|
158
|
+
ctx.fillStyle = backgroundColor;
|
|
159
|
+
this.roundRect(ctx, this.x, lineY, lineWidth, lineHeight, this.borderRadius);
|
|
160
|
+
ctx.fill();
|
|
161
|
+
|
|
162
|
+
// Shimmer sur la ligne
|
|
163
|
+
ctx.save();
|
|
164
|
+
this.roundRect(ctx, this.x, lineY, lineWidth, lineHeight, this.borderRadius);
|
|
165
|
+
ctx.clip();
|
|
166
|
+
|
|
167
|
+
ctx.fillStyle = this.createShimmerGradient(ctx, this.x, lineY, lineWidth, lineHeight);
|
|
168
|
+
ctx.fillRect(this.x - 100, lineY - 100, lineWidth + 200, lineHeight + 200);
|
|
169
|
+
ctx.restore();
|
|
74
170
|
}
|
|
75
171
|
break;
|
|
172
|
+
|
|
76
173
|
default:
|
|
77
|
-
|
|
174
|
+
// Rectangle de base
|
|
175
|
+
ctx.fillStyle = backgroundColor;
|
|
176
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
177
|
+
ctx.fill();
|
|
178
|
+
|
|
179
|
+
// Shimmer
|
|
180
|
+
ctx.save();
|
|
181
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
182
|
+
ctx.clip();
|
|
183
|
+
|
|
184
|
+
ctx.fillStyle = this.createShimmerGradient(ctx, this.x, this.y, this.width, this.height);
|
|
185
|
+
ctx.fillRect(this.x - 100, this.y - 100, this.width + 200, this.height + 200);
|
|
186
|
+
ctx.restore();
|
|
78
187
|
}
|
|
79
188
|
|
|
80
189
|
ctx.restore();
|
|
81
190
|
}
|
|
82
191
|
}
|
|
83
192
|
|
|
84
|
-
export default Skeleton;
|
|
193
|
+
export default Skeleton;
|
package/index.js
CHANGED