// ==UserScript==
// @name JVC_ImageViewer
// @namespace http://tampermonkey.net/
// @version 1.41.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/*
// @match https://*.jeuxvideo.com/profil/*
// @match https://jvarchive.com/forums/*
// @match https://jvarchive.com/*
// @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.indicatorsContainer = null;
this.indicators = [];
this.zoomLevel = 1;
this.isDragging = false;
this.isPinchZooming = 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;
this.imageElementScale = 1;
this.start = {};
this.isMouseDown = false;
this.isTouchDragging = false;
this.dragTimeout = null;
this.mouseDownX = 0;
this.mouseDownY = 0;
this.initialScale = 1;
this.isViewerOpen = false;
this.thumbnailPanel = null;
this.previousThumbnail = null;
this.touchSensitivityFactor = 0.5; // Pour les appareils tactiles
this.mouseSensitivityFactor = 0.4; // Pour les mouvements de souris
ImageViewer.instance = this;
this.handlePopState = this.handlePopState.bind(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();
this.indicatorsContainer = this.createElement('div', {
display: 'flex',
justifyContent: 'center',
marginBottom: '10px 0',
position: 'absolute',
bottom: '40px',
});
// this.addScrollbarStyles();
this.resetHideButtons();
// Événements associés aux boutons et à l'overlay
this.addEventListeners();
this.addInteractionListeners();
// Ajout des éléments au DOM
this.overlay.append(this.imgElement, this.spinner, this.infoText, this.prevButton, this.nextButton, this.closeButton);
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 isMobileDevice = isMobile();
const button = this.createElement('button', {
position: 'absolute',
[position]: '5px',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
color: 'white',
fontSize: isMobileDevice ? '18px' : '22px',//'22px',
border: 'none',
borderRadius: '50%',
width: isMobileDevice ? '35px' : '40px',//'40px',
height: isMobileDevice ? '35px' : '40px',//'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;*
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('width', '24');
svg.setAttribute('height', '24');
svg.setAttribute('fill', 'white');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', position === 'left'
? 'M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6z' // Icône flèche gauche
: 'M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6z'); // Icône flèche droite
svg.appendChild(path);
button.appendChild(svg);
this.addButtonEffects(button);
return button;
}
// Crée le bouton de fermeture
createCloseButton() {
const isMobileDevice = isMobile();
const button = this.createElement('button', {
position: 'absolute',
top: '80px',
right: '10px',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
color: 'white',
fontSize: isMobileDevice ? '18px' : '16px',//'14px',
border: 'none',
borderRadius: '50%',
width: isMobileDevice ? '40px' : '35px', //'35px',
height: isMobileDevice ? '40px' : '35px', //'35px',
cursor: 'pointer',
zIndex: 99999999
});
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',
top: '80px',
left: '15px',
color: 'white',
fontSize: '12px',
backgroundColor: 'rgba(5, 5, 5, 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() {
// Bouttons de controles du visualiseur
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));
this.imgElement.addEventListener('mousedown', this.handleMouseDown.bind(this));
this.imgElement.addEventListener('mouseup', this.handleMouseUp.bind(this));
// Touches avec les doigts
this.imgElement.addEventListener('touchstart', (event) => this.handleTouchEvent(event));
this.imgElement.addEventListener('touchmove', (event) => this.handleTouchEvent(event));
this.imgElement.addEventListener('touchend', (event) => this.handleTouchEvent(event));
// Ouvrir l'image dans une no6velle fenêtre
this.imgElement.addEventListener('click', () => {
if (!this.isDragging) {
window.open(this.images[this.currentIndex].href, '_blank');
}
});
// Touches du clavier
document.addEventListener('keydown', (event) => this.handleKeyboardEvents(event));
}
// Fonctions pour gérer les touches du clavier
handleKeyboardEvents(event) {
switch (event.key) {
case 'ArrowLeft':
case 'ArrowUp':
this.changeImage(-1);
break;
case 'ArrowRight':
case 'ArrowDown':
this.changeImage(1);
break;
case 'Escape':
event.preventDefault();
this.closeViewer();
break;
}
}
// Fonctions pour gérer les touches tactiles
handleTouchEvent(event) {
switch (event.type) {
case 'touchstart':
if (event.touches.length === 1) {
if (this.imageElementScale > 1) {
// Si l'image est zoomée, permettre le déplacement (drag)
this.startDrag(event);
} else {
// Sinon, démarrer le swipe
console.log("swipe start");
this.handleSwipeStart(event);
}
} else if (event.touches.length === 2) {
// Démarrer le pinch zoom
this.handlePinchStart(event);
}
break;
case 'touchmove':
if (event.touches.length === 1) {
if (this.imageElementScale > 1) {
// Si l'image est zoomée, permettre le déplacement (drag)
this.onDrag(event);
} else {
console.log("swipe move");
this.handleSwipeMove(event);
}
} else if (event.touches.length === 2) {
// Gérer le pinch zoom
this.handlePinchMove(event);
}
break;
case 'touchend':
if (event.touches.length === 1) {
if (this.imageElementScale > 1) {
this.endDrag(event);
}
}
else if (event.touches.length === 0) {
if (this.isSwiping) {
this.handleSwipeEnd(event);
} else if (this.isPinchZooming) {
this.handlePinchEnd(event);
} else {
this.endDrag(event);
}
}
break;
}
}
// Gestion du début de l'interaction tactile
handleSwipeStart(event) {
if (event.touches.length === 1) {
if(this.isPinchZooming) {
return;
}
// Commencer le swipe
this.isSwiping = true;
this.startX = event.touches[0].clientX;
this.startY = event.touches[0].clientY;
}
}
// Gestion du mouvement tactile pour swipe
handleSwipeMove(event) {
if (this.isSwiping && event.touches.length === 1) {
this.currentX = event.touches[0].clientX;
this.currentY = event.touches[0].clientY;
}
}
// Gestion de la fin de l'interaction tactile
handleSwipeEnd(event) {
if (event.touches.length === 0) {
this.initialDistance = null;
}
if (this.isSwiping) {
this.isSwiping = false;
const deltaX = this.currentX - this.startX;
const deltaY = this.currentY - this.startY;
// Si le mouvement est suffisamment grand, on change d'image
if (Math.abs(deltaX) > 50) {
if (deltaX > 0) {
this.showPreviousImage();
} else {
this.showNextImage();
}
}
// Si le mouvement est suffisamment grand verticalement, on ferme le visualiseur
if (Math.abs(deltaY) > 50) {
this.closeViewer();
}
}
// l'image revient à sa taille d'origine, réinitialiser le zIndex
this.imgElement.style.zIndex = '';
}
// Calculate distance between two fingers
distance(event){
return Math.hypot(event.touches[0].pageX - event.touches[1].pageX, event.touches[0].pageY - event.touches[1].pageY);
}
// Gestion du début de l'interaction tactile pour le pincement
handlePinchStart(event) {
if (event.touches.length === 2) {
event.preventDefault(); // Prevent page scroll
this.isPinchZooming = true;
// Calculate where the fingers have started on the X and Y axis
this.start.x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
this.start.y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
this.start.distance = this.distance(event);
// Save the current scale to use it as a base for the new scale
this.initialScale = this.imageElementScale || 1; // Use 1 if there's no previous zoom
}
}
// Gestion du mouvement tactile pour le pincement (zoom)
handlePinchMove(event) {
if (event.touches.length === 2) {
event.preventDefault(); // Prevent page scroll
// Safari provides event.scale as two fingers move on the screen
// For other browsers just calculate the scale manually
let scale;
if (event.scale) {
scale = event.scale;
} else {
const deltaDistance = this.distance(event);
scale = (deltaDistance / this.start.distance); //* this.touchSensitivityFactor;
}
// this.imageElementScale = Math.min(Math.max(1, scale), 4);
// Multiply the new scale by the starting scale to retain the zoom level
// this.imageElementScale = Math.min(Math.max(1, this.startScale * scale), 4);
this.imageElementScale = Math.min(Math.max(1, this.initialScale * scale), 4);
// Calculate how much the fingers have moved on the X and Y axis
const deltaX = (((event.touches[0].pageX + event.touches[1].pageX) / 2) - this.start.x) * 2; // x2 for accelarated movement
const deltaY = (((event.touches[0].pageY + event.touches[1].pageY) / 2) - this.start.y) * 2; // x2 for accelarated movement
// Transform the image to make it grow and move with fingers
const transform = `translate3d(${deltaX}px, ${deltaY}px, 0) scale(${this.imageElementScale})`;
this.imgElement.style.transform = transform;
this.imgElement.style.WebkitTransform = transform;
this.imgElement.style.zIndex = "9999";
this.closeButton.style.zIndex = "10003";
}
}
// Gestion de la fin de l'interaction tactile pour le pincement
handlePinchEnd(event) {
if (event.touches.length < 2) {
// Ajouter un délai pour réinitialiser le drag (empeche les conflits avec le clic)
this.dragTimeout = setTimeout(() => {
this.isPinchZooming = false;
this.dragTimeout = null;
}, 100);
// Si l'image est revenue à sa taille d'origine, réinitialiser le zIndex
if (this.imageElementScale <= 1) {
this.imgElement.style.zIndex = '';
}
}
}
// 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.07;
/*if (event.deltaY < 0) {
this.imageElementScale += zoomIncrement; // Zoomer
} else {
this.imageElementScale = Math.max(1, this.zoomLevel - zoomIncrement); // Dézoomer, mais ne pas descendre sous 1
}*/
if (event.deltaY < 0) {
this.imageElementScale = Math.min(4, this.imageElementScale + zoomIncrement); // Limite max
} else {
this.imageElementScale = Math.max(1, this.imageElementScale - zoomIncrement); // Limite min
}
this.imgElement.style.transform = `scale(${this.imageElementScale}) translate(${this.offsetX}px, ${this.offsetY}px)`;
// Si le niveau de zoom est supérieur à 1, mettre l'image devant les boutons
if (this.zoomLevel > 1) {
this.imgElement.style.zIndex = 10002;
} else {
// Si le zoom revient à la normale, remettre le zIndex initial
this.imgElement.style.zIndex = '';
}
}
startDrag(event) {
event.preventDefault(); // Empêche la sélection de l'image
// Gestion tactile
if (event.type === 'touchstart') {
this.isTouchDragging = true;
this.startX = event.touches[0].clientX; //- this.offsetX;
this.startY = event.touches[0].clientY; //- this.offsetY;
} else {
// Gestion avec la souris
this.isMouseDown = true;
this.startX = event.clientX; //- this.offsetX;
this.startY = event.clientY; // - this.offsetY;
}
this.isDragging = true;
this.imgElement.style.cursor = 'grabbing';
this.imgElement.style.userSelect = 'none';
// Ajouter des listeners sur le document pour capturer les mouvements
if (event.touches) {
document.addEventListener('touchmove', this.onDragBound = this.onDrag.bind(this));
document.addEventListener('touchend', this.endDragBound = this.endDrag.bind(this));
} else {
document.addEventListener('mousemove', this.onDragBound = this.onDrag.bind(this));
document.addEventListener('mouseup', this.endDragBound = this.endDrag.bind(this));
}
}
onDrag(event) {
if (!this.isDragging) return;
event.preventDefault();
let deltaX, deltaY;
if (event.type === 'touchmove') {
// Gestion tactile
deltaX = (event.touches[0].clientX - this.startX) * this.touchSensitivityFactor;
deltaY = (event.touches[0].clientY - this.startY) * this.touchSensitivityFactor;
} else {
// Gestion avec la souris
deltaX = (event.clientX - this.startX) * this.mouseSensitivityFactor;
deltaY = (event.clientY - this.startY) * this.mouseSensitivityFactor;
}
// Appliquer la translation ajustée par le facteur de sensibilité
this.offsetX += deltaX;
this.offsetY += deltaY;
// Mettre à jour les points de départ pour le prochain déplacement
this.startX = event.type === 'touchmove' ? event.touches[0].clientX : event.clientX;
this.startY = event.type === 'touchmove' ? event.touches[0].clientY : event.clientY;
// Appliquer la transformation avec le zoom actuel, en se déplaçant dans l'image
this.imgElement.style.transform = `scale(${this.imageElementScale}) translate(${this.offsetX}px, ${this.offsetY}px)`;
}
endDrag(event) {
this.imgElement.style.cursor = 'grab';
// Retirer les listeners
if (event.type === 'touchend') {
this.isTouchDragging = false; // Réinitialise l'état tactile
document.removeEventListener('touchmove', this.onDragBound);
document.removeEventListener('touchend', this.endDragBound);
} else {
this.isMouseDown = false; // Réinitialise l'état de la souris
document.removeEventListener('mousemove', this.onDragBound);
document.removeEventListener('mouseup', this.endDragBound);
}
// Ajouter un délai pour réinitialiser le drag (empeche les conflits avec le clic)
this.dragTimeout = setTimeout(() => {
this.isDragging = false;
this.imgElement.style.cursor = 'pointer';
this.dragTimeout = null;
}, 100);
}
handleMouseDown(event) {
this.isMouseDown = true;
this.mouseDownX = event.clientX;
this.mouseDownY = event.clientY;
// Démarrer le drag après un délai pour éviter le drag lors des clics courts
this.dragTimeout = setTimeout(() => {
if (this.isMouseDown) {
this.startDrag(event);
}
}, 200);
}
handleMouseUp(event) {
this.isMouseDown = false;
// Si le délai pour démarrer le drag est encore en cours, le nettoyer
if (this.dragTimeout) {
clearTimeout(this.dragTimeout);
this.dragTimeout = null;
}
// Vérifier si le mouvement est suffisant pour considérer que c'est un drag
const movedX = Math.abs(event.clientX - this.mouseDownX);
const movedY = Math.abs(event.clientY - this.mouseDownY);
if (movedX < 5 && movedY < 5) {
// handleImageClick(event); // Traiter le clic si le mouvement est minime
}
}
// Réinitialiser le zoom de l'image
resetZoom() {
this.imgElement.style.transform = 'scale(1)';
this.imgElement.style.transformOrigin = '0 0';
this.imageElementScale = 1;
this.offsetX = 0;
this.offsetY = 0;
}
// Réinitialiser la position du drag de l'image
resetDrag() {
this.imgElement.style.left = '0px';
this.imgElement.style.top = '0px';
}
// 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';
// Réinitialiser le zoom et la position du drag
this.resetZoom();
this.resetDrag();
// Calcul de la position des boutons
const imgRect = this.imgElement.getBoundingClientRect();
const isMobileDevice = isMobile(); // Détection des mobiles
if (isMobileDevice) {
// pass
} else {
const margin = 30;
this.prevButton.style.left = `${imgRect.left - this.prevButton.offsetWidth - margin}px`;
this.nextButton.style.right = `${window.innerWidth - imgRect.right - this.nextButton.offsetWidth - margin}px`;
}
this.focusOnThumbnail();
};
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]);
// Tester l'URL avec un élément Image temporaire
const imgTest = new Image();
imgTest.src = updatedUrl;
imgTest.onload = () => {
// Si l'image se charge avec succès, l'assigner à l'élément d'image principal
this.imgElement.src = updatedUrl;
};
imgTest.onerror = () => {
console.log("Error loading: " + updatedUrl);
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);
}
// Met à jour le focus sur la miniature correspondante
focusOnThumbnail() {
// Obtenez la miniature actuelle
const thumbnails = this.thumbnailPanel ? this.thumbnailPanel.querySelectorAll('img') : [];
const currentThumbnail = thumbnails[this.currentIndex];
// Réinitialiser la bordure de la miniature précédente si elle existe
if (this.previousThumbnail) {
this.previousThumbnail.style.border = 'none';
this.previousThumbnail.style.transform = 'scale(1)';
this.previousThumbnail.style.boxShadow = 'none';
}
// Mettre à jour la bordure de la miniature actuelle
if (currentThumbnail) {
//currentThumbnail.style.border = '2px solid rgba(40, 40, 40, 0.8)'; // Appliquer la bordure
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
currentThumbnail.style.transform = 'scale(1.15)'; // Agrandir l'élément
currentThumbnail.parentElement.scrollIntoView({ behavior: 'smooth', inline: 'center' });
}
// Mettre à jour la référence de la miniature précédente
this.previousThumbnail = currentThumbnail;
}
// 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';
}
}
// Cacher temporairement le menu JVC
toggleMenuVisibility(isVisible) {
const menu = document.querySelector('.header__bottom');
if (menu) {
menu.style.display = isVisible ? '' : 'none';
}
}
// Fonction pour créer et afficher le panneau des miniatures
toggleThumbnailPanel() {
if (this.thumbnailPanel) {
this.closeThumbnailPanel(); // Ferme le panneau si déjà ouvert
return;
}
// Créer le panneau
this.thumbnailPanel = this.createElement('div', {
position: 'fixed',
bottom: '10px',
left: '50%',
transform: 'translateX(-50%)',
border: '0px solid',
padding: '0px',
zIndex: '99999999',
maxHeight: '80px',
maxWidth: '80%',
overflowY: 'hidden',
overflowX: 'auto',
display: 'flex',
alignItems: 'center',
backgroundColor: 'transparent',
});
// Conteneur pour le défilement horizontal
const scrollContainer = this.createElement('div', {
display: 'flex',
overflowX: 'auto',
whiteSpace: 'nowrap',
maxWidth: '100%',
});
// Ajout des images au conteneur
this.images.forEach((image, index) => {
const imgContainer = this.createElement('div', {
display: 'inline-block',
width: '50px',
height: '50px',
margin: '5px',
padding: '4px',
transition: 'transform 0.3s',
});
const imgElement = this.createElement('img');
imgElement.src = image.querySelector('img') ? image.querySelector('img').src : image.href || image.thumbnail;
imgElement.alt = `Image ${index + 1}`;
imgElement.style.width = 'auto';
imgElement.style.height = '100%';
imgElement.style.objectFit = 'cover';
imgElement.style.cursor = 'pointer';
imgElement.addEventListener('click', () => {
this.images.forEach((_, i) => {
const container = scrollContainer.children[i];
container.querySelector('img').style.border = 'none';
});
//imgElement.style.border = '2px solid blue';
this.currentIndex = index;
this.updateImage();
//imgContainer.scrollIntoView({ behavior: 'smooth', inline: 'center' });
});
imgContainer.appendChild(imgElement);
scrollContainer.appendChild(imgContainer);
});
this.thumbnailPanel.appendChild(scrollContainer);
this.overlay.appendChild(this.thumbnailPanel);
this.focusOnThumbnail();
}
// Ecouteurs d'événements pour réinitialiser le timer
addInteractionListeners() {
this.overlay.addEventListener('mousemove', this.resetHideButtons.bind(this));
this.overlay.addEventListener('click', this.resetHideButtons.bind(this));
this.overlay.addEventListener('touchstart', this.resetHideButtons.bind(this));
}
// Réinitialisez le timer pour cacher les boutons
resetHideButtons() {
if (this.hideButtonsTimeout) {
clearTimeout(this.hideButtonsTimeout);
}
this.toggleButtonsVisibility(true);
this.hideButtonsTimeout = setTimeout(() => {
this.toggleButtonsVisibility(false); // Cachez les boutons après 3 secondes
}, 2500);
}
// Changez la visibilité des boutons
toggleButtonsVisibility(visible) {
const displayValue = visible ? 'flex' : 'none';
if (this.prevButton) {
this.prevButton.style.display = displayValue;
}
if (this.nextButton) {
this.nextButton.style.display = displayValue;
}
if (this.thumbnailPanel) {
this.thumbnailPanel.style.display = displayValue;
}
if (this.infoText) {
this.infoText.style.display = displayValue;
}
}
// Fonction pour fermer le panneau des miniatures
closeThumbnailPanel(thumbnailPanel) {
if (this.thumbnailPanel && this.overlay.contains(this.thumbnailPanel)) {
this.overlay.removeChild(this.thumbnailPanel);
this.thumbnailPanel = null;
}
}
closeViewer() {
if (this.overlay) {
this.handleCloseViewer(); // Ferme le visualiseur
history.back(); // Supprime l'état ajouté par pushState
}
}
// Ferme le visualiseur d'images
handleCloseViewer() {
if (this.overlay) {
document.body.removeChild(this.overlay);
// Ferme le panneau des miniatures si ouvert
if (this.thumbnailPanel) {
this.closeThumbnailPanel(this.thumbnailPanel);
}
window.removeEventListener('popstate', this.handlePopState);
this.overlay = null;
this.isViewerOpen = false;
ImageViewer.instance = null; // Réinitialise l'instance singleton
this.toggleMenuVisibility(true);
}
}
openViewer(images, currentIndex) {
if (this.overlay) {
this.images = images;
this.currentIndex = currentIndex;
this.updateImage();
this.toggleThumbnailPanel();
} else {
new ImageViewer();
this.images = images;
this.currentIndex = currentIndex;
this.createOverlay();
this.updateImage();
this.toggleThumbnailPanel();
}
this.isViewerOpen = true;
this.addHistoryState()
window.addEventListener('popstate', this.handlePopState); // Ecouter l'événement bouton back du navigateur
this.toggleMenuVisibility(false);
}
handlePopState(event) {
if (ImageViewer.instance) {
event.preventDefault();
this.handleCloseViewer();
}
}
// Ajouter une entrée dans l'historique
addHistoryState() {
history.pushState({ viewerOpen: true }, '');
}
}
function addSpinnerStyles() {
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner {
width: 50px;
height: 50px;
border-radius: 50%;
border: 5px solid transparent;
border-top: 5px solid rgba(0, 0, 0, 0.1);
background: conic-gradient(from 0deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0));
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) {
// Si Ctrl ou Cmd est enfoncé, ne pas ouvrir l'ImageViewer
if (event.ctrlKey || event.metaKey) {
return;
}
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 isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// Fonction pour observer les changements dans le DOM
function observeDOMChanges() {
const observer = new MutationObserver(() => addListeners());
observer.observe(document, { childList: true, subtree: true });
}
// Fonction dédiée à la détection des changements d'URL
function observeURLChanges() {
let lastUrl = window.location.href;
const urlObserver = new MutationObserver(() => {
if (lastUrl !== window.location.href) {
lastUrl = window.location.href;
addListeners();
}
});
urlObserver.observe(document, { subtree: true, childList: true });
}
function main() {
addSpinnerStyles();
addListeners();
observeDOMChanges();
observeURLChanges();
}
main();
})();