您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows all photos in a true grid overlay (fixed selectors) on ZeroToleranceFilms photosets.
// ==UserScript== // @name ZeroTolerance Grid View (fixed) // @namespace http://tampermonkey.net/ // @version 1.1 // @description Shows all photos in a true grid overlay (fixed selectors) on ZeroToleranceFilms photosets. // @match https://members.zerotolerancefilms.com/* // @license GPL-3.0 // @grant none // ==/UserScript== (function() { 'use strict'; let currentCarouselImages = []; let gridScrollPosition = 0; // Hide the React overlay container (once we've pulled URLs) function hideDefaultCarousel() { const overlay = document.getElementById('pageOverlaySlot'); if (overlay) overlay.style.display = 'none'; } // Pull URLs from the overlay function extractCarouselImages() { const overlay = document.getElementById('pageOverlaySlot'); const imgs = overlay.querySelectorAll('img'); return Array.from(imgs).map(i => i.src).filter((v,i,a)=> v && a.indexOf(v)===i); } // Step through overlay arrows only function loadAllCarouselImages(callback) { const overlay = document.getElementById('pageOverlaySlot'); const img = overlay.querySelector('img'); if (!loadAllCarouselImages.startSrc && img) { loadAllCarouselImages.startSrc = img.src; } const nextArrow = overlay.querySelector('a.next-Link:not(.disabled-Link)'); // if we’ve looped back to the first src, we’re done if (loadAllCarouselImages.called && img && img.src === loadAllCarouselImages.startSrc) { return callback(); } loadAllCarouselImages.called = true; if (nextArrow) { nextArrow.click(); setTimeout(() => loadAllCarouselImages(callback), 500); } else { callback(); } } // Build the grid overlay function createOverlayContainer(images) { let c = document.getElementById('ztGridOverlay'); if (c) c.remove(); c = document.createElement('div'); c.id = 'ztGridOverlay'; Object.assign(c.style, { position:'fixed', top:0, left:0, width:'100vw', height:'100vh', backgroundColor:'rgba(0,0,0,0.95)', backgroundColor:'black', display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(600px,1fr))', gap:'10px', padding:'20px', overflowY:'auto', zIndex:9999 }); c.addEventListener('scroll', ()=> { gridScrollPosition = c.scrollTop; }); images.forEach((src,i) => { const cell = document.createElement('div'); Object.assign(cell.style, { height:'562.5px', overflow:'hidden', display:'flex', alignItems:'center', justifyContent:'center' }); const img = document.createElement('img'); img.src = src; Object.assign(img.style, { maxWidth:'100%', maxHeight:'100%', objectFit:'contain', cursor:'pointer' }); img.addEventListener('click', ()=> { c.remove(); document.getElementById('ztGridClose').remove(); createCustomCarousel(i); }); cell.appendChild(img); c.appendChild(cell); }); document.body.appendChild(c); // restore scroll setTimeout(()=> c.scrollTop = gridScrollPosition, 0); const btn = document.createElement('button'); btn.id = 'ztGridClose'; btn.textContent = 'Close Grid'; Object.assign(btn.style, { position:'fixed', top:'20px', right:'20px', padding:'10px 20px', fontSize:'16px', cursor:'pointer', zIndex:10000 }); btn.addEventListener('click', ()=> { c.remove(); btn.remove(); }); document.body.appendChild(btn); } // Build a simple zoom/drag carousel function createCustomCarousel(startIndex) { const overlay = document.getElementById('pageOverlaySlot'); // hide the React version hideDefaultCarousel(); let idx = startIndex, scale=1, tx=0, ty=0, sx, sy; const wrapper = document.createElement('div'); Object.assign(wrapper.style, { position:'fixed',top:0,left:0, width:'100vw',height:'100vh', backgroundColor:'black', display:'flex',alignItems:'center',justifyContent:'center', overflow:'hidden',zIndex:10000 }); const img = document.createElement('img'); img.src = currentCarouselImages[idx]; Object.assign(img.style, { maxWidth:'90%', maxHeight:'90%', objectFit:'contain', cursor:'grab', transformOrigin:'0 0' }); img.draggable = false; // wheel zoom img.addEventListener('wheel', e => { e.preventDefault(); const r = e.deltaY > 0 ? -0.1 : 0.1; const newScale = Math.max(1, scale + r); const rect = img.getBoundingClientRect(); const ox = e.clientX - rect.left, oy = e.clientY - rect.top; const ratio = newScale/scale; tx = ox - ratio*(ox - tx); ty = oy - ratio*(oy - ty); scale = newScale; img.style.transform = `translate(${tx}px,${ty}px) scale(${scale})`; }); // drag img.addEventListener('mousedown', e => { e.preventDefault(); sx = e.clientX - tx; sy = e.clientY - ty; document.addEventListener('mousemove', ondrag); document.addEventListener('mouseup', offdrag); }); function ondrag(e) { tx = e.clientX - sx; ty = e.clientY - sy; img.style.transform = `translate(${tx}px,${ty}px) scale(${scale})`; } function offdrag() { document.removeEventListener('mousemove', ondrag); document.removeEventListener('mouseup', offdrag); } wrapper.appendChild(img); ['‹','›'].forEach((sym,dirIdx)=> { const btn = document.createElement('button'); btn.textContent = sym; Object.assign(btn.style, { position:'absolute', [dirIdx===0?'left':'right']:'20px', top:'50%', transform:'translateY(-50%)', fontSize:'30px', background:'transparent', border:'none', color:'white', cursor:'pointer', zIndex:10001 }); btn.addEventListener('click', ()=> { scale=1; tx=0; ty=0; idx = (idx + (dirIdx===0?-1:1) + currentCarouselImages.length) % currentCarouselImages.length; img.src = currentCarouselImages[idx]; img.style.transform = 'translate(0,0) scale(1)'; }); wrapper.appendChild(btn); }); const close = document.createElement('button'); close.textContent = 'Close Carousel'; Object.assign(close.style, { position:'fixed', top:'20px', right:'20px', padding:'10px 20px', fontSize:'16px', cursor:'pointer', zIndex:10002 }); close.addEventListener('click', ()=> { wrapper.remove(); close.remove(); createOverlayContainer(currentCarouselImages); }); document.body.appendChild(wrapper); document.body.appendChild(close); } // Main toggle routine function toggleOverlay() { // first, walk the React overlay loadAllCarouselImages(() => { const images = extractCarouselImages(); currentCarouselImages = images.slice(); if (images.length) { // now hide React and show our grid hideDefaultCarousel(); createOverlayContainer(images); } else { alert('No carousel images found.'); } }); } // Ensure overlay is open, then toggle function launchGridOverlay() { const overlayImg = document.querySelector('#pageOverlaySlot img'); if (overlayImg) { toggleOverlay(); } else { // click first thumbnail to open React lightbox const thumb = document.querySelector('.ListingGrid-ListingGridItem img'); if (!thumb) return alert('No gallery images found.'); thumb.click(); const iv = setInterval(() => { if (document.querySelector('#pageOverlaySlot img')) { clearInterval(iv); toggleOverlay(); } }, 300); } } // Inject the “Grid View” button next to the title function addGridButton() { const sel = 'h1.Title.PhotosetGallery-PhotosetTitle-Title'; const insert = () => { const h1 = document.querySelector(sel); if (h1 && !document.getElementById('ztGridBtn')) { const btn = document.createElement('button'); btn.id = 'ztGridBtn'; btn.textContent = 'Grid View'; Object.assign(btn.style, { marginLeft:'20px', padding:'8px 16px', fontSize:'16px', cursor:'pointer', backgroundColor:'yellow', border:'1px solid #999', borderRadius:'4px', fontWeight:'bold' }); btn.addEventListener('click', launchGridOverlay); h1.parentNode.insertBefore(btn, h1.nextSibling); return true; } return false; }; if (!insert()) { const obs = new MutationObserver((m,o)=> { if (insert()) o.disconnect(); }); obs.observe(document.body,{childList:true,subtree:true}); setInterval(insert, 500); } } if (document.readyState!=='loading') addGridButton(); else document.addEventListener('DOMContentLoaded', addGridButton); })();