Rule34 Auto Fit Image Toggle + Auto Scroll

Fit Rule34 post images to screen, click to toggle original size, and stably auto-scroll to image

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Rule34 Auto Fit Image Toggle + Auto Scroll
// @namespace    https://rule34.xxx/
// @version      1.3
// @description  Fit Rule34 post images to screen, click to toggle original size, and stably auto-scroll to image
// @author       icynic
// @match        https://rule34.xxx/index.php?page=post&s=view*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let fitted = true;
    let initialized = false;
    let autoScrolled = false;

    function getImage() {
        return document.querySelector('#image');
    }

    function applyFit(img) {
        img.removeAttribute('width');
        img.removeAttribute('height');

        img.style.maxWidth = 'calc(100vw - 40px)';
        img.style.maxHeight = 'calc(100vh - 40px)';
        img.style.width = 'auto';
        img.style.height = 'auto';
        img.style.objectFit = 'contain';
        img.style.display = 'block';
        img.style.cursor = 'zoom-in';

        fitted = true;
    }

    function applyOriginal(img) {
        img.style.maxWidth = 'none';
        img.style.maxHeight = 'none';
        img.style.width = img.naturalWidth + 'px';
        img.style.height = img.naturalHeight + 'px';
        img.style.cursor = 'zoom-out';

        fitted = false;
    }

    function scrollToImageStable(img) {
        // 这里控制图片顶部距离屏幕顶部的空隙
        const topGap = 8;

        const rect = img.getBoundingClientRect();
        const targetY = window.scrollY + rect.top - topGap;

        window.scrollTo({
            top: targetY,
            left: 0,
            behavior: 'auto'
        });
    }

    function stableAutoScroll() {
        const img = getImage();
        if (!img) return;

        applyFit(img);

        let attempts = 0;
        const maxAttempts = 12;
        const topGap = 8;

        const timer = setInterval(() => {
            const rect = img.getBoundingClientRect();

            // 如果图片顶部已经接近目标位置,就停止修正
            if (Math.abs(rect.top - topGap) <= 2) {
                clearInterval(timer);
                autoScrolled = true;
                return;
            }

            scrollToImageStable(img);

            attempts++;
            if (attempts >= maxAttempts) {
                clearInterval(timer);
                autoScrolled = true;
            }
        }, 150);
    }

    function init(shouldAutoScroll = false) {
        const img = getImage();
        if (!img) return;

        applyFit(img);

        if (!initialized) {
            initialized = true;

            img.addEventListener('click', function (event) {
                event.preventDefault();
                event.stopPropagation();

                if (fitted) {
                    applyOriginal(img);
                } else {
                    applyFit(img);
                    setTimeout(() => scrollToImageStable(img), 50);
                }
            }, true);
        }

        if (shouldAutoScroll && !autoScrolled) {
            stableAutoScroll();
        }
    }

    document.addEventListener('DOMContentLoaded', () => {
        init(true);
    });

    window.addEventListener('load', () => {
        init(true);
    });

    // 等页面自己的脚本、广告、图片尺寸变化稳定一点
    setTimeout(() => init(true), 300);
    setTimeout(() => init(true), 800);
    setTimeout(() => init(true), 1500);
})();