您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a "Grid View" button next to the scene title that, when pressed, automatically opens the carousel (if needed) and then launches a grid overlay. In the grid overlay the images are shown inside cells with a fixed height increased by 50% so that both vertical and landscape images appear larger and are not cut off. The grid overlay also retains its scroll position between openings. The carousel overlay includes mouse-wheel zoom (centered on the mouse pointer) and drag functionality.
// ==UserScript== // @name Adulttime Grid View // @namespace http://tampermonkey.net/ // @version 1.0 // @description Adds a "Grid View" button next to the scene title that, when pressed, automatically opens the carousel (if needed) and then launches a grid overlay. In the grid overlay the images are shown inside cells with a fixed height increased by 50% so that both vertical and landscape images appear larger and are not cut off. The grid overlay also retains its scroll position between openings. The carousel overlay includes mouse-wheel zoom (centered on the mouse pointer) and drag functionality. // @match https://members.adulttime.com/* // @license GPL-3.0 // @grant none // ==/UserScript== (function() { 'use strict'; let currentCarouselImages = []; let gridScrollPosition = 0; // To store grid overlay scroll position // Hide the default carousel container (if any) function hideDefaultCarousel() { const defaultCarousel = document.querySelector('#pageOverlaySlot'); if (defaultCarousel) { defaultCarousel.style.display = 'none'; } } // Extract image URLs from the carousel (if already open) function extractCarouselImages() { const imageNodes = document.querySelectorAll('#pageOverlaySlot .image-gallery-slide img.image-gallery-image'); const urls = []; imageNodes.forEach(img => { if (img.src && !urls.includes(img.src)) { urls.push(img.src); } }); return urls; } // Recursively click the "next" arrow until all images have been loaded. function loadAllCarouselImages(callback) { const activeImg = document.querySelector('.image-gallery-slide.image-gallery-center img.image-gallery-image'); if (!loadAllCarouselImages.firstSrc && activeImg) { loadAllCarouselImages.firstSrc = activeImg.src; } if (activeImg && loadAllCarouselImages.called && activeImg.src === loadAllCarouselImages.firstSrc) { callback(); return; } loadAllCarouselImages.called = true; const nextArrow = document.querySelector('a.next-Link:not(.disabled-Link)'); if (nextArrow) { nextArrow.click(); setTimeout(() => { loadAllCarouselImages(callback); }, 1000); } else { callback(); } } // Create grid overlay container with fixed cell height (increased by 50%) and scroll retention. function createOverlayContainer(images) { let container = document.getElementById('gridOverlayContainer'); if (container) container.remove(); container = document.createElement('div'); container.id = 'gridOverlayContainer'; Object.assign(container.style, { position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh', backgroundColor: 'black', zIndex: '9999', display: 'grid', // Set fixed cell width using auto-fit; using minmax with 600px as minimum (50% more than 400px) gridTemplateColumns: 'repeat(auto-fit, minmax(600px, 1fr))', gap: '10px', padding: '20px', overflowY: 'auto' }); // Listen for scroll events to update scroll retention. container.addEventListener('scroll', () => { gridScrollPosition = container.scrollTop; }); // For each image, wrap it in a cell container. images.forEach(src => { const cell = document.createElement('div'); // Fixed cell height: assume original vertical cell height was 375px; increased by 50% becomes 562.5px. cell.style.height = '562.5px'; cell.style.overflow = 'hidden'; cell.style.display = 'flex'; cell.style.alignItems = 'center'; cell.style.justifyContent = 'center'; const img = document.createElement('img'); img.src = src; // Ensure image fits inside the cell without cropping. img.style.maxHeight = '100%'; img.style.maxWidth = '100%'; img.style.objectFit = 'contain'; img.style.cursor = 'pointer'; // On clicking an image, remove grid overlay and open custom carousel at that image. img.addEventListener('click', () => { container.remove(); const closeBtn = document.getElementById('gridOverlayCloseButton'); if (closeBtn) closeBtn.remove(); createCustomCarousel(images.indexOf(src)); }); cell.appendChild(img); container.appendChild(cell); }); // Reapply stored scroll position, if any. setTimeout(() => { container.scrollTop = gridScrollPosition; }, 0); document.body.appendChild(container); // Add a persistent close button for grid overlay. const closeBtn = document.createElement('button'); closeBtn.id = 'gridOverlayCloseButton'; closeBtn.textContent = 'Close Grid'; closeBtn.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 10000; padding: 10px 20px; font-size: 16px; cursor: pointer;'; closeBtn.addEventListener('click', () => { container.remove(); closeBtn.remove(); }); document.body.appendChild(closeBtn); } // Create a custom carousel overlay with zoom (based on mouse position) and drag. function createCustomCarousel(startIndex) { hideDefaultCarousel(); let currentIndex = startIndex; const carouselContainer = document.createElement('div'); carouselContainer.id = 'customCarouselContainer'; Object.assign(carouselContainer.style, { position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh', backgroundColor: 'black', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: '10000', overflow: 'hidden' }); const carouselImage = document.createElement('img'); carouselImage.id = 'customCarouselImage'; carouselImage.src = currentCarouselImages[currentIndex]; carouselImage.style.maxWidth = '90%'; carouselImage.style.maxHeight = '90%'; carouselImage.style.objectFit = 'contain'; carouselImage.draggable = false; // Setup transform parameters for zoom and drag. let scale = 1, translateX = 0, translateY = 0, startX, startY; carouselImage.style.transformOrigin = '0 0'; carouselImage.style.transition = 'transform 0.1s ease-out'; // Mouse wheel zoom (centered on mouse pointer) carouselImage.addEventListener('wheel', (e) => { e.preventDefault(); const rect = carouselImage.getBoundingClientRect(); const offsetX = e.clientX - rect.left; const offsetY = e.clientY - rect.top; const delta = e.deltaY > 0 ? -0.1 : 0.1; const newScale = Math.max(1, scale + delta); const ratio = newScale / scale; // Adjust translation so that zoom centers on mouse pointer. translateX = offsetX - ratio * (offsetX - translateX); translateY = offsetY - ratio * (offsetY - translateY); scale = newScale; carouselImage.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; }); // Drag functionality: update translation on mouse move. carouselImage.addEventListener('mousedown', (e) => { e.preventDefault(); startX = e.clientX - translateX; startY = e.clientY - translateY; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); function onMouseMove(e) { translateX = e.clientX - startX; translateY = e.clientY - startY; carouselImage.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } function onMouseUp() { document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } carouselContainer.appendChild(carouselImage); // Left arrow button for carousel navigation. const leftButton = document.createElement('button'); leftButton.textContent = '<'; leftButton.style.cssText = 'position: absolute; left: 20px; top: 50%; transform: translateY(-50%); font-size: 30px; background: transparent; border: none; color: white; cursor: pointer;'; leftButton.addEventListener('click', () => { // Reset zoom/drag on image change. scale = 1; translateX = 0; translateY = 0; currentIndex = (currentIndex - 1 + currentCarouselImages.length) % currentCarouselImages.length; carouselImage.src = currentCarouselImages[currentIndex]; carouselImage.style.transform = `translate(0px, 0px) scale(1)`; }); carouselContainer.appendChild(leftButton); // Right arrow button for carousel navigation. const rightButton = document.createElement('button'); rightButton.textContent = '>'; rightButton.style.cssText = 'position: absolute; right: 20px; top: 50%; transform: translateY(-50%); font-size: 30px; background: transparent; border: none; color: white; cursor: pointer;'; rightButton.addEventListener('click', () => { scale = 1; translateX = 0; translateY = 0; currentIndex = (currentIndex + 1) % currentCarouselImages.length; carouselImage.src = currentCarouselImages[currentIndex]; carouselImage.style.transform = `translate(0px, 0px) scale(1)`; }); carouselContainer.appendChild(rightButton); // Close carousel button. const closeCarouselBtn = document.createElement('button'); closeCarouselBtn.textContent = 'Close Carousel'; closeCarouselBtn.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 11000; padding: 10px 20px; font-size: 16px; cursor: pointer;'; closeCarouselBtn.addEventListener('click', () => { carouselContainer.remove(); closeCarouselBtn.remove(); // When closing carousel, reopen the grid overlay with scroll retention. createOverlayContainer(currentCarouselImages); }); document.body.appendChild(carouselContainer); document.body.appendChild(closeCarouselBtn); // Keyboard navigation for left/right arrows and escape. function keyHandler(e) { if (e.key === 'ArrowLeft') leftButton.click(); else if (e.key === 'ArrowRight') rightButton.click(); else if (e.key === 'Escape') closeCarouselBtn.click(); } document.addEventListener('keydown', keyHandler); } // Toggle grid overlay: load carousel images then open grid overlay. function toggleOverlay() { hideDefaultCarousel(); loadAllCarouselImages(() => { const images = extractCarouselImages(); currentCarouselImages = images.slice(); if (images.length > 0) { createOverlayContainer(images); } else { alert('No carousel images found.'); } }); } // Launch grid overlay automatically – if no carousel image is active, simulate a click on first thumbnail. function launchGridOverlay() { let activeImg = document.querySelector('.image-gallery-slide.image-gallery-center img.image-gallery-image'); if (!activeImg) { const firstThumb = document.querySelector('.PhotosetGallery-Image-BackgroundBox img'); if (firstThumb) { firstThumb.click(); const checkActive = setInterval(() => { activeImg = document.querySelector('.image-gallery-slide.image-gallery-center img.image-gallery-image'); if (activeImg) { clearInterval(checkActive); toggleOverlay(); } }, 500); } else { alert('No carousel thumbnail found.'); } } else { toggleOverlay(); } } // Add a "Grid View" button next to the scene title. function addGridButton() { const targetSelector = 'h1.Title.PhotosetGallery-PhotosetTitle-Title'; function insertButton() { const titleElement = document.querySelector(targetSelector); if (titleElement && !document.getElementById('gridViewButton')) { const gridButton = document.createElement('button'); gridButton.id = 'gridViewButton'; gridButton.textContent = 'Grid View'; // Style the button to be a bit larger and with a yellow background. gridButton.style.cssText = ` margin-left: 20px; padding: 8px 16px; font-size: 16px; cursor: pointer; background-color: yellow; border: 1px solid #999; border-radius: 4px; font-weight: bold; vertical-align: middle; `; gridButton.addEventListener('click', launchGridOverlay); titleElement.parentElement.insertBefore(gridButton, titleElement.nextSibling); return true; } return false; } if (insertButton()) return; const observer = new MutationObserver((mutations, obs) => { if (insertButton()) { obs.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); const pollInterval = setInterval(() => { if (insertButton()) { clearInterval(pollInterval); } }, 500); } if (document.readyState !== 'loading') { addGridButton(); } else { document.addEventListener('DOMContentLoaded', addGridButton); } })();