scroll up and down buttons

Add up/down navigation buttons with options for transparency

2025-12-14 기준 버전입니다. 최신 버전을 확인하세요.

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         scroll up and down buttons
// @namespace    http://tampermonkey.net/
// @version      1.7.0.1
// @description  Add up/down navigation buttons with options for transparency
// @author       the pie stealer
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license      Apache 2.0
// ==/UserScript==

(function () {
    'use strict';

    if (window.top !== window.self) return;
    if (document.body && document.body.children.length === 1) {
        const tag = document.body.children[0].tagName.toLowerCase();
        if (['img', 'video', 'audio', 'embed', 'object'].includes(tag)) return;
    }

    const STORAGE_KEY_ENABLED = 'scrollButtons_enabled';
    let isScriptEnabled = GM_getValue(STORAGE_KEY_ENABLED, true);
    let menuCmdId = null;
    let scrollAnimationId = null;
    let isTemporarilyHidden = false;

    function smoothScrollTo(targetY) {
        if (scrollAnimationId) cancelAnimationFrame(scrollAnimationId);
        const startY = window.scrollY;
        const distance = targetY - startY;
        const duration = 800; // change this to 1000 or whatever higher scroll speed value you want if this current scroll speed value is scrolling the web page too fast for you
        const startTime = performance.now();

        function step(currentTime) {
            const timeElapsed = currentTime - startTime;
            const progress = Math.min(timeElapsed / duration, 1);
            const ease = progress < 0.5 ? 2 * progress * progress : -1 + (4 - 2 * progress) * progress;
            window.scrollTo(0, startY + (distance * ease));
            if (progress < 1) scrollAnimationId = requestAnimationFrame(step);
            else scrollAnimationId = null;
        }
        scrollAnimationId = requestAnimationFrame(step);
    }

    const style = document.createElement('style');
    style.innerHTML = `
        :root {
            --sb-bg: rgba(30, 30, 30);
            --sb-hover: rgba(50, 50, 50, 0.96);
            --sb-text: #ffffff;
            --sb-glass-border: 1px solid rgba(255, 255, 255, 0.1);
            --sb-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
            --sb-blur: blur(10px);
        }

        .sb-container {
            position: fixed;
            bottom: 13px;
            right: 13px;
            display: flex;
            flex-direction: column;
            gap: 5px;
            z-index: 2147483647;
            transition: opacity 0.3s ease, transform 0.3s ease;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }

        .sb-btn {
            width: 37px;
            height: 37px;
            background: var(--sb-bg);
            backdrop-filter: var(--sb-blur);
            -webkit-backdrop-filter: var(--sb-blur);
            border: var(--sb-glass-border);
            color: var(--sb-text);
            border-radius: 50%;
            cursor: pointer;
            font-size: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: var(--sb-shadow);
            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
            outline: none;
            user-select: none;
        }

        .sb-btn:hover {
            background: var(--sb-hover);
            transform: translateY(-2px);
            box-shadow: 0 6px 16px rgba(0,0,0,0.4);
        }

        .sb-btn:active {
            transform: scale(0.95);
        }

        .sb-btn.sb-transparent { opacity: 0 !important; pointer-events: none; }
        .sb-container:hover .sb-btn.sb-transparent { opacity: 1 !important; pointer-events: auto; }

        .sb-panel {
            position: fixed;
            bottom: 30px;
            right: 90px;
            width: 240px;
            background: rgba(20, 20, 20, 0.9);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border: var(--sb-glass-border);
            border-radius: 16px;
            padding: 20px;
            color: #eee;
            display: none;
            flex-direction: column;
            gap: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
            z-index: 2147483647;
            font-size: 14px;
            transform-origin: bottom right;
            animation: sb-fade-in 0.2s ease-out;
        }

        @keyframes sb-fade-in {
            from { opacity: 0; transform: scale(0.9) translateY(10px); }
            to { opacity: 1; transform: scale(1) translateY(0); }
        }

        .sb-panel-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-weight: 600;
            font-size: 16px;
            margin-bottom: 5px;
            color: var(--sb-accent);
        }

        .sb-close {
            cursor: pointer;
            opacity: 0.6;
            font-size: 30px;
            transition: opacity 0.2s;
        }
        .sb-close:hover { opacity: 1; color: #ff5f5f; }

        .sb-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .sb-input {
            width: 50px;
            background: rgba(255,255,255,0.1);
            border: 1px solid rgba(255,255,255,0.2);
            border-radius: 6px;
            color: white;
            padding: 4px 8px;
            text-align: center;
            font-size: 13px;
        }

        .sb-input:focus {
            outline: none;
            border-color: var(--sb-accent);
            background: rgba(255,255,255,0.15);
        }

        .sb-action-btn {
            width: 100%;
            padding: 8px;
            background: var(--sb-accent);
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 500;
            margin-top: 5px;
            background-color: #4a90e2;
            transition: background 0.2s;
        }

        .sb-action-btn:hover { background: #black; }

        .sb-switch {
            position: relative;
            display: inline-block;
            width: 40px;
            height: 22px;
        }
        .sb-switch input { opacity: 0; width: 0; height: 0; }

        .sb-slider {
            position: absolute;
            cursor: pointer;
            top: 0; left: 0; right: 0; bottom: 0;
            background-color: #555;
            transition: .4s;
            border-radius: 34px;
        }

        .sb-slider:before {
            position: absolute;
            content: "";
            height: 16px;
            width: 16px;
            left: 3px;
            bottom: 3px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }

        .sb-switch input:checked + .sb-slider { background-color: #4a90e2; }
        .sb-switch input:checked + .sb-slider:before { transform: translateX(18px); }

        .sb-hr {
            border: 0;
            height: 1px;
            background: rgba(255, 255, 255, 0.1);
            margin: 5px 0;
        }
    `;

    const container = document.createElement('div');
    container.className = 'sb-container';

    const createBtn = (html, title, onClick) => {
        const btn = document.createElement('button');
        btn.className = 'sb-btn';
        btn.innerHTML = html;
        btn.title = title;
        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            onClick(e);
        });
        return btn;
    };

    const upBtn = createBtn(
        '<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>',
        'Scroll to Top',
        () => smoothScrollTo(0)
    );

    const downBtn = createBtn(
        '<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>',
        'Scroll to Bottom',
        () => smoothScrollTo(document.documentElement.scrollHeight)
    );

    const settingsBtn = createBtn(
        '<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>',
        'Settings',
        () => {
            const isFlex = settingsPanel.style.display === 'flex';
            settingsPanel.style.display = isFlex ? 'none' : 'flex';
        }
    );

    container.append(upBtn, settingsBtn, downBtn);

    const settingsPanel = document.createElement('div');
    settingsPanel.className = 'sb-panel';

    const header = document.createElement('div');
    header.className = 'sb-panel-header';
    header.innerHTML = `<span>Settings</span><span class="sb-close">&times;</span>`;
    header.querySelector('.sb-close').onclick = () => settingsPanel.style.display = 'none';
    settingsPanel.appendChild(header);

    const timerRow = document.createElement('div');
    timerRow.className = 'sb-row';
    timerRow.innerHTML = `<span>Hide duration (s)</span>`;

    const timerInput = document.createElement('input');
    timerInput.type = 'number';
    timerInput.className = 'sb-input';
    timerInput.min = '1';
    timerInput.value = '5';
    timerRow.appendChild(timerInput);
    settingsPanel.appendChild(timerRow);

    const hideBtn = document.createElement('button');
    hideBtn.className = 'sb-action-btn';
    hideBtn.innerText = 'Start Temporary Hide';
    hideBtn.onclick = () => {
        const seconds = parseInt(timerInput.value, 10) * 1000;
        if (isNaN(seconds)) return;

        removeUI();
        isTemporarilyHidden = true;

        setTimeout(() => {
            isTemporarilyHidden = false;
            addUI();
        }, seconds);
    };
    settingsPanel.appendChild(hideBtn);

    settingsPanel.appendChild(document.createElement('div')).className = 'sb-hr';

    const transpRow = document.createElement('div');
    transpRow.className = 'sb-row';
    transpRow.innerHTML = `<span>transparency toggle</span>`;

    const switchLabel = document.createElement('label');
    switchLabel.className = 'sb-switch';
    const switchInput = document.createElement('input');
    switchInput.type = 'checkbox';
    const switchSlider = document.createElement('span');
    switchSlider.className = 'sb-slider';

    switchLabel.append(switchInput, switchSlider);
    transpRow.appendChild(switchLabel);
    settingsPanel.appendChild(transpRow);

    const STORAGE_KEY_TRANSP = 'scrollBtn_transparency';
    const buttons = [upBtn, downBtn, settingsBtn];

    function applyTransparency(isTransparent) {
        switchInput.checked = isTransparent;
        buttons.forEach(btn => {
            isTransparent ? btn.classList.add('sb-transparent') : btn.classList.remove('sb-transparent');
        });
        localStorage.setItem(STORAGE_KEY_TRANSP, isTransparent ? '1' : '0');
    }

    switchInput.addEventListener('change', () => applyTransparency(switchInput.checked));

    const savedTransp = localStorage.getItem(STORAGE_KEY_TRANSP);
    if (savedTransp === '1') applyTransparency(true);

    function removeUI() {
        if (container) container.remove();
        if (settingsPanel) settingsPanel.remove();
        if (style) style.remove();
    }

    function addUI() {
        if (!isScriptEnabled) return;
        if (!document.head.contains(style)) document.head.appendChild(style);
        if (!document.body.contains(container)) document.body.appendChild(container);
        if (!document.body.contains(settingsPanel)) document.body.appendChild(settingsPanel);
    }

    function toggleScript() {
        isScriptEnabled = !isScriptEnabled;
        GM_setValue(STORAGE_KEY_ENABLED, isScriptEnabled);
        isScriptEnabled ? addUI() : removeUI();
        updateMenuCommand();
    }

    function updateMenuCommand() {
        if (menuCmdId !== null) GM_unregisterMenuCommand(menuCmdId);
        menuCmdId = GM_registerMenuCommand(
            isScriptEnabled ? "disable scroll buttons" : "enable scroll nuttons",
            toggleScript
        );
    }

    updateMenuCommand();
    if (isScriptEnabled) addUI();

    document.addEventListener('fullscreenchange', () => {
        if (!isScriptEnabled) return;
        container.style.display = document.fullscreenElement ? 'none' : 'flex';
    });

    const observer = new MutationObserver(() => {
        if (!isScriptEnabled || isTemporarilyHidden) return;
        addUI();
    });
    observer.observe(document.body, { childList: true, subtree: true });

})();