Hitomi页面滚动条

可上下滚动翻页,并可调整"Fit"模式图像宽度。

当前为 2024-12-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name:ko           Hitomi 페이지 스크롤러
// @name              Hitomi page scroller
// @name:ru           Hitomi прокрутка страниц
// @name:ja           Hitomiページスクローラー
// @name:zh-TW        Hitomi頁面滾動條
// @name:zh-CN        Hitomi页面滚动条

// @description:ko    위아래로 스크롤하여 페이지를 넘길 수 있으며, "Fit"모드 이미지 넓이를 조절 할 수 있습니다.
// @description       You can scroll up and down to turn the page and adjust the "Fit" mode image area.
// @description:ru    Вы можете прокручивать страницы вверх и вниз, регулируя ширину изображения в режиме "Fit".
// @description:ja    上下にスクロールしてページをめくることができ、「Fit」モードイメージの広さを調節することができます。
// @description:zh-TW 可上下滾動翻頁,並可調整"Fit"模式圖像寬度。
// @description:zh-CN 可上下滚动翻页,并可调整"Fit"模式图像宽度。

// @namespace         https://ndaesik.tistory.com/
// @version           2024.12.04.17.32
// @author            ndaesik
// @icon              https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://hitomi.la
// @match             https://*.la/reader/*

// @grant             GM_getValue
// @grant             GM_setValue
// ==/UserScript==

(function() {
    'use strict';
    const startPage = parseInt(window.location.hash.slice(1)) || 1;
    let currentPage = startPage;
    let isLoading = false;
    let loadQueue = 0;
    let upScrollCount = 0;
    let lastScrollTime = 0;
    let initialLoad = true;
    const baseUrl = window.location.href.split('#')[0];
    let paddingSize = GM_getValue('paddingSize', 20);

    const style = document.createElement('style');
    style.textContent = `
        #comicImages {
            padding: 0 ${paddingSize}vw !important;
            width: 100vw !important;
            box-sizing: border-box !important;
            user-select: none !important;
            display: block !important;
            position: relative !important;
        }
        #comicImages picture {
            pointer-events: none !important;
            display: block !important;
            padding: 3px 0 !important
        }
        #comicImages img {
            width: 100% !important;
            display: block !important;
        }
        #comicImages.fitVertical img {
            max-height: unset !important;
        }
        .width-control-container {
            display: flex !important;
            align-items: center !important;
            gap: 10px !important;
            padding: 12px !important;
        }
        .width-range {
            width: 100px !important;
        }
    `;
    document.head.appendChild(style);

    function createPaddingControl() {
        const navbarNav = document.querySelector('.navbar-nav');
        if (!navbarNav) return;

        const container = document.createElement('div');
        container.className = 'width-control-container';
        const range = document.createElement('input');
        range.type = 'range';
        range.className = 'width-range';
        range.min = '0';
        range.max = '45';
        range.value = paddingSize;

        range.addEventListener('input', (e) => {
            paddingSize = e.target.value;
            document.querySelector('#comicImages').style.cssText += `padding: 0 ${paddingSize}vw !important;`;
            GM_setValue('paddingSize', paddingSize);
        });

        container.appendChild(range);
        navbarNav.appendChild(container);
    }

    function updateCurrentPage() {
        const container = document.querySelector('#comicImages');
        if (!container) return;
        const pictures = container.querySelectorAll('picture');
        if (!pictures.length) return;

        const pageSelect = document.querySelector('#single-page-select');
        if (!pageSelect) return;

        // 현재 뷰포트의 중앙 위치 계산
        const viewportHeight = window.innerHeight;
        const viewportCenter = window.scrollY + (viewportHeight / 2);

        // 각 picture 요소의 위치 확인
        let closestPicture = null;
        let closestDistance = Infinity;

        pictures.forEach((picture, index) => {
            const rect = picture.getBoundingClientRect();
            // picture 요소의 절대 위치 계산
            const pictureTop = rect.top + window.scrollY;
            const pictureCenter = pictureTop + (rect.height / 2);
            const distance = Math.abs(viewportCenter - pictureCenter);

            if (distance < closestDistance) {
                closestDistance = distance;
                closestPicture = index;
            }
        });

        // URL의 해시값을 기준으로 현재 페이지 계산
        if (closestPicture !== null) {
            const totalValue = parseInt(window.location.hash.slice(1)) + closestPicture;
            if (totalValue !== parseInt(pageSelect.value)) {
                pageSelect.value = totalValue;
                console.log(`Hash: ${window.location.hash}, Index: ${closestPicture}, Total: ${totalValue}`);
            }
        }
    }

    function handleScrollWheel(e) {
        const container = document.querySelector('#comicImages');
        if (!container) return;

        if (container.scrollTop === 0 && e.deltaY < 0) {
            const currentTime = Date.now();
            if (currentTime - lastScrollTime < 500) {
                upScrollCount++;
                if (upScrollCount >= 2) {
                    const prevPanel = document.querySelector('#prevPanel');
                    if (prevPanel) prevPanel.click();
                    upScrollCount = 0;
                }
            } else {
                upScrollCount = 1;
            }
            lastScrollTime = currentTime;
        } else {
            upScrollCount = 0;
        }
    }

    function initScrollListener() {
        const container = document.querySelector('#comicImages');
        if (!container) {
            setTimeout(initScrollListener, 100);
            return;
        }

        let scrollTimeout;
        container.addEventListener('scroll', () => {
            if (scrollTimeout) return;
            scrollTimeout = setTimeout(() => {
                checkScrollAndLoad();
                updateCurrentPage();
                scrollTimeout = null;
            }, 200);
        });

        container.addEventListener('wheel', handleScrollWheel);
        container.style.cssText += `padding: 0 ${paddingSize}vw !important;`;

        document.querySelector('#single-page-select').value = startPage;

        if (initialLoad) {
            loadNextImage();
            initialLoad = false;
        }

        checkScrollAndLoad();
        updateCurrentPage();
    }

    function getMaxPage() {
        const options = document.querySelectorAll('#single-page-select option');
        let maxPage = 0;
        options.forEach(option => {
            const value = parseInt(option.value);
            if (value > maxPage) maxPage = value;
        });
        return maxPage;
    }

    async function loadNextImage() {
        if (isLoading) {
            loadQueue++;
            return;
        }

        const maxPage = getMaxPage();
        if (currentPage >= maxPage) {
            loadQueue = 0;
            return;
        }

        isLoading = true;

        try {
            currentPage++;
            const iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            document.body.appendChild(iframe);
            iframe.src = `${baseUrl}#${currentPage}`;

            await new Promise(resolve => iframe.onload = resolve);
            const imgElement = await waitForElement(iframe, '#comicImages > picture > img');

            if (!imgElement?.src) throw new Error('Image not found');

            const pictureElement = document.createElement('picture');
            const newImage = document.createElement('img');
            newImage.src = imgElement.src;
            newImage.style.cssText = 'width: 100% !important; display: block !important;';

            await new Promise((resolve, reject) => {
                newImage.onload = resolve;
                newImage.onerror = reject;
            });

            pictureElement.appendChild(newImage);
            const container = document.querySelector('#comicImages');
            if (!container) throw new Error('Container not found');

            container.appendChild(pictureElement);
            iframe.remove();

            if (loadQueue > 0) {
                loadQueue--;
                loadNextImage();
            }
            checkScrollAndLoad();
            updateCurrentPage();
        } catch (error) {
            currentPage--;
            loadQueue = 0;
        } finally {
            isLoading = false;
        }
    }

    function checkScrollAndLoad() {
        const container = document.querySelector('#comicImages');
        if (!container) return;
        const scrollPosition = container.scrollTop + container.clientHeight;
        const remainingHeight = container.scrollHeight - scrollPosition;
        if (remainingHeight < container.clientHeight * 1.5) loadNextImage();
    }

    function waitForElement(iframe, selector, timeout = 5000) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();
            const check = () => {
                const element = iframe.contentDocument.querySelector(selector);
                if (element) return resolve(element);
                if (Date.now() - startTime > timeout) return reject(new Error(`Timeout`));
                setTimeout(check, 100);
            };
            check();
        });
    }

    createPaddingControl();
    initScrollListener();
})();