- // ==UserScript==
- // @name JVC_ImageViewer
- // @namespace http://tampermonkey.net/
- // @version 1.36.3
- // @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/*
- // @match https://jvarchive.com/forums/*
- // @grant none
- // @run-at document-end
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- class ImageViewer {
- constructor() {
- if (ImageViewer.instance) {
- return ImageViewer.instance;
- }
-
- this.images = [];
- this.currentIndex = 0;
- this.overlay = null;
- this.imgElement = null;
- this.spinner = null;
- this.prevButton = null;
- this.nextButton = null;
- this.closeButton = null;
- this.infoText = null;
- this.zoomLevel = 1;
- this.isDragging = false;
- this.startX = 0;
- this.startY = 0;
- this.offsetX = 0;
- this.offsetY = 0;
- this.xDown = null;
- this.yDown = null;
- this.initialDistance = null;
- this.startTouches = [];
- this.isSwiping = false;
- this.isScaling = false;
-
- ImageViewer.instance = this;
-
- 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() {
- console.log("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());
-
-
- // Glissement sur mobile (swipe)
- this.overlay.addEventListener('touchstart', (event) => this.handleTouchStart(event));
- this.overlay.addEventListener('touchmove', (event) => this.handleTouchMove(event));
- this.overlay.addEventListener('touchend', (event) => this.handleTouchEnd(event));
-
- // Zoom sur mobile
- this.overlay.addEventListener('touchstart', (event) => this.handlePinchStart(event));
- this.overlay.addEventListener('touchmove', (event) => this.handlePinchMove(event));
- this.overlay.addEventListener('touchend', (event) => this.handlePinchEnd(event));
-
-
- this.imgElement.addEventListener('click', () => {
- window.open(this.images[this.currentIndex].href, '_blank');
- });
- }
-
- // Gestion du début de l'interaction tactile
- handleTouchStart(event) {
- if (event.touches.length === 1) {
- // Commencer le swipe
- this.isSwiping = true;
- this.startX = event.touches[0].clientX;
- } else if (event.touches.length === 2) {
- // Commencer le zoom par pincement
- this.startTouches = [...event.touches];
- this.initialDistance = this.getTouchDistance(this.startTouches);
- }
- }
-
- // Gestion du mouvement tactile pour swipe ou zoom
- handleTouchMove(event) {
- if (this.isSwiping && event.touches.length === 1) {
- // Swipe
- this.currentX = event.touches[0].clientX;
- } else if (event.touches.length === 2) {
- // Zoom par pincement
- this.handlePinchZoom(event);
- }
- }
-
- // Gestion de la fin de l'interaction tactile
- handleTouchEnd(event) {
- if (event.touches.length < 2) {
- this.initialDistance = null;
- }
- if (this.isSwiping) {
- this.isSwiping = false;
- const deltaX = this.currentX - this.startX;
-
- // Si le mouvement est suffisamment grand, on change d'image
- if (Math.abs(deltaX) > 50) {
- if (deltaX > 0) {
- this.showPreviousImage(); // Fonction pour afficher l'image précédente
- } else {
- this.showNextImage(); // Fonction pour afficher l'image suivante
- }
- }
- }
- }
-
- // Gestion du début de l'interaction tactile pour le pincement
- handlePinchStart(event) {
- if (event.touches.length === 2) {
- // On commence à zoomer
- this.initialDistance = this.getTouchDistance(event.touches);
- this.isScaling = true;
- }
- }
-
- // Gestion du mouvement tactile pour le pincement (zoom)
- handlePinchMove(event) {
- if (this.isScaling && event.touches.length === 2) {
- console.log("moving the zoom");
- const newDistance = this.getTouchDistance(event.touches);
- const scaleFactor = newDistance / this.initialDistance;
- this.zoomImage(scaleFactor);
- this.initialDistance = newDistance;
- }
- }
-
- // Gestion de la fin de l'interaction tactile pour le pincement
- handlePinchEnd(event) {
- if (event.touches.length < 2) {
- this.isScaling = false;
- console.log("end of zoom pinch");
- }
- }
-
- /*zoomImage(scaleFactor) {
- this.zoomLevel *= scaleFactor;
- this.imgElement.style.transform = `scale(${this.zoomLevel})`;
- }*/
-
- zoomImage(scaleFactor) {
- console.log("zooming");
- // Calcul du nouveau niveau de zoom
- this.zoomLevel *= scaleFactor;
-
- // Ajuster le zoomLevel pour ne pas descendre sous 1
- this.zoomLevel = Math.max(1, this.zoomLevel);
-
- // Calculer la position du zoom basé sur le centre de l'image
- const rect = this.imgElement.getBoundingClientRect();
- const offsetX = (rect.width / 2 - (rect.width / 2 * scaleFactor)) / scaleFactor;
- const offsetY = (rect.height / 2 - (rect.height / 2 * scaleFactor)) / scaleFactor;
-
- // Appliquer la transformation CSS
- this.imgElement.style.transform = `scale(${this.zoomLevel}) translate(${offsetX}px, ${offsetY}px)`;
- console.log("zoom done");
- console.log(this.imgElement)
- }
-
-
- // Fonction pour calculer la distance entre deux points de contact
- getTouchDistance(touches) {
- const [touch1, touch2] = touches;
- const deltaX = touch2.clientX - touch1.clientX;
- const deltaY = touch2.clientY - touch1.clientY;
- return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- }
-
- 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() {
- if (this.currentIndex >= 0 && this.currentIndex < this.images.length) {
- 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 extensions = ['.jpg', '.png', '.jpeg'];
- const baseUrl = miniUrl.replace('/minis/', '/fichiers/');
-
- const tryNextExtension = (index) => {
- if (index >= extensions.length) {
- // Si toutes les extensions échouent, tenter l'URL originale
- this.imgElement.src = miniUrl;
- return;
- }
-
- // Remplacer l'extension et mettre à jour l'URL
- const updatedUrl = baseUrl.replace(/\.(jpg|png|jpeg)$/, extensions[index]);
- this.imgElement.src = updatedUrl;
-
- // Tester l'URL
- this.imgElement.onerror = () => tryNextExtension(index + 1);
- };
-
- // Commencer les essais avec la première extension
- tryNextExtension(0);
- }
-
- // 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();
- }
-
- showPreviousImage() {
- this.changeImage(-1);
- }
-
- showNextImage() {
- this.changeImage(1);
- }
-
- // Ferme le visualiseur d'images
- closeViewer() {
- if (this.overlay) {
- document.body.removeChild(this.overlay);
- this.overlay = null;
- ImageViewer.instance = null; // Réinitialise l'instance singleton
- }
- }
-
- // 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';
- }
- }
-
- openViewer(images, currentIndex) {
- if (this.overlay) {
- this.images = images;
- this.currentIndex = currentIndex;
- this.updateImage();
- //this.overlay.style.display = 'flex';
- } else {
- new ImageViewer();
- this.images = images;
- this.currentIndex = currentIndex;
- this.createOverlay();
- this.updateImage();
- //this.overlay.style.display = 'flex';
- }
- }
- }
-
- function addSpinnerStyles() {
- const style = document.createElement('style');
- style.textContent = `
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- .spinner { /* Exemple de classe pour spinner */
- width: 50px;
- height: 50px;
- border: 5px solid rgba(0, 0, 0, 0.1);
- border-left-color: #000;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- `;
- document.head.appendChild(style);
- }
-
- const parentClasses = '.txt-msg, .message, .conteneur-message.mb-3, .bloc-editor-forum, .signature-msg';
- const linkSelectors = parentClasses.split(', ').map(cls => `${cls} a`);
-
- // Ajouter des écouteurs d'événements aux images sur la page
- function addListeners() {
- linkSelectors.forEach(selector => {
- document.querySelectorAll(selector).forEach(link => {
- link.addEventListener('click', handleImageClick, true);
- });
- });
- }
-
- function handleImageClick(event) {
- const imgElement = this.querySelector('img');
- if (imgElement) {
- event.preventDefault();
- const closestElement = this.closest(parentClasses);
- if (closestElement) {
- const images = Array.from(closestElement.querySelectorAll('a')).filter(imgLink => imgLink.querySelector('img'));
- const currentIndex = images.indexOf(this);
-
- const viewer = new ImageViewer();
- viewer.openViewer(images, currentIndex);
- }
- }
- }
-
- function main() {
- addSpinnerStyles();
- addListeners();
-
- const observer = new MutationObserver(() => addListeners());
- observer.observe(document, { childList: true, subtree: true });
- }
-
- main();
-
-
- })();