JVC_ImageViewer

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

目前為 2024-09-15 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         JVC_ImageViewer
// @namespace    http://tampermonkey.net/
// @version      1.35.1
// @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.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));

            // Zoom pour appareils tactiles (iOS et Android)
            this.imgElement.addEventListener('gesturestart', (event) => {
                event.preventDefault();
                this.handlePinchZoom(event);
            });

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

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

          // Gestion du zoom avec la molette de la souris
          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})`;
          }

          // 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 => {
            document.querySelectorAll(selector).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);
            });
        });
    }

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

    if (targetNode) {
        observer.observe(targetNode, config);
    }

    addListeners();
})();