JVC_ImageViewer

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

目前为 2024-09-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         JVC_ImageViewer
// @namespace    http://tampermonkey.net/
// @version      1.35.2
// @description  Naviguer entre les images d'un post sous forme de slideshow en cliquant sur une image sans ouvrir NoelShack.
// @author       HulkDu92
// @match        https://*.jeuxvideo.com/forums/*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    class ImageViewer {
        constructor(images, currentIndex) {
            this.images = images;
            this.currentIndex = currentIndex;
            this.overlay = null;
            this.imgElement = null;
            this.spinner = null;
            this.prevButton = null;
            this.nextButton = null;
            this.closeButton = null;
            this.infoText = null;
            this.zoomLevel = 1; // Niveau de zoom initial
            this.isDragging = false; // Pour savoir si l'utilisateur est en train de glisser
            this.startX = 0; // Position de départ sur l'axe X lors du glissement
            this.startY = 0; // Position de départ sur l'axe Y lors du glissement
            this.offsetX = 0; // Décalage de l'image sur l'axe X
            this.offsetY = 0; // Décalage de l'image sur l'axe Y

            this.createOverlay();
            this.updateImage();
        }

        // Crée et configure les éléments du visualiseur d'images (overlay, boutons, texte d'information, etc.)
        createOverlay() {
            this.overlay = this.createElement('div', {
                position: 'fixed',
                top: 0,
                left: 0,
                width: '100%',
                height: '100%',
                backgroundColor: 'rgba(0, 0, 0, 0.8)',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                zIndex: 10000
            });

            this.imgElement = this.createElement('img', {
                maxWidth: '90%',
                maxHeight: '80%',
                objectFit: 'contain',
                transition: 'opacity 0.3s',
                opacity: 0,
                cursor: 'pointer'
            });

            this.spinner = this.createSpinner();
            this.prevButton = this.createButton('<', 'left');
            this.nextButton = this.createButton('>', 'right');
            this.closeButton = this.createCloseButton();
            this.infoText = this.createInfoText();

            // Événements associés aux boutons et à l'overlay
            this.addEventListeners();

            // Ajout des éléments au DOM
            this.overlay.append(this.imgElement, this.spinner, this.prevButton, this.nextButton, this.closeButton, this.infoText);
            document.body.appendChild(this.overlay);
        }

        // Crée un élément HTML avec des styles
        createElement(tag, styles = {}) {
            const element = document.createElement(tag);
            Object.assign(element.style, styles);
            return element;
        }

        // Crée le bouton précédent ou suivant
        createButton(text, position) {
          const button = this.createElement('button', {
              position: 'absolute',
              [position]: '10px',
              backgroundColor: 'rgba(0, 0, 0, 0.6)',
              color: 'white',
              fontSize: '22px',
              border: 'none',
              borderRadius: '50%',
              width: '40px',
              height: '40px',
              cursor: 'pointer',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.6)',
              transition: 'background-color 0.3s, transform 0.3s'
          });

          button.textContent = text;
          this.addButtonEffects(button);

          return button;
      }

        // Crée le bouton de fermeture
        createCloseButton() {
            const button = this.createElement('button', {
                position: 'absolute',
                top: '80px',
                right: '10px',
                backgroundColor: 'rgba(0, 0, 0, 0.8)',
                color: 'white',
                fontSize: '14px',
                border: 'none',
                borderRadius: '50%',
                width: '35px',
                height: '35px',
                cursor: 'pointer',
                zIndex: 10001
            });

           button.textContent = '✕';
           this.addButtonEffects(button);

          return button;
        }

        // Crée la zone d'affichage du texte d'information (numéro d'image)
        createInfoText() {
            return this.createElement('div', {
                position: 'absolute',
                bottom: '10px',
                right: '10px',
                color: 'white',
                fontSize: '16px',
                backgroundColor: 'rgba(0, 0, 0, 0.5)',
                padding: '5px',
                borderRadius: '5px',
                zIndex: 10001
            });
        }

        // Crée un spinner pour indiquer le chargement de l'image
        createSpinner() {
            const spinner = this.createElement('div', {
                position: 'absolute',
                border: '8px solid #f3f3f3',
                borderTop: '8px solid #3498db',
                borderRadius: '50%',
                width: '50px',
                height: '50px',
                animation: 'spin 1s linear infinite',
                zIndex: 10001
            });
            return spinner;
        }

        addButtonEffects(button) {
            button.addEventListener('mouseenter', () => {
                button.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
                button.style.color = 'black';
                button.style.transform = 'scale(1.1)';
            });

            button.addEventListener('mouseleave', () => {
                button.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
                button.style.color = 'white';
                button.style.transform = 'scale(1)';
            });

            button.addEventListener('mousedown', () => {
                button.style.transform = 'scale(0.9)';
            });

            button.addEventListener('mouseup', () => {
                button.style.transform = 'scale(1.1)';
            });
        }

        // Ajoute les événements aux différents éléments du visualiseur
        addEventListeners() {
            this.prevButton.addEventListener('click', () => this.changeImage(-1));
            this.nextButton.addEventListener('click', () => this.changeImage(1));
            this.closeButton.addEventListener('click', () => this.closeViewer());
            this.overlay.addEventListener('click', (event) => {
                if (event.target === this.overlay) {
                    this.closeViewer();
                }
            });

            // Zoom avec la molette de la souris
            this.imgElement.addEventListener('wheel', (event) => this.handleZoom(event));

            // Déplacement lors du zoom (drag)
            this.imgElement.addEventListener('mousedown', (event) => this.startDrag(event));
            document.addEventListener('mousemove', (event) => this.onDrag(event));
            document.addEventListener('mouseup', () => this.endDrag());

            // Déplacement pour appareils tactiles (pincement et drag)
            this.imgElement.addEventListener('touchstart', (event) => this.startTouchDrag(event));
            this.imgElement.addEventListener('touchmove', (event) => this.onTouchDrag(event));
            this.imgElement.addEventListener('touchend', () => this.endDrag());

            this.imgElement.addEventListener('click', () => {
                window.open(this.images[this.currentIndex].href, '_blank');
            });
        }

        handleZoom(event) {
            event.preventDefault();
            const zoomIncrement = 0.1;

            if (event.deltaY < 0) {
                this.zoomLevel += zoomIncrement; // Zoomer
            } else {
                this.zoomLevel = Math.max(1, this.zoomLevel - zoomIncrement); // Dézoomer, mais ne pas descendre sous 1
            }

            this.imgElement.style.transform = `scale(${this.zoomLevel}) translate(${this.offsetX}px, ${this.offsetY}px)`;
        }

        startDrag(event) {
            this.isDragging = true;
            this.startX = event.clientX - this.offsetX;
            this.startY = event.clientY - this.offsetY;
            this.imgElement.style.cursor = 'grabbing';
        }

        onDrag(event) {
            if (!this.isDragging) return;

            this.offsetX = event.clientX - this.startX;
            this.offsetY = event.clientY - this.startY;

            this.imgElement.style.transform = `scale(${this.zoomLevel}) translate(${this.offsetX}px, ${this.offsetY}px)`;
        }

        endDrag() {
            this.isDragging = false;
            this.imgElement.style.cursor = 'grab';
        }

        startTouchDrag(event) {
            if (event.touches.length === 1) { // S'assurer qu'il y a un seul doigt
                this.isDragging = true;
                this.startX = event.touches[0].clientX - this.offsetX;
                this.startY = event.touches[0].clientY - this.offsetY;
            }
        }

        onTouchDrag(event) {
            if (!this.isDragging || event.touches.length !== 1) return;

            this.offsetX = event.touches[0].clientX - this.startX;
            this.offsetY = event.touches[0].clientY - this.startY;

            this.imgElement.style.transform = `scale(${this.zoomLevel}) translate(${this.offsetX}px, ${this.offsetY}px)`;
        }


          // Gestion du zoom pour appareils tactiles (pincement)
          handlePinchZoom(event) {
              this.zoomLevel += event.scale - 1; // Ajuste le zoom en fonction de l'échelle du pincement
              this.imgElement.style.transform = `scale(${Math.max(1, this.zoomLevel)})`; // Ne pas descendre en dessous d'un zoom de 1
          }

        // Met à jour l'image affichée dans le visualiseur
        updateImage() {
            const imageUrl = this.images[this.currentIndex].href;
            this.imgElement.src = imageUrl;
            this.infoText.textContent = `${this.currentIndex + 1} / ${this.images.length}`;
            this.spinner.style.display = 'block';

            this.toggleButtonState();

            this.imgElement.onload = () => {
                this.imgElement.style.opacity = 1;
                this.spinner.style.display = 'none';
            };

            this.imgElement.onerror = () => this.handleImageError();
        }

        // Gestion des erreurs de chargement d'image
        handleImageError() {
            const miniUrl = this.images[this.currentIndex].querySelector('img').src;
            const updatedUrl = miniUrl.replace('/minis/', '/fichiers/').replace('.png', '.jpg');

            this.imgElement.src = updatedUrl;

            this.imgElement.onerror = () => {
                const secondTryUrl = miniUrl.replace('/minis/', '/fichiers/').replace('.jpg', '.png');
                this.imgElement.src = secondTryUrl;

                this.imgElement.onerror = () => {
                    this.imgElement.src = miniUrl;
                };
            };
        }

        // Change d'image en fonction de la direction (suivant/précédent)
        changeImage(direction) {
            this.currentIndex = (this.currentIndex + direction + this.images.length) % this.images.length;
            this.imgElement.style.opacity = 0;
            this.spinner.style.display = 'block';
            this.updateImage();
        }

        // Ferme le visualiseur d'images
        closeViewer() {
            document.body.removeChild(this.overlay);
        }

      // Désactive ou active les boutons suivant/précédent en fonction de l'index actuel
      toggleButtonState() {
          if (this.currentIndex === 0) {
              this.prevButton.disabled = true;
              this.prevButton.style.opacity = 0.5;
              this.prevButton.style.cursor = 'initial';
          } else {
              this.prevButton.disabled = false;
              this.prevButton.style.opacity = 1;
              this.prevButton.style.cursor = 'pointer';
          }

          if (this.currentIndex === this.images.length - 1) {
              this.nextButton.disabled = true;
              this.nextButton.style.opacity = 0.5;
              this.nextButton.style.cursor = 'initial';
          } else {
              this.nextButton.disabled = false;
              this.nextButton.style.opacity = 1;
              this.nextButton.style.cursor = 'pointer';
          }
      }
    }

    // Ajout des animations pour le spinner
    const style = document.createElement('style');
    style.textContent = `
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    `;
    document.head.appendChild(style);

    // Ajoute des écouteurs d'événements aux images sur la page
    function addListeners() {
        const selectors = ['.txt-msg a', '.message a'];

        selectors.forEach(selector => {
            const links = document.querySelectorAll(selector);
            if (links.length > 0) {
                links.forEach(link => {
                    link.addEventListener('click', function(event) {
                        const imgElement = this.querySelector('img');
                        if (imgElement) {
                            event.preventDefault();
                            const images = Array.from(this.closest('.txt-msg, .message').querySelectorAll('a')).filter(imgLink => imgLink.querySelector('img'));
                            const currentIndex = images.indexOf(this);
                            new ImageViewer(images, currentIndex);
                        }
                    }, true);
                });
            }
        });
    }

    function main(){
      // Surveille les changements dans le DOM pour ajouter des événements aux nouveaux éléments
      let observer;
      try {
          const selectors = ['#page-messages-forum', '.bloc-messages']
          observer = new MutationObserver(() => addListeners());
          const targetNode = document.querySelector(selectors);
          const config = { childList: true, subtree: true };

          if (targetNode) {
              observer.observe(targetNode, config);
          }
      } catch (error) {
          // console.error("Error setting up MutationObserver:", error);
      }

      // Ajoute les écouteurs d'événements initiaux
      try {
          addListeners();
      } catch (error) {
          // console.error("Error in initial addListeners call:", error);
      }
    }

    main();

})();