您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Interactive Inline Gallery Carousel with full-res Images for Reddit Search Gallery.
当前为
// ==UserScript== // @name Reddit Search Preview Inline Interactive Gallery Carousel // @namespace http://tampermonkey.net/ // @version 1.2 // @description Interactive Inline Gallery Carousel with full-res Images for Reddit Search Gallery. // @author UniverseDev // @license GPL-3.0-or-later // @icon https://www.reddit.com/favicon.ico // @match *://*.reddit.com/search/*type=media* // @grant GM_addStyle // @grant GM.xmlHttpRequest // ==/UserScript== (() => { 'use strict'; GM_addStyle(` .reddit-carousel { position: relative; overflow: hidden; width: 100%; height: 100%; min-height: 200px; margin-bottom: 0; border: none; cursor: default; z-index: 1; } .reddit-carousel-slide-container { display: flex; transition: transform 125ms ease; z-index: 1; height: 100%; } .reddit-carousel-slide { flex: 0 0 100%; width: 100%; text-align: center; min-height: 200px; background-color: black; /* Black background for slides */ z-index: 1; display: flex; align-items: center; justify-content: center; } .reddit-carousel-slide img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; object-position: center; /* Centers the image */ display: block; margin: auto; background-color: black; /* Ensures the image's background remains black */ z-index: 1; } .reddit-carousel-error { color: red; font-size: 14px; padding: 20px; z-index: 1; } .reddit-carousel-arrow { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(0,0,0,0.7); border: none; width: 30px; height: 30px; cursor: pointer; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 2; transition: background 0.2s ease, transform 0.15s ease; color: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.4); pointer-events: auto; } .reddit-carousel-arrow:hover { background: rgba(0,0,0,0.85); transform: translateY(-50%) scale(1.1); box-shadow: 0 4px 8px rgba(0,0,0,0.5); } .reddit-carousel-arrow:active { transform: translateY(-50%) scale(0.95); } .reddit-carousel-arrow svg { width: 16px; height: 16px; fill: currentColor; } .reddit-carousel-arrow.left { left: 10px; display: none; } .reddit-carousel-arrow.right { right: 10px; display: flex; } `); const redditCarousel_animateTransition = (container, start, end, duration, callback) => { const startTime = performance.now(); const step = now => { const progress = Math.min((now - startTime) / duration, 1); const ease = 1 - Math.pow(1 - progress, 3); container.style.transform = `translateX(${start + (end - start) * ease}px)`; if (progress < 1) requestAnimationFrame(step); else if (callback) callback(); }; requestAnimationFrame(step); }; const redditCarousel_createCarousel = (urls, mediaMeta, altText) => { if (!urls?.length) return null; const carousel = document.createElement('div'); carousel.classList.add('reddit-carousel'); carousel.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); }); const slideContainer = document.createElement('div'); slideContainer.classList.add('reddit-carousel-slide-container'); carousel.appendChild(slideContainer); urls.forEach(url => { const slide = document.createElement('div'); slide.classList.add('reddit-carousel-slide'); const img = document.createElement('img'); img.src = url; img.alt = altText; img.onerror = function() { slide.innerHTML = '<div class="reddit-carousel-error">Image failed to load</div>'; }; img.addEventListener('load', () => { redditCarousel_recalcDimensions(); redditCarousel_updateArrowVisibility(); updateArrowPositions(); }); slide.appendChild(img); slideContainer.appendChild(slide); }); const carouselCounterWrapper = document.createElement('div'); carouselCounterWrapper.innerHTML = `<div class="absolute inset-0 overflow-visible flex items-right justify-end"> <button rpl="" class="pointer-events-none m-xs leading-4 pl-2xs pr-2xs py-0 text-sm h-fit button-small px-[var(--rem10)] button-media items-center justify-center button inline-flex "> <span class="flex items-center justify-center"> <span class="carousel-counter flex items-center gap-xs">1/${urls.length}</span> </span> </button> </div>`; carousel.appendChild(carouselCounterWrapper); let redditCarousel_currentIndex = 0, redditCarousel_currentOffset = 0; const redditCarousel_recalcDimensions = () => { const containerWidth = slideContainer.clientWidth; redditCarousel_currentOffset = -redditCarousel_currentIndex * containerWidth; slideContainer.style.transform = `translateX(${redditCarousel_currentOffset}px)`; }; const redditCarousel_updateCounter = () => { const newCounterText = `${redditCarousel_currentIndex + 1}/${urls.length}`; const carouselCounter = carousel.querySelector('.carousel-counter'); if (carouselCounter) { carouselCounter.textContent = newCounterText; } }; const redditCarousel_goToSlide = index => { index = Math.max(0, Math.min(index, urls.length - 1)); const containerWidth = slideContainer.clientWidth; const startOffset = redditCarousel_currentOffset; const endOffset = -index * containerWidth; redditCarousel_animateTransition(slideContainer, startOffset, endOffset, 125, () => { redditCarousel_currentOffset = endOffset; redditCarousel_currentIndex = index; redditCarousel_updateCounter(); redditCarousel_updateArrowVisibility(); updateArrowPositions(); }); }; let redditCarousel_leftArrow, redditCarousel_rightArrow; if (urls.length > 1) { const redditCarousel_createArrow = dir => { const btn = document.createElement('button'); btn.classList.add('reddit-carousel-arrow', dir); btn.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); }); const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 20 20"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M12.793 19.707l-9-9a1 1 0 0 1 0-1.414l9-9 1.414 1.414L5.914 10l8.293 8.293-1.414 1.414Z"); svg.appendChild(path); if (dir === 'right') { svg.style.transform = 'scaleX(-1)'; } btn.appendChild(svg); return btn; }; redditCarousel_leftArrow = redditCarousel_createArrow('left'); redditCarousel_leftArrow.addEventListener('click', () => { redditCarousel_goToSlide(redditCarousel_currentIndex - 1); }); carousel.appendChild(redditCarousel_leftArrow); redditCarousel_rightArrow = redditCarousel_createArrow('right'); redditCarousel_rightArrow.addEventListener('click', () => { redditCarousel_goToSlide(redditCarousel_currentIndex + 1); }); carousel.appendChild(redditCarousel_rightArrow); } const redditCarousel_updateArrowVisibility = () => { if (urls.length <= 1) return; if (redditCarousel_currentIndex === 0) { redditCarousel_leftArrow.style.display = 'none'; redditCarousel_rightArrow.style.display = 'flex'; } else if (redditCarousel_currentIndex === urls.length - 1) { redditCarousel_leftArrow.style.display = 'flex'; redditCarousel_rightArrow.style.display = 'none'; } else { redditCarousel_leftArrow.style.display = 'flex'; redditCarousel_rightArrow.style.display = 'flex'; } }; const updateArrowPositions = () => { requestAnimationFrame(() => { const rect = carousel.getBoundingClientRect(); const arrowTop = (rect.height - 30) / 2; if (redditCarousel_leftArrow) { redditCarousel_leftArrow.style.top = arrowTop + 'px'; } if (redditCarousel_rightArrow) { redditCarousel_rightArrow.style.top = arrowTop + 'px'; } }); }; updateArrowPositions(); let redditCarousel_resizeTimeout; window.addEventListener('resize', () => { clearTimeout(redditCarousel_resizeTimeout); redditCarousel_resizeTimeout = setTimeout(() => { redditCarousel_recalcDimensions(); redditCarousel_updateArrowVisibility(); updateArrowPositions(); }, 100); }); redditCarousel_updateArrowVisibility(); return carousel; }; const redditCarousel_fetchAndProcessGallery = (postURL, container) => { GM.xmlHttpRequest({ url: `${postURL}.json`, method: 'GET', onload: response => { if (response.status >= 200 && response.status < 300) { try { const jsonData = JSON.parse(response.responseText); const postData = jsonData[0]?.data?.children[0]?.data; if (!postData) return; const altText = postData.title || "Reddit Gallery Image"; let fullResUrls = []; let previewUrls = []; let mediaMeta = {}; if (postData?.gallery_data && postData?.media_metadata) { const { items } = postData.gallery_data; mediaMeta = postData.media_metadata; fullResUrls = items.reduce((acc, item) => { const meta = mediaMeta[item.media_id]; if (meta && meta.id && meta.m) { let ext = "jpg"; if (meta.m.includes("png")) ext = "png"; else if (meta.m.includes("gif")) ext = "gif"; else if (meta.m.includes("webp")) ext = "webp"; acc.push(`https://i.redd.it/${meta.id}.${ext}`); } return acc; }, []); } if (fullResUrls.length === 0) { console.log("Full-res URLs extraction failed, falling back to previews..."); if (postData.preview && postData.preview.images && postData.preview.images.length > 0) { const previewImages = postData.preview.images; previewUrls = previewImages.map(image => { let previewUrl = image.source?.url; if (previewUrl) { previewUrl = previewUrl.replace(/&/g, '&'); return previewUrl; } return null; }).filter(url => url); } else if (postData.thumbnail && postData.thumbnail.startsWith('http')) { previewUrls = [postData.thumbnail]; } else if (postData?.gallery_data && postData?.media_metadata) { const { items } = postData.gallery_data; mediaMeta = postData.media_metadata; previewUrls = items.reduce((acc, item) => { const meta = mediaMeta[item.media_id]; if (meta && meta.id && meta.p && meta.p.u) { let previewUrl = meta.p.u; previewUrl = previewUrl.replace(/&/g, '&'); acc.push(previewUrl); } return acc; }, []); } } const urlsToUse = fullResUrls.length > 0 ? fullResUrls : previewUrls; const carousel = redditCarousel_createCarousel(urlsToUse, mediaMeta, altText); if (carousel) { const targetElement = container.querySelector('.relative.w-full.h-full'); if (targetElement) { targetElement.innerHTML = ''; targetElement.appendChild(carousel); const parentLink = targetElement.closest('a'); if (parentLink) { parentLink.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); }); } } } } catch (error) { console.error("JSON parse error:", error); } } }, onerror: err => console.error("Request failed:", err) }); }; const redditCarousel_galleryObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const container = entry.target; if (!container.hasAttribute('data-gallery-intersected')) { container.setAttribute('data-gallery-intersected', 'true'); const postUnit = container.closest('div[data-id="search-media-post-unit"]'); const postLink = postUnit?.querySelector('a.no-underline'); if (postLink?.href) { redditCarousel_fetchAndProcessGallery(postLink.href, container); } } observer.unobserve(container); } }); }, { threshold: 0.1 }); const redditCarousel_processSearchResults = () => { document.querySelectorAll('div[data-id="search-media-post-unit"]').forEach(post => { if (post.hasAttribute('data-gallery-checked')) return; post.setAttribute('data-gallery-checked', 'true'); const indicator = post.querySelector('div.absolute.inset-0.overflow-visible.flex.items-right.justify-end button span'); if (indicator?.textContent.includes('/')) { const container = post.querySelector('shreddit-aspect-ratio'); if (container) redditCarousel_galleryObserver.observe(container); } }); }; let redditCarousel_ticking = false; window.addEventListener('scroll', () => { if (!redditCarousel_ticking) { requestAnimationFrame(() => { redditCarousel_processSearchResults(); redditCarousel_ticking = false; }); redditCarousel_ticking = true; } }); window.addEventListener('load', redditCarousel_processSearchResults); })();