您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Добавляет удобный просмотр изображений с перелистыванием и предзагрузкой для PostImg галерей
// ==UserScript== // @name PostImg Gallery Viewer // @namespace http://tampermonkey.net/ // @version 1.1 // @description Добавляет удобный просмотр изображений с перелистыванием и предзагрузкой для PostImg галерей // @author NastyaLove // @license MIT // @match https://postimg.cc/gallery/* // @icon https://www.google.com/s2/favicons?sz=64&domain=postimg.cc // @grant none // ==/UserScript== (function() { 'use strict'; // Собираем все изображения из галереи const images = []; document.querySelectorAll('#thumb-list > .thumb-container').forEach(el => { const imageKey = el.dataset.image; const hotlink = el.dataset.hotlink; const name = el.dataset.name; const ext = el.dataset.ext; const thumbnailUrl = `https://i.postimg.cc/${imageKey}/${name}.${ext}`; const fullUrl = `https://i.postimg.cc/${hotlink}/${name}.${ext}`; images.push({ thumbnail: thumbnailUrl, full: fullUrl, name: `${name}.${ext}`, key: imageKey, hotlink: hotlink, loaded: false, preloadedImage: null }); }); if (images.length === 0) return; let currentIndex = 0; const PRELOAD_COUNT = 3; // Количество изображений для предзагрузки вперед и назад // Создаем стили const style = document.createElement('style'); style.textContent = ` .gallery-viewer { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.95); z-index: 9999; display: none; flex-direction: column; } .gallery-viewer.active { display: flex; } .gallery-header { padding: 15px 20px; background: rgba(0, 0, 0, 0.8); color: white; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .gallery-info { display: flex; flex-direction: column; gap: 5px; } .gallery-counter { font-size: 16px; font-weight: bold; } .gallery-title { font-size: 14px; color: #ccc; max-width: 600px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .gallery-preload-status { font-size: 12px; color: #95a5a6; } .gallery-close { background: #e74c3c; border: none; color: white; padding: 8px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: bold; transition: background 0.2s; } .gallery-close:hover { background: #c0392b; } .gallery-main { flex: 1; display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden; padding: 20px; } .gallery-image { max-width: 100%; max-height: 100%; object-fit: contain; user-select: none; opacity: 0; transition: opacity 0.2s; } .gallery-image.loaded { opacity: 1; } .gallery-nav { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(0, 0, 0, 0.7); border: none; color: white; padding: 20px 15px; cursor: pointer; font-size: 24px; border-radius: 4px; transition: background 0.2s; z-index: 10; } .gallery-nav:hover { background: rgba(0, 0, 0, 0.9); } .gallery-nav:disabled { opacity: 0.3; cursor: not-allowed; } .gallery-nav-prev { left: 20px; } .gallery-nav-next { right: 20px; } .gallery-thumbnails { background: rgba(0, 0, 0, 0.8); padding: 15px; border-top: 1px solid rgba(255, 255, 255, 0.1); overflow-x: auto; overflow-y: hidden; white-space: nowrap; max-height: 150px; } .gallery-thumbnails::-webkit-scrollbar { height: 8px; } .gallery-thumbnails::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); } .gallery-thumbnails::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 4px; } .gallery-thumb { display: inline-block; width: 100px; height: 100px; margin-right: 10px; cursor: pointer; border: 3px solid transparent; border-radius: 4px; overflow: hidden; transition: border-color 0.2s; position: relative; } .gallery-thumb:hover { border-color: rgba(255, 255, 255, 0.5); } .gallery-thumb.active { border-color: #3498db; } .gallery-thumb.preloaded::after { content: '✓'; position: absolute; top: 2px; right: 2px; background: #27ae60; color: white; width: 18px; height: 18px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; } .gallery-thumb img { width: 100%; height: 100%; object-fit: cover; } .gallery-loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 20px; display: flex; flex-direction: column; align-items: center; gap: 10px; } .gallery-spinner { width: 40px; height: 40px; border: 4px solid rgba(255, 255, 255, 0.3); border-top-color: white; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .open-gallery-btn { position: fixed; bottom: 20px; right: 20px; background: #3498db; color: white; border: none; padding: 15px 30px; border-radius: 50px; cursor: pointer; font-size: 16px; font-weight: bold; box-shadow: 0 4px 15px rgba(52, 152, 219, 0.4); transition: all 0.3s; z-index: 1000; } .open-gallery-btn:hover { background: #2980b9; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(52, 152, 219, 0.6); } `; document.head.appendChild(style); // Создаем HTML структуру галереи const viewer = document.createElement('div'); viewer.className = 'gallery-viewer'; viewer.innerHTML = ` <div class="gallery-header"> <div class="gallery-info"> <div class="gallery-counter"> <span class="current">1</span> / <span class="total">${images.length}</span> </div> <div class="gallery-title"></div> <div class="gallery-preload-status"></div> </div> <button class="gallery-close">✕ Закрыть</button> </div> <div class="gallery-main"> <button class="gallery-nav gallery-nav-prev">‹</button> <img class="gallery-image" src="" alt=""> <div class="gallery-loading"> <div class="gallery-spinner"></div> <div>Загрузка...</div> </div> <button class="gallery-nav gallery-nav-next">›</button> </div> <div class="gallery-thumbnails"></div> `; document.body.appendChild(viewer); // Кнопка открытия галереи const openBtn = document.createElement('button'); openBtn.className = 'open-gallery-btn'; openBtn.textContent = `📷 Просмотр (${images.length})`; document.body.appendChild(openBtn); // Элементы const mainImage = viewer.querySelector('.gallery-image'); const loading = viewer.querySelector('.gallery-loading'); const counterCurrent = viewer.querySelector('.current'); const counterTotal = viewer.querySelector('.total'); const titleEl = viewer.querySelector('.gallery-title'); const preloadStatus = viewer.querySelector('.gallery-preload-status'); const prevBtn = viewer.querySelector('.gallery-nav-prev'); const nextBtn = viewer.querySelector('.gallery-nav-next'); const closeBtn = viewer.querySelector('.gallery-close'); const thumbnailsContainer = viewer.querySelector('.gallery-thumbnails'); // Создаем миниатюры const thumbElements = []; images.forEach((img, index) => { const thumb = document.createElement('div'); thumb.className = 'gallery-thumb'; thumb.innerHTML = `<img src="${img.thumbnail}" alt="${img.name}">`; thumb.addEventListener('click', () => showImage(index)); thumbnailsContainer.appendChild(thumb); thumbElements.push(thumb); }); // Функция предзагрузки изображения function preloadImage(index) { if (index < 0 || index >= images.length) return Promise.resolve(); if (images[index].loaded) return Promise.resolve(); return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { images[index].loaded = true; images[index].preloadedImage = img; thumbElements[index].classList.add('preloaded'); updatePreloadStatus(); resolve(); }; img.onerror = () => { console.warn(`Ошибка предзагрузки изображения ${index}`); reject(); }; img.src = images[index].full; }); } // Предзагрузка окружающих изображений function preloadSurroundingImages(centerIndex) { const promises = []; // Предзагружаем текущее изображение с наивысшим приоритетом if (!images[centerIndex].loaded) { promises.push(preloadImage(centerIndex)); } // Предзагружаем следующие изображения for (let i = 1; i <= PRELOAD_COUNT; i++) { const nextIndex = centerIndex + i; if (nextIndex < images.length && !images[nextIndex].loaded) { promises.push(preloadImage(nextIndex)); } } // Предзагружаем предыдущие изображения for (let i = 1; i <= PRELOAD_COUNT; i++) { const prevIndex = centerIndex - i; if (prevIndex >= 0 && !images[prevIndex].loaded) { promises.push(preloadImage(prevIndex)); } } return Promise.all(promises); } // Обновление статуса предзагрузки function updatePreloadStatus() { const loadedCount = images.filter(img => img.loaded).length; preloadStatus.textContent = `Предзагружено: ${loadedCount}/${images.length}`; } // Показать изображение function showImage(index) { if (index < 0 || index >= images.length) return; currentIndex = index; const img = images[index]; // Обновляем счетчик counterCurrent.textContent = index + 1; titleEl.textContent = img.name; // Показываем загрузку loading.style.display = 'flex'; mainImage.classList.remove('loaded'); // Если изображение уже предзагружено, показываем его сразу if (img.loaded && img.preloadedImage) { mainImage.src = img.preloadedImage.src; mainImage.classList.add('loaded'); loading.style.display = 'none'; } else { // Загружаем изображение const tempImg = new Image(); tempImg.onload = () => { mainImage.src = img.full; mainImage.classList.add('loaded'); loading.style.display = 'none'; img.loaded = true; img.preloadedImage = tempImg; thumbElements[index].classList.add('preloaded'); updatePreloadStatus(); }; tempImg.onerror = () => { loading.innerHTML = '<div>Ошибка загрузки</div>'; }; tempImg.src = img.full; } // Обновляем активную миниатюру thumbElements.forEach((thumb, i) => { thumb.classList.toggle('active', i === index); }); // Прокручиваем к активной миниатюре const activeThumb = thumbnailsContainer.children[index]; if (activeThumb) { activeThumb.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } // Обновляем кнопки навигации prevBtn.disabled = index === 0; nextBtn.disabled = index === images.length - 1; // Запускаем предзагрузку окружающих изображений preloadSurroundingImages(index); } // Навигация prevBtn.addEventListener('click', () => showImage(currentIndex - 1)); nextBtn.addEventListener('click', () => showImage(currentIndex + 1)); // Клавиатурная навигация document.addEventListener('keydown', (e) => { if (!viewer.classList.contains('active')) return; if (e.key === 'ArrowLeft') showImage(currentIndex - 1); if (e.key === 'ArrowRight') showImage(currentIndex + 1); if (e.key === 'Escape') closeViewer(); }); // Открыть/закрыть галерею function openViewer() { viewer.classList.add('active'); showImage(0); document.body.style.overflow = 'hidden'; } function closeViewer() { viewer.classList.remove('active'); document.body.style.overflow = ''; } openBtn.addEventListener('click', openViewer); closeBtn.addEventListener('click', closeViewer); // Закрытие по клику на фон viewer.addEventListener('click', (e) => { if (e.target === viewer) closeViewer(); }); // Инициализация - предзагружаем первые несколько изображений updatePreloadStatus(); preloadSurroundingImages(0).then(() => { console.log(`PostImg Gallery Viewer: Найдено ${images.length} изображений, предзагружено первые ${Math.min(PRELOAD_COUNT + 1, images.length)}`); }); })();