游戏手柄网页操控器

使用游戏手柄控制网页滚动和导航

当前为 2025-08-09 提交的版本,查看 最新版本

// ==UserScript==
// @name         游戏手柄网页操控器
// @namespace    https://github.com/ended_world
// @version      1.2
// @license      MIT
// @description  使用游戏手柄控制网页滚动和导航
// @author       ended_world
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // 配置参数
    const config = {
        scrollSpeed: 20,          // 滚动速度
        deadZone: 0.3,            // 摇杆死区阈值
        pageScrollDuration: 500,  // 翻页动画持续时间(ms)
        vibrationDuration: 30,    // 按钮振动反馈持续时间(ms)
        panelAutoCloseTime: 5000  // 面板自动关闭时间(毫秒)
    };

    // 手柄状态
    let gamepadState = {
        connected: false,
        gamepad: null,
        prevButtons: [],
        prevAxes: [],
        panelVisible: false,
        panelTimeout: null
    };

    // 初始化手柄连接
    function initGamepad() {
        window.addEventListener("gamepadconnected", (e) => {
            gamepadState.connected = true;
            gamepadState.gamepad = e.gamepad;
            gamepadState.prevButtons = new Array(e.gamepad.buttons.length).fill(false);
            gamepadState.prevAxes = new Array(e.gamepad.axes.length).fill(0);
            console.log(`手柄已连接: ${e.gamepad.id}`);

            // 自动显示控制面板
            gamepadState.panelVisible = true;
            updateStatusIndicator();
            updateInstructions();

            // 设置面板自动关闭
            if (gamepadState.panelTimeout) clearTimeout(gamepadState.panelTimeout);
            gamepadState.panelTimeout = setTimeout(() => {
                gamepadState.panelVisible = false;
                updateInstructions();
            }, config.panelAutoCloseTime);

            startGamepadLoop();
        });

        window.addEventListener("gamepaddisconnected", (e) => {
            gamepadState.connected = false;
            gamepadState.panelVisible = false; // 断开时隐藏面板
            console.log(`手柄已断开: ${e.gamepad.id}`);
            updateStatusIndicator();
            updateInstructions();

            // 清除自动关闭定时器
            if (gamepadState.panelTimeout) {
                clearTimeout(gamepadState.panelTimeout);
                gamepadState.panelTimeout = null;
            }
        });
    }

    // 开始游戏手柄轮询
    function startGamepadLoop() {
        if (!gamepadState.connected) return;

        const gamepad = navigator.getGamepads()[gamepadState.gamepad.index];
        if (!gamepad) return;

        // 处理摇杆输入
        handleJoystickInput(gamepad);

        // 处理按钮输入
        handleButtonInput(gamepad);

        // 更新前一帧状态
        gamepadState.prevButtons = [...gamepad.buttons.map(b => b.pressed)];
        gamepadState.prevAxes = [...gamepad.axes];

        requestAnimationFrame(startGamepadLoop);
    }

    // 处理摇杆输入
    function handleJoystickInput(gamepad) {
        // 左摇杆 - 垂直滚动 (axes[1])
        const leftStickY = gamepad.axes[1];
        if (Math.abs(leftStickY) > config.deadZone) {
            const scrollAmount = leftStickY * config.scrollSpeed;
            window.scrollBy(0, scrollAmount);
        }

        // 右摇杆 - 水平滚动 (axes[2])
        const rightStickX = gamepad.axes[2];
        if (Math.abs(rightStickX) > config.deadZone) {
            const scrollAmount = rightStickX * config.scrollSpeed;
            window.scrollBy(scrollAmount, 0);
        }
    }

    // 处理按钮输入
    function handleButtonInput(gamepad) {
        // 方向键上 - 向上翻页
        if (buttonPressed(gamepad, 12) && !gamepadState.prevButtons[12]) {
            scrollPage('up');
        }

        // 方向键下 - 向下翻页
        if (buttonPressed(gamepad, 13) && !gamepadState.prevButtons[13]) {
            scrollPage('down');
        }

        // A按钮 - 网页前进
        if (buttonPressed(gamepad, 0) && !gamepadState.prevButtons[0]) {
            window.history.forward();
            vibrate();
        }

        // B按钮 - 返回上一页
        if (buttonPressed(gamepad, 1) && !gamepadState.prevButtons[1]) {
            window.history.back();
            vibrate();
        }

        // X按钮 - 刷新页面
        if (buttonPressed(gamepad, 2) && !gamepadState.prevButtons[2]) {
            window.location.reload();
            vibrate();
        }

        // Y按钮 - 打开新标签页
        if (buttonPressed(gamepad, 3) && !gamepadState.prevButtons[3]) {
            window.open('', '_blank');
            vibrate();
        }
    }

    // 检查按钮是否按下
    function buttonPressed(gamepad, buttonIndex) {
        return gamepad.buttons[buttonIndex]?.pressed || false;
    }

    // 翻页滚动
    function scrollPage(direction) {
        const currentPosition = window.scrollY;
        const pageHeight = window.innerHeight;
        const targetPosition = direction === 'down' ?
            currentPosition + pageHeight :
            Math.max(0, currentPosition - pageHeight);

        // 使用平滑滚动
        window.scrollTo({
            top: targetPosition,
            behavior: 'smooth'
        });

        // 提供振动反馈
        vibrate();
    }

    // 手柄振动反馈
    function vibrate() {
        if (gamepadState.gamepad && gamepadState.gamepad.vibrationActuator) {
            gamepadState.gamepad.vibrationActuator.playEffect("dual-rumble", {
                startDelay: 0,
                duration: config.vibrationDuration,
                weakMagnitude: 0.8,
                strongMagnitude: 0.5
            });
        }
    }

    // 创建状态指示器
    function createStatusIndicator() {
        const indicator = document.createElement('div');
        indicator.id = 'gamepad-indicator';
        indicator.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 24px;
            height: 24px;
            border-radius: 50%;
            background-color: #ff4444;
            box-shadow: 0 0 10px rgba(255, 0, 0, 0.5);
            z-index: 10000;
            transition: background-color 0.3s, transform 0.3s;
            cursor: pointer;
            display: none; /* 初始不显示 */
            justify-content: center;
            align-items: center;
            font-size: 14px;
            color: white;
            font-weight: bold;
        `;
        indicator.innerHTML = '柄';
        document.body.appendChild(indicator);

        // 添加点击事件
        indicator.addEventListener('click', toggleInstructionsPanel);
    }

    // 切换控制面板显示状态
    function toggleInstructionsPanel() {
        if (!gamepadState.connected) return;

        gamepadState.panelVisible = !gamepadState.panelVisible;
        updateInstructions();

        // 清除之前的定时器
        if (gamepadState.panelTimeout) {
            clearTimeout(gamepadState.panelTimeout);
            gamepadState.panelTimeout = null;
        }

        // 如果面板显示,设置自动关闭定时器
        if (gamepadState.panelVisible) {
            gamepadState.panelTimeout = setTimeout(() => {
                gamepadState.panelVisible = false;
                updateInstructions();
            }, config.panelAutoCloseTime);
        }
    }

    // 更新状态指示器
    function updateStatusIndicator() {
        const indicator = document.getElementById('gamepad-indicator');
        if (indicator) {
            // 只有当手柄连接时才显示
            indicator.style.display = gamepadState.connected ? 'flex' : 'none';

            if (gamepadState.connected) {
                indicator.style.backgroundColor = '#44ff44';
                indicator.style.boxShadow = '0 0 15px rgba(0, 255, 0, 0.7)';
            }
        }
    }

    // 创建控制说明
    function createInstructions() {
        const panel = document.createElement('div');
        panel.id = 'gamepad-instructions';
        panel.style.cssText = `
            position: fixed;
            bottom: 60px;
            right: 20px;
            background: rgba(0, 0, 0, 0.85);
            color: white;
            padding: 20px;
            border-radius: 15px;
            font-family: "Microsoft YaHei", sans-serif;
            z-index: 9999;
            max-width: 280px;
            display: none; /* 初始不显示 */
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
            border: 1px solid #4CAF50;
            backdrop-filter: blur(5px);
            transition: opacity 0.3s, transform 0.3s;
            opacity: 0;
            transform: translateY(10px);
        `;

        panel.innerHTML = `
            <h3 style="margin:0 0 15px;color:#4CAF50;font-size:18px;border-bottom:1px solid #444;padding-bottom:10px;">手柄控制说明</h3>
            <p style="margin:0 0 12px;font-size:14px;line-height:1.6;"><strong>左摇杆</strong>: 上下滚动页面</p>
            <p style="margin:0 0 12px;font-size:14px;line-height:1.6;"><strong>右摇杆</strong>: 左右滚动页面</p>
            <p style="margin:0 0 12px;font-size:14px;line-height:1.6;"><strong>方向键上/下</strong>: 上下翻页</p>
            <p style="margin:0 0 12px;font-size:14px;line-height:1.6;"><strong>A按钮</strong>: 前进下一页</p>
            <p style="margin:0 0 12px;font-size:14px;line-height:1.6;"><strong>B按钮</strong>: 返回上一页</p>
            <p style="margin:0 0 12px;font-size:14px;line-height:1.6;"><strong>X按钮</strong>: 刷新页面</p>
            <p style="margin:0 0 5px;font-size:14px;line-height:1.6;"><strong>Y按钮</strong>: 新标签页</p>
            <div style="margin-top:15px;font-size:12px;color:#aaa;text-align:center;">面板将在5秒后自动关闭</div>
        `;

        document.body.appendChild(panel);
    }

    // 更新说明面板
    function updateInstructions() {
        const panel = document.getElementById('gamepad-instructions');
        if (panel) {
            // 只有当手柄连接且面板可见时才显示
            if (gamepadState.panelVisible && gamepadState.connected) {
                panel.style.display = 'block';
                setTimeout(() => {
                    panel.style.opacity = '1';
                    panel.style.transform = 'translateY(0)';
                }, 10);
            } else {
                panel.style.opacity = '0';
                panel.style.transform = 'translateY(10px)';
                setTimeout(() => {
                    panel.style.display = 'none';
                }, 300);
            }
        }
    }

    // 添加点击外部关闭面板的功能
    function setupDocumentClickListener() {
        document.addEventListener('click', (e) => {
            const indicator = document.getElementById('gamepad-indicator');
            const panel = document.getElementById('gamepad-instructions');

            // 如果点击的不是指示器或面板,则关闭面板
            if (panel && gamepadState.panelVisible &&
                e.target !== indicator &&
                e.target !== panel &&
                !panel.contains(e.target)) {
                gamepadState.panelVisible = false;
                updateInstructions();

                // 清除自动关闭定时器
                if (gamepadState.panelTimeout) {
                    clearTimeout(gamepadState.panelTimeout);
                    gamepadState.panelTimeout = null;
                }
            }
        });
    }

    // 初始化
    function init() {
        initGamepad();
        createStatusIndicator();
        createInstructions();
        setupDocumentClickListener();

        // 定期检查连接状态
        setInterval(() => {
            updateStatusIndicator();
        }, 1000);
    }

    // 启动脚本
    init();
})();

QingJ © 2025

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