JVC_ImageViewer

Naviguer entre les images d'un post sous forme de slideshow en cliquant sur une image sans ouvrir NoelShack.

当前为 2024-09-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name JVC_ImageViewer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.40.11
  5. // @description Naviguer entre les images d'un post sous forme de slideshow en cliquant sur une image sans ouvrir NoelShack.
  6. // @author HulkDu92
  7. // @match https://*.jeuxvideo.com/forums/*
  8. // @match https://jvarchive.com/forums/*
  9. // @grant none
  10. // @run-at document-end
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. class ImageViewer {
  18. constructor() {
  19. if (ImageViewer.instance) {
  20. return ImageViewer.instance;
  21. }
  22.  
  23. this.images = [];
  24. this.currentIndex = 0;
  25. this.overlay = null;
  26. this.imgElement = null;
  27. this.spinner = null;
  28. this.prevButton = null;
  29. this.nextButton = null;
  30. this.closeButton = null;
  31. this.infoText = null;
  32. this.indicatorsContainer = null;
  33. this.indicators = [];
  34. this.zoomLevel = 1;
  35. this.isDragging = false;
  36. this.isPinchZooming = false;
  37. this.startX = 0;
  38. this.startY = 0;
  39. this.offsetX = 0;
  40. this.offsetY = 0;
  41. this.xDown = null;
  42. this.yDown = null;
  43. this.initialDistance = null;
  44. this.startTouches = [];
  45. this.isSwiping = false;
  46. this.isScaling = false;
  47. this.imageElementScale = 1;
  48. this.start = {};
  49. this.isMouseDown = false;
  50. this.isTouchDragging = false;
  51. this.dragTimeout = null;
  52. this.mouseDownX = 0;
  53. this.mouseDownY = 0;
  54. this.initialScale = 1;
  55. this.isViewerOpen = false;
  56. this.thumbnailPanel = null;
  57. this.previousThumbnail = null;
  58. this.touchSensitivityFactor = 0.5; // Pour les appareils tactiles
  59. this.mouseSensitivityFactor = 0.4; // Pour les mouvements de souris
  60.  
  61. ImageViewer.instance = this;
  62.  
  63. this.handlePopState = this.handlePopState.bind(this);
  64.  
  65. this.createOverlay();
  66. this.updateImage();
  67. }
  68.  
  69. // Crée et configure les éléments du visualiseur d'images (overlay, boutons, texte d'information, etc.)
  70. createOverlay() {
  71. this.overlay = this.createElement('div', {
  72. position: 'fixed',
  73. top: 0,
  74. left: 0,
  75. width: '100%',
  76. height: '100%',
  77. backgroundColor: 'rgba(0, 0, 0, 0.8)',
  78. display: 'flex',
  79. alignItems: 'center',
  80. justifyContent: 'center',
  81. zIndex: 10000
  82. });
  83.  
  84. this.imgElement = this.createElement('img', {
  85. maxWidth: '90%',
  86. maxHeight: '80%',
  87. objectFit: 'contain',
  88. transition: 'opacity 0.3s',
  89. opacity: 0,
  90. cursor: 'pointer',
  91. });
  92.  
  93. this.spinner = this.createSpinner();
  94. this.prevButton = this.createButton('<', 'left');
  95. this.nextButton = this.createButton('>', 'right');
  96. this.closeButton = this.createCloseButton();
  97. this.infoText = this.createInfoText();
  98.  
  99. this.indicatorsContainer = this.createElement('div', {
  100. display: 'flex',
  101. justifyContent: 'center',
  102. marginBottom: '10px 0',
  103. position: 'absolute',
  104. bottom: '40px',
  105. });
  106.  
  107. // this.addScrollbarStyles();
  108. this.resetHideButtons();
  109.  
  110. // Événements associés aux boutons et à l'overlay
  111. this.addEventListeners();
  112. this.addInteractionListeners();
  113.  
  114. // Ajout des éléments au DOM
  115. this.overlay.append(this.imgElement, this.spinner, this.infoText, this.prevButton, this.nextButton, this.closeButton);
  116. document.body.appendChild(this.overlay);
  117. }
  118.  
  119. // Crée un élément HTML avec des styles
  120. createElement(tag, styles = {}) {
  121. const element = document.createElement(tag);
  122. Object.assign(element.style, styles);
  123. return element;
  124. }
  125.  
  126. // Crée le bouton précédent ou suivant
  127. createButton(text, position) {
  128.  
  129. const isMobileDevice = isMobile();
  130.  
  131. const button = this.createElement('button', {
  132. position: 'absolute',
  133. [position]: '5px',
  134. backgroundColor: 'rgba(0, 0, 0, 0.6)',
  135. color: 'white',
  136. fontSize: isMobileDevice ? '18px' : '22px',//'22px',
  137. border: 'none',
  138. borderRadius: '50%',
  139. width: isMobileDevice ? '35px' : '40px',//'40px',
  140. height: isMobileDevice ? '35px' : '40px',//'40px',
  141. cursor: 'pointer',
  142. display: 'flex',
  143. alignItems: 'center',
  144. justifyContent: 'center',
  145. boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.6)',
  146. transition: 'background-color 0.3s, transform 0.3s'
  147. });
  148.  
  149.  
  150. //button.textContent = text;*
  151. const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  152. svg.setAttribute('viewBox', '0 0 24 24');
  153. svg.setAttribute('width', '24');
  154. svg.setAttribute('height', '24');
  155. svg.setAttribute('fill', 'white');
  156.  
  157. const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  158. path.setAttribute('d', position === 'left'
  159. ? 'M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6z' // Icône flèche gauche
  160. : 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6z'); // Icône flèche droite
  161. svg.appendChild(path);
  162. button.appendChild(svg);
  163.  
  164. this.addButtonEffects(button);
  165.  
  166. return button;
  167. }
  168.  
  169. // Crée le bouton de fermeture
  170. createCloseButton() {
  171. const isMobileDevice = isMobile();
  172.  
  173. const button = this.createElement('button', {
  174. position: 'absolute',
  175. top: '80px',
  176. right: '10px',
  177. backgroundColor: 'rgba(0, 0, 0, 0.8)',
  178. color: 'white',
  179. fontSize: isMobileDevice ? '18px' : '16px',//'14px',
  180. border: 'none',
  181. borderRadius: '50%',
  182. width: isMobileDevice ? '40px' : '35px', //'35px',
  183. height: isMobileDevice ? '40px' : '35px', //'35px',
  184. cursor: 'pointer',
  185. zIndex: 99999999
  186. });
  187.  
  188. button.textContent = '✕';
  189. this.addButtonEffects(button);
  190.  
  191. return button;
  192. }
  193.  
  194. // Crée la zone d'affichage du texte d'information (numéro d'image)
  195. createInfoText() {
  196. return this.createElement('div', {
  197. position: 'absolute',
  198. top: '80px',
  199. left: '15px',
  200. color: 'white',
  201. fontSize: '12px',
  202. backgroundColor: 'rgba(5, 5, 5, 0.5)',
  203. padding: '5px',
  204. borderRadius: '5px',
  205. zIndex: 10001
  206. });
  207. }
  208.  
  209. // Crée un spinner pour indiquer le chargement de l'image
  210. createSpinner() {
  211. const spinner = this.createElement('div', {
  212. position: 'absolute',
  213. border: '8px solid #f3f3f3',
  214. borderTop: '8px solid #3498db',
  215. borderRadius: '50%',
  216. width: '50px',
  217. height: '50px',
  218. animation: 'spin 1s linear infinite',
  219. zIndex: 10001
  220. });
  221. return spinner;
  222. }
  223.  
  224. addButtonEffects(button) {
  225. button.addEventListener('mouseenter', () => {
  226. button.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
  227. button.style.color = 'black';
  228. button.style.transform = 'scale(1.1)';
  229. });
  230.  
  231. button.addEventListener('mouseleave', () => {
  232. button.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
  233. button.style.color = 'white';
  234. button.style.transform = 'scale(1)';
  235. });
  236.  
  237. button.addEventListener('mousedown', () => {
  238. button.style.transform = 'scale(0.9)';
  239. });
  240.  
  241. button.addEventListener('mouseup', () => {
  242. button.style.transform = 'scale(1.1)';
  243. });
  244. }
  245.  
  246. // Ajoute les événements aux différents éléments du visualiseur
  247. addEventListeners() {
  248. // Bouttons de controles du visualiseur
  249. this.prevButton.addEventListener('click', () => this.changeImage(-1));
  250. this.nextButton.addEventListener('click', () => this.changeImage(1));
  251. this.closeButton.addEventListener('click', () => this.closeViewer());
  252. this.overlay.addEventListener('click', (event) => {
  253. if (event.target === this.overlay) {
  254. this.closeViewer();
  255. }
  256. });
  257.  
  258. // Zoom avec la molette de la souris
  259. this.imgElement.addEventListener('wheel', (event) => this.handleZoom(event));
  260.  
  261.  
  262. // Déplacement lors du zoom (drag)
  263. this.imgElement.addEventListener('mousedown', (event) => this.startDrag(event));
  264. this.imgElement.addEventListener('mousedown', this.handleMouseDown.bind(this));
  265. this.imgElement.addEventListener('mouseup', this.handleMouseUp.bind(this));
  266.  
  267. // Touches avec les doigts
  268. this.imgElement.addEventListener('touchstart', (event) => this.handleTouchEvent(event));
  269. this.imgElement.addEventListener('touchmove', (event) => this.handleTouchEvent(event));
  270. this.imgElement.addEventListener('touchend', (event) => this.handleTouchEvent(event));
  271.  
  272. // Ouvrir l'image dans une no6velle fenêtre
  273. this.imgElement.addEventListener('click', () => {
  274. if (!this.isDragging) {
  275. window.open(this.images[this.currentIndex].href, '_blank');
  276. }
  277. });
  278.  
  279. // Touches du clavier
  280. document.addEventListener('keydown', (event) => this.handleKeyboardEvents(event));
  281. }
  282.  
  283. // Fonctions pour gérer les touches du clavier
  284. handleKeyboardEvents(event) {
  285. switch (event.key) {
  286. case 'ArrowLeft':
  287. case 'ArrowUp':
  288. this.changeImage(-1);
  289. break;
  290. case 'ArrowRight':
  291. case 'ArrowDown':
  292. this.changeImage(1);
  293. break;
  294. case 'Escape':
  295. event.preventDefault();
  296. this.closeViewer();
  297. break;
  298. }
  299. }
  300.  
  301. // Fonctions pour gérer les touches tactiles
  302. handleTouchEvent(event) {
  303. switch (event.type) {
  304. case 'touchstart':
  305. if (event.touches.length === 1) {
  306. if (this.imageElementScale > 1) {
  307. // Si l'image est zoomée, permettre le déplacement (drag)
  308. this.startDrag(event);
  309. } else {
  310. // Sinon, démarrer le swipe
  311. this.handleSwipeStart(event);
  312. }
  313. } else if (event.touches.length === 2) {
  314. // Démarrer le pinch zoom
  315. this.handlePinchStart(event);
  316. }
  317. break;
  318.  
  319. case 'touchmove':
  320. if (event.touches.length === 1) {
  321. if (this.imageElementScale > 1) {
  322. // Si l'image est zoomée, permettre le déplacement (drag)
  323. this.onDrag(event);
  324. } else {
  325. this.handleSwipeMove(event);
  326. }
  327. } else if (event.touches.length === 2) {
  328. // Gérer le pinch zoom
  329. this.handlePinchMove(event);
  330. }
  331. break;
  332.  
  333. case 'touchend':
  334. console.log("touchend");
  335. console.log("touch lenght = " + event.touches.length);
  336. console.log("image scale = " + this.imageElementScale > 1);
  337. if (event.touches.length === 1) {
  338. if (this.imageElementScale > 1) {
  339. this.endDrag(event);
  340. } else {
  341. this.handleSwipeEnd(event);
  342. }
  343. }
  344. else if (event.touches.length === 2) {
  345. this.handlePinchEnd(event);
  346. }
  347. break;
  348. }
  349. }
  350.  
  351.  
  352. // Gestion du début de l'interaction tactile
  353. handleSwipeStart(event) {
  354. if (event.touches.length === 1) {
  355. if(this.isPinchZooming) {
  356. console.log("is still pinch zooming");
  357. return;
  358. }
  359. // Commencer le swipe
  360. this.isSwiping = true;
  361. this.startX = event.touches[0].clientX;
  362. this.startY = event.touches[0].clientY;
  363. }
  364. }
  365.  
  366. // Gestion du mouvement tactile pour swipe
  367. handleSwipeMove(event) {
  368. if (this.isSwiping && event.touches.length === 1) {
  369. this.currentX = event.touches[0].clientX;
  370. this.currentY = event.touches[0].clientY;
  371. }
  372. }
  373.  
  374. // Gestion de la fin de l'interaction tactile
  375. handleSwipeEnd(event) {
  376. if (event.touches.length < 2) {
  377. this.initialDistance = null;
  378. }
  379. if (this.isSwiping) {
  380. this.isSwiping = false;
  381. const deltaX = this.currentX - this.startX;
  382. const deltaY = this.currentY - this.startY;
  383.  
  384. // Si le mouvement est suffisamment grand, on change d'image
  385. if (Math.abs(deltaX) > 50) {
  386. if (deltaX > 0) {
  387. this.showPreviousImage();
  388. } else {
  389. this.showNextImage();
  390. }
  391. }
  392.  
  393. // Si le mouvement est suffisamment grand verticalement, on ferme le visualiseur
  394. if (Math.abs(deltaY) > 50) {
  395. this.closeViewer();
  396. }
  397. }
  398.  
  399. // l'image revient à sa taille d'origine, réinitialiser le zIndex
  400. this.imgElement.style.zIndex = '';
  401. }
  402.  
  403. // Calculate distance between two fingers
  404. distance(event){
  405. return Math.hypot(event.touches[0].pageX - event.touches[1].pageX, event.touches[0].pageY - event.touches[1].pageY);
  406. }
  407.  
  408. // Gestion du début de l'interaction tactile pour le pincement
  409. handlePinchStart(event) {
  410. if (event.touches.length === 2) {
  411. event.preventDefault(); // Prevent page scroll
  412. this.isPinchZooming = true;
  413.  
  414. // Calculate where the fingers have started on the X and Y axis
  415. this.startX = (event.touches[0].pageX + event.touches[1].pageX) / 2;
  416. this.startY = (event.touches[0].pageY + event.touches[1].pageY) / 2;
  417. this.start.distance = this.distance(event);
  418.  
  419. // Save the current scale to use it as a base for the new scale
  420. this.initialScale = this.imageElementScale || 1; // Use 1 if there's no previous zoom
  421. }
  422. }
  423.  
  424. // Gestion du mouvement tactile pour le pincement (zoom)
  425. handlePinchMove(event) {
  426. if (event.touches.length === 2) {
  427. event.preventDefault(); // Prevent page scroll
  428.  
  429. // Safari provides event.scale as two fingers move on the screen
  430. // For other browsers just calculate the scale manually
  431. let scale;
  432. if (event.scale) {
  433. scale = event.scale;
  434. } else {
  435. const deltaDistance = this.distance(event);
  436. scale = (deltaDistance / this.start.distance); //* this.touchSensitivityFactor;
  437. }
  438. // this.imageElementScale = Math.min(Math.max(1, scale), 4);
  439. // Multiply the new scale by the starting scale to retain the zoom level
  440. // this.imageElementScale = Math.min(Math.max(1, this.startScale * scale), 4);
  441. this.imageElementScale = Math.min(Math.max(1, this.initialScale * scale), 4);
  442.  
  443.  
  444. // Calculate how much the fingers have moved on the X and Y axis
  445. const deltaX = (((event.touches[0].pageX + event.touches[1].pageX) / 2) - this.startX) * 2; // x2 for accelarated movement
  446. const deltaY = (((event.touches[0].pageY + event.touches[1].pageY) / 2) - this.startY) * 2; // x2 for accelarated movement
  447.  
  448. // Transform the image to make it grow and move with fingers
  449. const transform = `translate3d(${deltaX}px, ${deltaY}px, 0) scale(${this.imageElementScale})`;
  450. this.imgElement.style.transform = transform;
  451. this.imgElement.style.WebkitTransform = transform;
  452. this.imgElement.style.zIndex = "9999";
  453. this.closeButton.style.zIndex = "10003";
  454. }
  455. }
  456.  
  457. // Gestion de la fin de l'interaction tactile pour le pincement
  458. handlePinchEnd(event) {
  459. console.log("avant set timeout");
  460. if (event.touches.length < 2) {
  461. // Ajouter un délai pour réinitialiser le drag (empeche les conflits avec le clic)
  462. this.dragTimeout = setTimeout(() => {
  463. console.log("après set timeout");
  464. this.isPinchZooming = false;
  465. this.dragTimeout = null;
  466. }, 100);
  467.  
  468. // Si l'image est revenue à sa taille d'origine, réinitialiser le zIndex
  469. if (this.imageElementScale <= 1) {
  470. this.imgElement.style.zIndex = '';
  471. }
  472. // else {
  473. // this.imgElement.style.zIndex = 10002;
  474. // }
  475.  
  476. }
  477. }
  478.  
  479. // Fonction pour calculer la distance entre deux points de contact
  480. getTouchDistance(touches) {
  481. const [touch1, touch2] = touches;
  482. const deltaX = touch2.clientX - touch1.clientX;
  483. const deltaY = touch2.clientY - touch1.clientY;
  484. return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
  485. }
  486.  
  487. handleZoom(event) {
  488. event.preventDefault();
  489. const zoomIncrement = 0.07;
  490.  
  491. /*if (event.deltaY < 0) {
  492. this.imageElementScale += zoomIncrement; // Zoomer
  493. } else {
  494. this.imageElementScale = Math.max(1, this.zoomLevel - zoomIncrement); // Dézoomer, mais ne pas descendre sous 1
  495. }*/
  496. if (event.deltaY < 0) {
  497. this.imageElementScale = Math.min(4, this.imageElementScale + zoomIncrement); // Limite max
  498. } else {
  499. this.imageElementScale = Math.max(1, this.imageElementScale - zoomIncrement); // Limite min
  500. }
  501.  
  502. this.imgElement.style.transform = `scale(${this.imageElementScale}) translate(${this.offsetX}px, ${this.offsetY}px)`;
  503.  
  504. // Si le niveau de zoom est supérieur à 1, mettre l'image devant les boutons
  505. if (this.zoomLevel > 1) {
  506. this.imgElement.style.zIndex = 10002;
  507. } else {
  508. // Si le zoom revient à la normale, remettre le zIndex initial
  509. this.imgElement.style.zIndex = '';
  510. }
  511. }
  512.  
  513.  
  514. startDrag(event) {
  515. event.preventDefault(); // Empêche la sélection de l'image
  516.  
  517. // Gestion tactile
  518. if (event.type === 'touchstart') {
  519. this.isTouchDragging = true;
  520. this.startX = event.touches[0].clientX; //- this.offsetX;
  521. this.startY = event.touches[0].clientY; //- this.offsetY;
  522. } else {
  523. // Gestion avec la souris
  524. this.isMouseDown = true;
  525. this.startX = event.clientX; //- this.offsetX;
  526. this.startY = event.clientY; // - this.offsetY;
  527. }
  528.  
  529. this.isDragging = true;
  530. this.imgElement.style.cursor = 'grabbing';
  531. this.imgElement.style.userSelect = 'none';
  532.  
  533. // Ajouter des listeners sur le document pour capturer les mouvements
  534. if (event.touches) {
  535. document.addEventListener('touchmove', this.onDragBound = this.onDrag.bind(this));
  536. document.addEventListener('touchend', this.endDragBound = this.endDrag.bind(this));
  537. } else {
  538. document.addEventListener('mousemove', this.onDragBound = this.onDrag.bind(this));
  539. document.addEventListener('mouseup', this.endDragBound = this.endDrag.bind(this));
  540. }
  541. }
  542.  
  543.  
  544. onDrag(event) {
  545. if (!this.isDragging) return;
  546.  
  547. event.preventDefault();
  548.  
  549. let deltaX, deltaY;
  550.  
  551. if (event.type === 'touchmove') {
  552. // Gestion tactile
  553. deltaX = (event.touches[0].clientX - this.startX) * this.touchSensitivityFactor;
  554. deltaY = (event.touches[0].clientY - this.startY) * this.touchSensitivityFactor;
  555. } else {
  556. // Gestion avec la souris
  557. deltaX = (event.clientX - this.startX) * this.mouseSensitivityFactor;
  558. deltaY = (event.clientY - this.startY) * this.mouseSensitivityFactor;
  559. }
  560.  
  561. // Appliquer la translation ajustée par le facteur de sensibilité
  562. this.offsetX += deltaX;
  563. this.offsetY += deltaY;
  564.  
  565. // Mettre à jour les points de départ pour le prochain déplacement
  566. this.startX = event.type === 'touchmove' ? event.touches[0].clientX : event.clientX;
  567. this.startY = event.type === 'touchmove' ? event.touches[0].clientY : event.clientY;
  568.  
  569. // Appliquer la transformation avec le zoom actuel, en se déplaçant dans l'image
  570. this.imgElement.style.transform = `scale(${this.imageElementScale}) translate(${this.offsetX}px, ${this.offsetY}px)`;
  571. }
  572.  
  573.  
  574. endDrag(event) {
  575. this.imgElement.style.cursor = 'grab';
  576.  
  577. // Retirer les listeners
  578. if (event.type === 'touchend') {
  579. this.isTouchDragging = false; // Réinitialise l'état tactile
  580. document.removeEventListener('touchmove', this.onDragBound);
  581. document.removeEventListener('touchend', this.endDragBound);
  582. } else {
  583. this.isMouseDown = false; // Réinitialise l'état de la souris
  584. document.removeEventListener('mousemove', this.onDragBound);
  585. document.removeEventListener('mouseup', this.endDragBound);
  586. }
  587.  
  588. // Ajouter un délai pour réinitialiser le drag (empeche les conflits avec le clic)
  589. this.dragTimeout = setTimeout(() => {
  590. this.isDragging = false;
  591. this.imgElement.style.cursor = 'pointer';
  592. this.dragTimeout = null;
  593. }, 100);
  594. }
  595.  
  596.  
  597. handleMouseDown(event) {
  598. this.isMouseDown = true;
  599. this.mouseDownX = event.clientX;
  600. this.mouseDownY = event.clientY;
  601.  
  602. // Démarrer le drag après un délai pour éviter le drag lors des clics courts
  603. this.dragTimeout = setTimeout(() => {
  604. if (this.isMouseDown) {
  605. this.startDrag(event);
  606. }
  607. }, 200);
  608. }
  609.  
  610. handleMouseUp(event) {
  611. this.isMouseDown = false;
  612.  
  613. // Si le délai pour démarrer le drag est encore en cours, le nettoyer
  614. if (this.dragTimeout) {
  615. clearTimeout(this.dragTimeout);
  616. this.dragTimeout = null;
  617. }
  618.  
  619. // Vérifier si le mouvement est suffisant pour considérer que c'est un drag
  620. const movedX = Math.abs(event.clientX - this.mouseDownX);
  621. const movedY = Math.abs(event.clientY - this.mouseDownY);
  622.  
  623. if (movedX < 5 && movedY < 5) {
  624. // handleImageClick(event); // Traiter le clic si le mouvement est minime
  625. }
  626. }
  627.  
  628. // Réinitialiser le zoom de l'image
  629. resetZoom() {
  630. this.imgElement.style.transform = 'scale(1)';
  631. this.imgElement.style.transformOrigin = '0 0';
  632. }
  633.  
  634. // Réinitialiser la position du drag de l'image
  635. resetDrag() {
  636. this.imgElement.style.left = '0px';
  637. this.imgElement.style.top = '0px';
  638. }
  639.  
  640. // Met à jour l'image affichée dans le visualiseur
  641. updateImage() {
  642. if (this.currentIndex >= 0 && this.currentIndex < this.images.length) {
  643. const imageUrl = this.images[this.currentIndex].href;
  644.  
  645. this.imgElement.src = imageUrl;
  646. this.infoText.textContent = `${this.currentIndex + 1} / ${this.images.length}`;
  647. this.spinner.style.display = 'block';
  648.  
  649. this.toggleButtonState();
  650.  
  651. this.imgElement.onload = () => {
  652. this.imgElement.style.opacity = 1;
  653. this.spinner.style.display = 'none';
  654.  
  655. // Réinitialiser le zoom et la position du drag
  656. this.resetZoom();
  657. this.resetDrag();
  658.  
  659. // Calcul de la position des boutons
  660. const imgRect = this.imgElement.getBoundingClientRect();
  661. const isMobileDevice = isMobile(); // Détection des mobiles
  662.  
  663. if (isMobileDevice) {
  664. // pass
  665. } else {
  666. const margin = 30;
  667. this.prevButton.style.left = `${imgRect.left - this.prevButton.offsetWidth - margin}px`;
  668. this.nextButton.style.right = `${window.innerWidth - imgRect.right - this.nextButton.offsetWidth - margin}px`;
  669. }
  670.  
  671. this.focusOnThumbnail();
  672. };
  673.  
  674. this.imgElement.onerror = () => this.handleImageError();
  675. }
  676. }
  677.  
  678. // Gestion des erreurs de chargement d'image
  679. handleImageError() {
  680. const miniUrl = this.images[this.currentIndex].querySelector('img').src;
  681. const extensions = ['.jpg', '.png', '.jpeg'];
  682. const baseUrl = miniUrl.replace('/minis/', '/fichiers/');
  683.  
  684. const tryNextExtension = (index) => {
  685. if (index >= extensions.length) {
  686. // Si toutes les extensions échouent, tenter l'URL originale
  687. this.imgElement.src = miniUrl;
  688. return;
  689. }
  690.  
  691. // Remplacer l'extension et mettre à jour l'URL
  692. const updatedUrl = baseUrl.replace(/\.(jpg|png|jpeg)$/, extensions[index]);
  693.  
  694. // Tester l'URL avec un élément Image temporaire
  695. const imgTest = new Image();
  696. imgTest.src = updatedUrl;
  697.  
  698. imgTest.onload = () => {
  699. // Si l'image se charge avec succès, l'assigner à l'élément d'image principal
  700. this.imgElement.src = updatedUrl;
  701. };
  702.  
  703. imgTest.onerror = () => {
  704. console.log("Error loading: " + updatedUrl);
  705. tryNextExtension(index + 1);
  706. };
  707. };
  708.  
  709. // Commencer les essais avec la première extension
  710. tryNextExtension(0);
  711. }
  712.  
  713. // Change d'image en fonction de la direction (suivant/précédent)
  714. changeImage(direction) {
  715. this.currentIndex = (this.currentIndex + direction + this.images.length) % this.images.length;
  716. this.imgElement.style.opacity = 0;
  717. this.spinner.style.display = 'block';
  718. this.updateImage();
  719. }
  720.  
  721. showPreviousImage() {
  722. this.changeImage(-1);
  723. }
  724.  
  725. showNextImage() {
  726. this.changeImage(1);
  727. }
  728.  
  729. // Met à jour le focus sur la miniature correspondante
  730. focusOnThumbnail() {
  731. // Obtenez la miniature actuelle
  732. const thumbnails = this.thumbnailPanel ? this.thumbnailPanel.querySelectorAll('img') : [];
  733. const currentThumbnail = thumbnails[this.currentIndex];
  734.  
  735. // Réinitialiser la bordure de la miniature précédente si elle existe
  736. if (this.previousThumbnail) {
  737. this.previousThumbnail.style.border = 'none';
  738. this.previousThumbnail.style.transform = 'scale(1)';
  739. this.previousThumbnail.style.boxShadow = 'none';
  740. }
  741.  
  742. // Mettre à jour la bordure de la miniature actuelle
  743. if (currentThumbnail) {
  744. //currentThumbnail.style.border = '2px solid rgba(40, 40, 40, 0.8)'; // Appliquer la bordure
  745. currentThumbnail.style.boxShadow = '0 0 0 2px rgba(40, 40, 40, 0.8), 0 0 10px rgba(40, 40, 40, 0.5)'; // Ombre portée
  746. currentThumbnail.style.transform = 'scale(1.15)'; // Agrandir l'élément
  747. currentThumbnail.parentElement.scrollIntoView({ behavior: 'smooth', inline: 'center' });
  748. }
  749.  
  750. // Mettre à jour la référence de la miniature précédente
  751. this.previousThumbnail = currentThumbnail;
  752. }
  753.  
  754. // Désactive ou active les boutons suivant/précédent en fonction de l'index actuel
  755. toggleButtonState() {
  756. if (this.currentIndex === 0) {
  757. // this.prevButton.disabled = true;
  758. this.prevButton.style.opacity = 0.5;
  759. this.prevButton.style.cursor = 'initial';
  760. } else {
  761. // this.prevButton.disabled = false;
  762. this.prevButton.style.opacity = 1;
  763. this.prevButton.style.cursor = 'pointer';
  764. }
  765.  
  766. if (this.currentIndex === this.images.length - 1) {
  767. // this.nextButton.disabled = true;
  768. this.nextButton.style.opacity = 0.5;
  769. this.nextButton.style.cursor = 'initial';
  770. } else {
  771. // this.nextButton.disabled = false;
  772. this.nextButton.style.opacity = 1;
  773. this.nextButton.style.cursor = 'pointer';
  774. }
  775. }
  776.  
  777. // Cacher temporairement le menu JVC
  778. toggleMenuVisibility(isVisible) {
  779. const menu = document.querySelector('.header__bottom');
  780. if (menu) {
  781. menu.style.display = isVisible ? '' : 'none';
  782. }
  783. }
  784.  
  785. // Fonction pour créer et afficher le panneau des miniatures
  786. toggleThumbnailPanel() {
  787. if (this.thumbnailPanel) {
  788. this.closeThumbnailPanel(); // Ferme le panneau si déjà ouvert
  789. return;
  790. }
  791.  
  792. // Créer le panneau
  793. this.thumbnailPanel = this.createElement('div', {
  794. position: 'fixed',
  795. bottom: '10px',
  796. left: '50%',
  797. transform: 'translateX(-50%)',
  798. border: '0px solid',
  799. padding: '0px',
  800. zIndex: '99999999',
  801. maxHeight: '80px',
  802. maxWidth: '80%',
  803. overflowY: 'hidden',
  804. overflowX: 'auto',
  805. display: 'flex',
  806. alignItems: 'center',
  807. backgroundColor: 'transparent',
  808. });
  809.  
  810. // Conteneur pour le défilement horizontal
  811. const scrollContainer = this.createElement('div', {
  812. display: 'flex',
  813. overflowX: 'auto',
  814. whiteSpace: 'nowrap',
  815. maxWidth: '100%',
  816. });
  817.  
  818. // Ajout des images au conteneur
  819. this.images.forEach((image, index) => {
  820. const imgContainer = this.createElement('div', {
  821. display: 'inline-block',
  822. width: '50px',
  823. height: '50px',
  824. margin: '5px',
  825. padding: '4px',
  826. transition: 'transform 0.3s',
  827. });
  828.  
  829. const imgElement = this.createElement('img');
  830. imgElement.src = image.querySelector('img') ? image.querySelector('img').src : image.href || image.thumbnail;
  831.  
  832. imgElement.alt = `Image ${index + 1}`;
  833. imgElement.style.width = 'auto';
  834. imgElement.style.height = '100%';
  835. imgElement.style.objectFit = 'cover';
  836. imgElement.style.cursor = 'pointer';
  837.  
  838. imgElement.addEventListener('click', () => {
  839. this.images.forEach((_, i) => {
  840. const container = scrollContainer.children[i];
  841. container.querySelector('img').style.border = 'none';
  842. });
  843. //imgElement.style.border = '2px solid blue';
  844. this.currentIndex = index;
  845. this.updateImage();
  846. //imgContainer.scrollIntoView({ behavior: 'smooth', inline: 'center' });
  847. });
  848.  
  849. imgContainer.appendChild(imgElement);
  850. scrollContainer.appendChild(imgContainer);
  851. });
  852.  
  853. this.thumbnailPanel.appendChild(scrollContainer);
  854. this.overlay.appendChild(this.thumbnailPanel);
  855.  
  856. this.focusOnThumbnail();
  857. }
  858.  
  859.  
  860. // Ecouteurs d'événements pour réinitialiser le timer
  861. addInteractionListeners() {
  862. this.overlay.addEventListener('mousemove', this.resetHideButtons.bind(this));
  863. this.overlay.addEventListener('click', this.resetHideButtons.bind(this));
  864. this.overlay.addEventListener('touchstart', this.resetHideButtons.bind(this));
  865. }
  866.  
  867. // Réinitialisez le timer pour cacher les boutons
  868. resetHideButtons() {
  869. if (this.hideButtonsTimeout) {
  870. clearTimeout(this.hideButtonsTimeout);
  871. }
  872. this.toggleButtonsVisibility(true);
  873. this.hideButtonsTimeout = setTimeout(() => {
  874. this.toggleButtonsVisibility(false); // Cachez les boutons après 3 secondes
  875. }, 2500);
  876. }
  877.  
  878. // Changez la visibilité des boutons
  879. toggleButtonsVisibility(visible) {
  880. const displayValue = visible ? 'flex' : 'none';
  881. if (this.prevButton) {
  882. this.prevButton.style.display = displayValue;
  883. }
  884. if (this.nextButton) {
  885. this.nextButton.style.display = displayValue;
  886. }
  887. if (this.thumbnailPanel) {
  888. this.thumbnailPanel.style.display = displayValue;
  889. }
  890. if (this.infoText) {
  891. this.infoText.style.display = displayValue;
  892. }
  893. }
  894.  
  895. // Fonction pour fermer le panneau des miniatures
  896. closeThumbnailPanel(thumbnailPanel) {
  897. if (this.thumbnailPanel && this.overlay.contains(this.thumbnailPanel)) {
  898. this.overlay.removeChild(this.thumbnailPanel);
  899. this.thumbnailPanel = null;
  900. }
  901. }
  902.  
  903. closeViewer() {
  904. if (this.overlay) {
  905. this.handleCloseViewer(); // Ferme le visualiseur
  906. history.back(); // Supprime l'état ajouté par pushState
  907. }
  908. }
  909.  
  910.  
  911. // Ferme le visualiseur d'images
  912. handleCloseViewer() {
  913. if (this.overlay) {
  914. document.body.removeChild(this.overlay);
  915.  
  916. // Ferme le panneau des miniatures si ouvert
  917. if (this.thumbnailPanel) {
  918. this.closeThumbnailPanel(this.thumbnailPanel);
  919. }
  920.  
  921. window.removeEventListener('popstate', this.handlePopState);
  922.  
  923. this.overlay = null;
  924. this.isViewerOpen = false;
  925. ImageViewer.instance = null; // Réinitialise l'instance singleton
  926.  
  927. this.toggleMenuVisibility(true);
  928. }
  929. }
  930.  
  931. openViewer(images, currentIndex) {
  932. if (this.overlay) {
  933. this.images = images;
  934. this.currentIndex = currentIndex;
  935. this.updateImage();
  936. this.toggleThumbnailPanel();
  937. } else {
  938. new ImageViewer();
  939. this.images = images;
  940. this.currentIndex = currentIndex;
  941. this.createOverlay();
  942. this.updateImage();
  943. this.toggleThumbnailPanel();
  944. }
  945. this.isViewerOpen = true;
  946.  
  947. this.addHistoryState()
  948. window.addEventListener('popstate', this.handlePopState); // Ecouter l'événement bouton back du navigateur
  949.  
  950. this.toggleMenuVisibility(false);
  951. }
  952.  
  953. handlePopState(event) {
  954. if (ImageViewer.instance) {
  955. event.preventDefault();
  956. this.handleCloseViewer();
  957. }
  958. }
  959.  
  960. // Ajouter une entrée dans l'historique
  961. addHistoryState() {
  962. history.pushState({ viewerOpen: true }, '');
  963. }
  964. }
  965.  
  966. function addSpinnerStyles() {
  967. const style = document.createElement('style');
  968. style.textContent = `
  969. @keyframes spin {
  970. 0% { transform: rotate(0deg); }
  971. 100% { transform: rotate(360deg); }
  972. }
  973. .spinner {
  974. width: 50px;
  975. height: 50px;
  976. border-radius: 50%;
  977. border: 5px solid transparent;
  978. border-top: 5px solid rgba(0, 0, 0, 0.1);
  979. background: conic-gradient(from 0deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0));
  980. animation: spin 1s linear infinite;
  981. }
  982. `;
  983. document.head.appendChild(style);
  984. }
  985.  
  986.  
  987. const parentClasses = '.txt-msg, .message, .conteneur-message.mb-3, .bloc-editor-forum, .signature-msg';
  988. const linkSelectors = parentClasses.split(', ').map(cls => `${cls} a`);
  989.  
  990. // Ajouter des écouteurs d'événements aux images sur la page
  991. function addListeners() {
  992. linkSelectors.forEach(selector => {
  993. document.querySelectorAll(selector).forEach(link => {
  994. link.addEventListener('click', handleImageClick, true);
  995. });
  996. });
  997. }
  998.  
  999. function handleImageClick(event) {
  1000. // Si Ctrl ou Cmd est enfoncé, ne pas ouvrir l'ImageViewer
  1001. if (event.ctrlKey || event.metaKey) {
  1002. return;
  1003. }
  1004.  
  1005. const imgElement = this.querySelector('img');
  1006. if (imgElement) {
  1007. event.preventDefault();
  1008. const closestElement = this.closest(parentClasses);
  1009. if (closestElement) {
  1010. const images = Array.from(closestElement.querySelectorAll('a')).filter(imgLink => imgLink.querySelector('img'));
  1011. const currentIndex = images.indexOf(this);
  1012.  
  1013. const viewer = new ImageViewer();
  1014. viewer.openViewer(images, currentIndex);
  1015. }
  1016. }
  1017. }
  1018.  
  1019.  
  1020. function isMobile() {
  1021. return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  1022. }
  1023.  
  1024.  
  1025. function main() {
  1026. addSpinnerStyles();
  1027. addListeners();
  1028.  
  1029. const observer = new MutationObserver(() => addListeners());
  1030. observer.observe(document, { childList: true, subtree: true });
  1031. }
  1032.  
  1033. main();
  1034.  
  1035.  
  1036. })();