@bebranded/bb-contents 1.0.146 → 1.0.148

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.
Files changed (2) hide show
  1. package/bb-contents.js +181 -128
  2. package/package.json +1 -1
package/bb-contents.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * BeBranded Contents
3
3
  * Contenus additionnels français pour Webflow
4
- * @version 1.0.146
4
+ * @version 1.0.148
5
5
  * @author BeBranded
6
6
  * @license MIT
7
7
  * @website https://www.bebranded.xyz
@@ -32,11 +32,11 @@
32
32
  window._bbContentsInitialized = true;
33
33
 
34
34
  // Log de démarrage simple (une seule fois)
35
- console.log('bb-contents | v1.0.146');
35
+ console.log('bb-contents | v1.0.148');
36
36
 
37
37
  // Configuration
38
38
  const config = {
39
- version: '1.0.146',
39
+ version: '1.0.148',
40
40
  debug: false, // Debug désactivé pour rendu propre
41
41
  prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
42
42
  youtubeEndpoint: null, // URL du worker YouTube (à définir par l'utilisateur)
@@ -347,57 +347,9 @@
347
347
  const mainBlock = document.createElement('div');
348
348
  mainBlock.innerHTML = originalHTML;
349
349
 
350
- // Permettre le retour à la ligne pour le texte dans les items du marquee
351
- // Le white-space: nowrap sur le conteneur flex empêche les items de se retourner,
352
- // mais ne doit pas empêcher le texte à l'intérieur des items de faire plusieurs lignes
353
- if (!isVertical) {
354
- setTimeout(() => {
355
- const marqueeItems = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"]');
356
- marqueeItems.forEach(item => {
357
- // Préserver la largeur de l'item définie dans Webflow
358
- const computedStyle = getComputedStyle(item);
359
- const itemWidth = computedStyle.width;
360
- if (itemWidth && itemWidth !== 'auto' && itemWidth !== '0px') {
361
- item.style.minWidth = itemWidth;
362
- item.style.width = itemWidth;
363
- }
364
-
365
- // Permettre le retour à la ligne pour les conteneurs de texte
366
- const textContainers = item.querySelectorAll('.use-case_client, .testimonial_client-info, [class*="text"], p, span');
367
- textContainers.forEach(container => {
368
- const containerComputed = getComputedStyle(container);
369
- // Si l'élément a une largeur définie, la préserver
370
- if (containerComputed.width && containerComputed.width !== 'auto' && containerComputed.width !== '0px') {
371
- container.style.width = containerComputed.width;
372
- } else {
373
- // Sinon, prendre 100% de la largeur du parent
374
- container.style.width = '100%';
375
- }
376
- // Forcer le retour à la ligne
377
- container.style.whiteSpace = 'normal';
378
- container.style.wordWrap = 'break-word';
379
- container.style.overflowWrap = 'break-word';
380
- });
381
- });
382
- }, 0);
383
- }
384
-
385
- mainBlock.style.cssText = `
386
- display: flex;
387
- ${isVertical ? 'flex-direction: column;' : ''}
388
- align-items: center;
389
- gap: ${gap}px;
390
- ${isVertical ? '' : 'white-space: nowrap;'}
391
- flex-shrink: 0;
392
- ${isVertical ? 'min-height: 100px;' : ''}
393
- `;
394
-
395
- // Créer 3 copies pour le défilement infini
396
- const repeatBlock1 = mainBlock.cloneNode(true);
397
- const repeatBlock2 = mainBlock.cloneNode(true);
398
-
399
- // Forcer le chargement COMPLET des images dans les copies pour éviter l'apparition tardive
400
- const preloadImagesInBlock = function(block) {
350
+ // IMPORTANT: Forcer le chargement de TOUTES les images dans mainBlock AVANT de cloner
351
+ // Cela garantit que les images sont dans le cache du navigateur avant le clonage
352
+ const preloadAllImagesFirst = function(block) {
401
353
  return new Promise(function(resolve) {
402
354
  const images = block.querySelectorAll('img');
403
355
  if (images.length === 0) {
@@ -421,7 +373,7 @@
421
373
  img.src = img.dataset.src;
422
374
  }
423
375
 
424
- // Si l'image est déjà chargée, compter comme chargée
376
+ // Si l'image est déjà complètement chargée
425
377
  if (img.complete && img.naturalWidth > 0 && img.naturalHeight > 0) {
426
378
  loadedCount++;
427
379
  checkComplete();
@@ -429,12 +381,20 @@
429
381
  // Précharger avec new Image() pour forcer le cache
430
382
  const preloadImg = new Image();
431
383
  preloadImg.onload = function() {
432
- // S'assurer que l'image dans le DOM est aussi chargée
433
- if (img.src && !img.complete) {
434
- img.src = img.src; // Forcer le rechargement
384
+ // Forcer aussi le chargement dans l'image du DOM
385
+ if (img.src) {
386
+ img.src = img.src;
435
387
  }
436
- loadedCount++;
437
- checkComplete();
388
+ // Attendre que l'image du DOM soit aussi chargée
389
+ const checkDomImage = function() {
390
+ if (img.complete && img.naturalWidth > 0 && img.naturalHeight > 0) {
391
+ loadedCount++;
392
+ checkComplete();
393
+ } else {
394
+ setTimeout(checkDomImage, 10);
395
+ }
396
+ };
397
+ setTimeout(checkDomImage, 10);
438
398
  };
439
399
  preloadImg.onerror = function() {
440
400
  errorCount++;
@@ -451,81 +411,147 @@
451
411
  }
452
412
 
453
413
  // Écouter aussi le chargement de l'image dans le DOM
454
- const originalOnload = img.onload;
455
414
  img.onload = function() {
456
- if (originalOnload) originalOnload();
457
- if (!img.complete || img.naturalWidth === 0) {
458
- // Attendre encore un peu
459
- setTimeout(function() {
460
- if (img.complete && img.naturalWidth > 0) {
461
- loadedCount++;
462
- checkComplete();
463
- }
464
- }, 50);
465
- } else {
415
+ if (img.complete && img.naturalWidth > 0 && img.naturalHeight > 0) {
466
416
  loadedCount++;
467
417
  checkComplete();
468
418
  }
469
419
  };
470
420
 
471
- // Si l'image a déjà un src, déclencher le chargement
421
+ // Forcer le chargement si l'image a déjà un src
472
422
  if (img.src) {
473
423
  img.src = img.src;
474
424
  }
475
425
  }
476
426
  });
427
+
428
+ // Timeout de sécurité (max 5 secondes)
429
+ setTimeout(function() {
430
+ if (loadedCount + errorCount < totalImages) {
431
+ errorCount = totalImages - loadedCount;
432
+ checkComplete();
433
+ }
434
+ }, 5000);
477
435
  });
478
436
  };
479
437
 
480
- // Pour les marquees horizontaux, utiliser position relative (plus simple et plus fiable)
438
+ // Permettre le retour à la ligne pour le texte dans les items du marquee
439
+ // Le white-space: nowrap sur le conteneur flex empêche les items de se retourner,
440
+ // mais ne doit pas empêcher le texte à l'intérieur des items de faire plusieurs lignes
481
441
  if (!isVertical) {
482
- scrollContainer.appendChild(mainBlock);
483
- scrollContainer.appendChild(repeatBlock1);
484
- scrollContainer.appendChild(repeatBlock2);
485
- mainContainer.appendChild(scrollContainer);
486
-
487
- // Calculer la hauteur maximale des items après ajout au DOM
488
- requestAnimationFrame(() => {
489
- requestAnimationFrame(() => {
490
- const items = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"], > *');
491
- let maxHeight = 0;
492
- items.forEach(function(item) {
493
- const itemHeight = item.offsetHeight;
494
- if (itemHeight > maxHeight) {
495
- maxHeight = itemHeight;
496
- }
497
- });
498
-
499
- // Si aucun item trouvé, essayer de prendre la hauteur du scrollContainer
500
- if (maxHeight === 0) {
501
- maxHeight = scrollContainer.offsetHeight;
442
+ setTimeout(() => {
443
+ const marqueeItems = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"]');
444
+ marqueeItems.forEach(item => {
445
+ // Préserver la largeur de l'item définie dans Webflow
446
+ const computedStyle = getComputedStyle(item);
447
+ const itemWidth = computedStyle.width;
448
+ if (itemWidth && itemWidth !== 'auto' && itemWidth !== '0px') {
449
+ item.style.minWidth = itemWidth;
450
+ item.style.width = itemWidth;
502
451
  }
503
452
 
504
- // Appliquer la hauteur calculée au mainContainer si elle est valide
505
- if (maxHeight > 0) {
506
- mainContainer.style.height = maxHeight + 'px';
507
- }
453
+ // Permettre le retour à la ligne pour les conteneurs de texte
454
+ const textContainers = item.querySelectorAll('.use-case_client, .testimonial_client-info, [class*="text"], p, span');
455
+ textContainers.forEach(container => {
456
+ const containerComputed = getComputedStyle(container);
457
+ // Si l'élément a une largeur définie, la préserver
458
+ if (containerComputed.width && containerComputed.width !== 'auto' && containerComputed.width !== '0px') {
459
+ container.style.width = containerComputed.width;
460
+ } else {
461
+ // Sinon, prendre 100% de la largeur du parent
462
+ container.style.width = '100%';
463
+ }
464
+ // Forcer le retour à la ligne
465
+ container.style.whiteSpace = 'normal';
466
+ container.style.wordWrap = 'break-word';
467
+ container.style.overflowWrap = 'break-word';
468
+ });
508
469
  });
509
- });
510
- } else {
511
- // Pour vertical, garder le comportement actuel
512
- scrollContainer.appendChild(mainBlock);
513
- scrollContainer.appendChild(repeatBlock1);
514
- scrollContainer.appendChild(repeatBlock2);
515
- mainContainer.appendChild(scrollContainer);
470
+ }, 0);
516
471
  }
517
472
 
518
- element.innerHTML = '';
519
- element.appendChild(mainContainer);
520
- element.setAttribute('data-bb-marquee-processed', 'true');
521
-
522
- // Attendre que TOUTES les images dans TOUTES les copies soient chargées avant de démarrer
523
- // Cela évite l'apparition tardive des logos dans le champ de vision
524
- Promise.all([
525
- preloadImagesInBlock(repeatBlock1),
526
- preloadImagesInBlock(repeatBlock2)
527
- ]).then(function() {
528
- // Attendre encore un peu pour s'assurer que le rendu est complet
473
+ mainBlock.style.cssText = `
474
+ display: flex;
475
+ ${isVertical ? 'flex-direction: column;' : ''}
476
+ align-items: center;
477
+ gap: ${gap}px;
478
+ ${isVertical ? '' : 'white-space: nowrap;'}
479
+ flex-shrink: 0;
480
+ ${isVertical ? 'min-height: 100px;' : ''}
481
+ `;
482
+
483
+ // NOUVELLE APPROCHE: Attendre que TOUTES les images du mainBlock soient chargées AVANT de cloner
484
+ // Cela garantit que les copies héritent d'images déjà dans le cache du navigateur
485
+ preloadAllImagesFirst(mainBlock).then(function() {
486
+ // Maintenant créer les copies - les images sont déjà en cache
487
+ const repeatBlock1 = mainBlock.cloneNode(true);
488
+ const repeatBlock2 = mainBlock.cloneNode(true);
489
+
490
+ // Forcer l'affichage immédiat des images dans les copies (elles sont en cache)
491
+ const forceImagesDisplay = function(block) {
492
+ const images = block.querySelectorAll('img');
493
+ images.forEach(function(img) {
494
+ if (img.dataset.src && !img.src) {
495
+ img.src = img.dataset.src;
496
+ }
497
+ // Forcer le chargement et l'affichage
498
+ if (img.src) {
499
+ img.src = img.src;
500
+ img.style.opacity = '1';
501
+ img.style.visibility = 'visible';
502
+ // Forcer un reflow pour s'assurer que l'image est rendue
503
+ void img.offsetHeight;
504
+ }
505
+ });
506
+ };
507
+
508
+ forceImagesDisplay(repeatBlock1);
509
+ forceImagesDisplay(repeatBlock2);
510
+
511
+ // Pour les marquees horizontaux, utiliser position relative (plus simple et plus fiable)
512
+ if (!isVertical) {
513
+ scrollContainer.appendChild(mainBlock);
514
+ scrollContainer.appendChild(repeatBlock1);
515
+ scrollContainer.appendChild(repeatBlock2);
516
+ mainContainer.appendChild(scrollContainer);
517
+
518
+ // Calculer la hauteur maximale des items après ajout au DOM
519
+ requestAnimationFrame(() => {
520
+ requestAnimationFrame(() => {
521
+ const items = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"], > *');
522
+ let maxHeight = 0;
523
+ items.forEach(function(item) {
524
+ const itemHeight = item.offsetHeight;
525
+ if (itemHeight > maxHeight) {
526
+ maxHeight = itemHeight;
527
+ }
528
+ });
529
+
530
+ // Si aucun item trouvé, essayer de prendre la hauteur du scrollContainer
531
+ if (maxHeight === 0) {
532
+ maxHeight = scrollContainer.offsetHeight;
533
+ }
534
+
535
+ // Appliquer la hauteur calculée au mainContainer si elle est valide
536
+ if (maxHeight > 0) {
537
+ mainContainer.style.height = maxHeight + 'px';
538
+ }
539
+ });
540
+ });
541
+ } else {
542
+ // Pour vertical, garder le comportement actuel
543
+ scrollContainer.appendChild(mainBlock);
544
+ scrollContainer.appendChild(repeatBlock1);
545
+ scrollContainer.appendChild(repeatBlock2);
546
+ mainContainer.appendChild(scrollContainer);
547
+ }
548
+
549
+ element.innerHTML = '';
550
+ element.appendChild(mainContainer);
551
+ element.setAttribute('data-bb-marquee-processed', 'true');
552
+
553
+ // Attendre un peu pour s'assurer que le rendu est complet
554
+ // Les images sont déjà chargées donc pas besoin d'attendre leur chargement
529
555
  requestAnimationFrame(() => {
530
556
  requestAnimationFrame(() => {
531
557
  // Maintenant démarrer l'animation
@@ -538,13 +564,32 @@
538
564
  });
539
565
  });
540
566
  }.bind(this)).catch(function() {
541
- // En cas d'erreur, démarrer quand même après un délai
542
- const initDelay = isVertical ? 500 : 300;
543
- setTimeout(() => {
544
- this.initAnimation(element, scrollContainer, mainBlock, {
545
- speed, direction, pauseOnHover, gap, isVertical, useAutoHeight
546
- });
547
- }, initDelay);
567
+ // En cas d'erreur, créer les copies quand même et démarrer
568
+ const repeatBlock1 = mainBlock.cloneNode(true);
569
+ const repeatBlock2 = mainBlock.cloneNode(true);
570
+
571
+ if (!isVertical) {
572
+ scrollContainer.appendChild(mainBlock);
573
+ scrollContainer.appendChild(repeatBlock1);
574
+ scrollContainer.appendChild(repeatBlock2);
575
+ mainContainer.appendChild(scrollContainer);
576
+ } else {
577
+ scrollContainer.appendChild(mainBlock);
578
+ scrollContainer.appendChild(repeatBlock1);
579
+ scrollContainer.appendChild(repeatBlock2);
580
+ mainContainer.appendChild(scrollContainer);
581
+ }
582
+
583
+ element.innerHTML = '';
584
+ element.appendChild(mainContainer);
585
+ element.setAttribute('data-bb-marquee-processed', 'true');
586
+
587
+ const initDelay = isVertical ? 500 : 300;
588
+ setTimeout(() => {
589
+ this.initAnimation(element, scrollContainer, mainBlock, {
590
+ speed, direction, pauseOnHover, gap, isVertical, useAutoHeight
591
+ });
592
+ }, initDelay);
548
593
  }.bind(this));
549
594
  });
550
595
  },
@@ -896,10 +941,14 @@
896
941
 
897
942
  if (direction === (isVertical ? 'bottom' : 'right')) {
898
943
  currentPosition += step * deltaTime;
899
- if (currentPosition >= 0) {
900
- currentPosition = -(finalContentSize + gapSize);
944
+ // Reset BEAUCOUP PLUS TÔT pour "right" aussi (comme pour "left") - Safari
945
+ // Reset à 80% du chemin au lieu d'attendre 100% pour avoir une marge de sécurité
946
+ const resetThreshold = -(0.2 * (finalContentSize + gapSize)); // 80% du chemin (on est à -20%)
947
+ if (currentPosition >= resetThreshold) {
948
+ // Reset en gardant la position relative pour éviter le saut visible
949
+ currentPosition = currentPosition - (finalContentSize + gapSize);
901
950
  }
902
- } else {
951
+ } else {
903
952
  currentPosition -= step * deltaTime;
904
953
  // Reset BEAUCOUP PLUS TÔT pour éviter toute saccade visible (Safari)
905
954
  // Reset à 80% du chemin au lieu d'attendre 100% pour avoir une marge de sécurité
@@ -983,9 +1032,13 @@
983
1032
 
984
1033
  if (direction === (isVertical ? 'bottom' : 'right')) {
985
1034
  currentPosition += step * clampedDelta;
986
- // Reset AVANT que le bloc ne sorte complètement pour éviter la saccade
987
- if (currentPosition >= 0) {
988
- currentPosition = -(contentSize + gapSize);
1035
+ // Reset BEAUCOUP PLUS TÔT pour "right" aussi (comme pour "left")
1036
+ // Reset à 80% du chemin au lieu d'attendre 100% pour avoir une marge de sécurité
1037
+ // Cela garantit que la copie suivante est toujours visible avant le reset
1038
+ const resetThreshold = -(0.2 * (contentSize + gapSize)); // 80% du chemin (on est à -20%)
1039
+ if (currentPosition >= resetThreshold) {
1040
+ // Reset en gardant la position relative pour éviter le saut visible
1041
+ currentPosition = currentPosition - (contentSize + gapSize);
989
1042
  }
990
1043
  } else {
991
1044
  currentPosition -= step * clampedDelta;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.146",
3
+ "version": "1.0.148",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {