Civitai Larger Thumbnails + Arrow Key Navigation

Enlarges thumbnails to take up the whole screen and adds Up/Down arrow key navigation to instantly go to the next thumbnail. No more tired eyes from constantly moving your eyes from thumbnail to thumbnail.

// ==UserScript==
// @name        Civitai Larger Thumbnails + Arrow Key Navigation
// @namespace   Violentmonkey Scripts
// @match       *://*.civitai.com/*
// @grant       GM_addStyle
// @version     1.21
// @author      rainlizard
// @license     MIT
// @description Enlarges thumbnails to take up the whole screen and adds Up/Down arrow key navigation to instantly go to the next thumbnail. No more tired eyes from constantly moving your eyes from thumbnail to thumbnail.
// ==/UserScript==

(function() {
    'use strict';

    let currentIndex = 0;
    let thumbnails = [];
    let isInitialized = false;

    // Function to wait for elements to appear
    function waitForElement(selector, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const element = document.querySelector(selector);
            if (element) {
                resolve(element);
                return;
            }

            const observer = new MutationObserver((mutations, obs) => {
                const element = document.querySelector(selector);
                if (element) {
                    obs.disconnect();
                    resolve(element);
                }
            });

            observer.observe(document, {
                childList: true,
                subtree: true
            });

            setTimeout(() => {
                observer.disconnect();
                reject(new Error(`Element ${selector} not found within ${timeout}ms`));
            }, timeout);
        });
    }

    // Function to calculate optimal thumbnail height
    function calculateThumbnailHeight() {
        const viewportHeight = window.innerHeight;

        // Check for header height
        const headerSelectors = [
            'header',
            '[role="banner"]',
            '.header',
            '.navbar',
            'nav:first-of-type'
        ];

        let headerHeight = 0;
        for (const selector of headerSelectors) {
            const header = document.querySelector(selector);
            if (header) {
                const rect = header.getBoundingClientRect();
                if (rect.top >= -10 && rect.height > 0) {
                    headerHeight = Math.max(headerHeight, rect.height);
                }
            }
        }

        // Use full viewport height minus header
        return viewportHeight - headerHeight;
    }

    // Function to find all thumbnail cards directly
    function findThumbnails() {
        // Look for thumbnails by their specific structure
        const thumbnailElements = document.querySelectorAll('.AspectRatioImageCard_content__IGj_A');

        // Get the parent containers (the actual thumbnail divs)
        const thumbnailContainers = Array.from(thumbnailElements).map(content => {
            // The parent should be the thumbnail container
            let parent = content.parentElement;
            // Look for the container with the aspect-ratio style or class structure
            while (parent && !parent.style.aspectRatio && !parent.classList.contains('relative')) {
                parent = parent.parentElement;
            }
            return parent;
        }).filter(Boolean);

        console.log(`Found ${thumbnailContainers.length} thumbnail containers`);
        return thumbnailContainers;
    }

    // Update thumbnail list
    function updateThumbnailList() {
        thumbnails = findThumbnails();
        if (currentIndex >= thumbnails.length && thumbnails.length > 0) {
            currentIndex = thumbnails.length - 1;
        } else if (thumbnails.length === 0) {
            currentIndex = 0;
        }
    }

    // Function to update all thumbnail heights
    function updateThumbnailHeights() {
        const newHeight = calculateThumbnailHeight();
        const heightValue = `${newHeight}px`;

        // Update CSS custom property
        document.documentElement.style.setProperty('--civitai-thumbnail-height', heightValue);

        // Update existing thumbnails
        thumbnails.forEach(thumbnail => {
            if (thumbnail.classList.contains('civitai-large-thumbnail')) {
                thumbnail.style.height = heightValue;
                thumbnail.style.minHeight = heightValue;
            }
        });
    }

    // Apply CSS for large thumbnails
    GM_addStyle(`
        html, body {
            scroll-behavior: smooth !important;
        }

        body {
            overflow-x: hidden !important;
        }

        /* Container for thumbnails */
        .civitai-thumbnail-container {
            display: flex !important;
            flex-direction: column !important;
            align-items: center !important;
            width: 100% !important;
            gap: 0px !important;
        }

        /* Large thumbnail styling */
        .civitai-large-thumbnail {
            width: 100vw !important;
            max-width: 100% !important;
            height: var(--civitai-thumbnail-height, 100vh) !important;
            min-height: var(--civitai-thumbnail-height, 100vh) !important;
            display: flex !important;
            flex-direction: column !important;
            justify-content: center !important;
            align-items: center !important;
            padding: 20px !important;
            box-sizing: border-box !important;
            background-color: #1A1B1E !important;
            position: relative !important;
            margin: 0 !important;
            aspect-ratio: auto !important;
            border-radius: 0 !important;
        }

        /* Style the content area */
        .civitai-large-thumbnail .AspectRatioImageCard_content__IGj_A {
            width: 100% !important;
            height: 100% !important;
            display: flex !important;
            flex-direction: column !important;
            justify-content: center !important;
            align-items: center !important;
        }

        /* Style the link */
        .civitai-large-thumbnail .AspectRatioImageCard_linkOrClick__d_K_4 {
            width: 90% !important;
            height: 80% !important;
            display: flex !important;
            justify-content: center !important;
            align-items: center !important;
        }

        /* Style images within large thumbnails */
        .civitai-large-thumbnail .AspectRatioImageCard_image__1xNTQ {
            max-width: 100vw !important;
            max-height: 100vh !important;
            width: auto !important;
            height: auto !important;
            object-fit: contain !important;
            border-radius: 8px !important;
        }

        /* Style the footer with no background */
        .civitai-large-thumbnail .AspectRatioImageCard_footer__FOU7a {
            position: absolute !important;
            bottom: 20px !important;
            left: 20px !important;
            right: 20px !important;
            background: rgba(0, 0, 0, 0) !important;
            padding: 20px !important;
            border-radius: 10px !important;
            color: white !important;
        }

        /* Style the header with no background */
        .civitai-large-thumbnail .AspectRatioImageCard_header__Mmd__ {
            position: absolute !important;
            top: 20px !important;
            right: 20px !important;
            background: rgba(0, 0, 0, 0) !important;
            padding: 10px !important;
            border-radius: 10px !important;
        }

        /* Navigation indicator */
        .civitai-nav-indicator {
            position: fixed !important;
            bottom: 20px !important;
            right: 20px !important;
            background: rgba(0, 0, 0, 0.8) !important;
            color: white !important;
            padding: 10px 15px !important;
            border-radius: 5px !important;
            font-size: 14px !important;
            z-index: 1000 !important;
            font-family: Arial, sans-serif !important;
            opacity: 0.8 !important;
        }
    `);

    // Scroll to specific thumbnail
    function scrollToThumbnail(index) {
        updateThumbnailList();
        if (index >= 0 && index < thumbnails.length) {
            currentIndex = index;
            const targetThumbnail = thumbnails[currentIndex];
            if (targetThumbnail) {
                targetThumbnail.scrollIntoView({
                    behavior: 'smooth',
                    block: 'center',
                    inline: 'nearest'
                });

                // Update navigation indicator
                const indicator = document.querySelector('.civitai-nav-indicator');
                if (indicator) {
                    indicator.textContent = `${currentIndex + 1} / ${thumbnails.length} - ↑↓ Navigate`;
                }
            }
        }
    }

    // Keyboard navigation
    document.addEventListener('keydown', function(e) {
        updateThumbnailList();
        if (thumbnails.length === 0) return;

        if (e.key === 'ArrowDown') {
            e.preventDefault();
            if (currentIndex < thumbnails.length - 1) {
                scrollToThumbnail(currentIndex + 1);
            }
        } else if (e.key === 'ArrowUp') {
            e.preventDefault();
            if (currentIndex > 0) {
                scrollToThumbnail(currentIndex - 1);
            }
        }
    });

    // Apply large thumbnail styling to all thumbnails
    function applyLargeThumbnailStyling() {
        updateThumbnailList();

        if (thumbnails.length === 0) return;

        // Find the parent container and modify its layout
        const firstThumbnail = thumbnails[0];
        const parentContainer = firstThumbnail.parentElement;

        if (parentContainer) {
            parentContainer.classList.add('civitai-thumbnail-container');
        }

        // Apply styling to each thumbnail
        thumbnails.forEach(thumbnail => {
            thumbnail.classList.add('civitai-large-thumbnail');
        });

        // Update heights
        updateThumbnailHeights();

        console.log(`Applied large styling to ${thumbnails.length} thumbnails`);
    }

    // Function to periodically check for new content
    function checkForUpdates() {
        const newThumbnails = findThumbnails();
        if (newThumbnails.length !== thumbnails.length) {
            console.log(`Updated: Now have ${newThumbnails.length} thumbnails`);

            // Apply styling to new thumbnails
            newThumbnails.forEach(thumbnail => {
                if (!thumbnail.classList.contains('civitai-large-thumbnail')) {
                    thumbnail.classList.add('civitai-large-thumbnail');
                }
            });

            thumbnails = newThumbnails;
            updateThumbnailHeights();
        }
    }

    // Function to observe viewport changes
    function observeViewportChanges() {
        // Listen for window resize
        window.addEventListener('resize', () => {
            updateThumbnailHeights();
        });

        // Listen for scroll to detect header changes
        let scrollTimeout;
        window.addEventListener('scroll', () => {
            clearTimeout(scrollTimeout);
            scrollTimeout = setTimeout(() => {
                updateThumbnailHeights();
            }, 100);
        });
    }

    // Initialize the script
    function init() {
        console.log('Civitai Larger Thumbnails script loaded');

        // Calculate initial height
        const initialHeight = calculateThumbnailHeight();
        document.documentElement.style.setProperty('--civitai-thumbnail-height', `${initialHeight}px`);

        // Wait for the main content area to load
        waitForElement('#main')
            .then(() => {
                setTimeout(() => {
                    applyLargeThumbnailStyling();

                    if (thumbnails.length > 0) {
                        console.log(`Applied large styling to ${thumbnails.length} thumbnails`);

                        // Add navigation indicator
                        const indicator = document.createElement('div');
                        indicator.className = 'civitai-nav-indicator';
                        indicator.textContent = `1 / ${thumbnails.length} - ↑↓ Navigate`;
                        document.body.appendChild(indicator);

                        // Set up viewport observation
                        observeViewportChanges();

                        isInitialized = true;
                    }

                    // Set up periodic checks for new content
                    setInterval(checkForUpdates, 2000);
                }, 2000);
            })
            .catch(error => {
                console.error('Civitai script: Failed to find main content area', error);
            });
    }

    // Start initialization after a delay to ensure page is ready
    setTimeout(init, 1000);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址