您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在起点中文网使用游戏手柄控制阅读体验,支持章节切换、页面滚动等功能
// ==UserScript== // @name 起点阅读手柄控制 // @namespace https://github.com/ended_world // @version 1.1 // @license MIT // @description 在起点中文网使用游戏手柄控制阅读体验,支持章节切换、页面滚动等功能 // @author ended_world // @match *://*.qidian.com/* // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // 配置参数 const config = { scrollSpeed: 30, // 滚动速度提升1.5倍(原20*1.5=30) deadZone: 0.3, // 摇杆死区阈值 pageScrollDuration: 500, // 翻页动画持续时间(ms) vibrationDuration: 30, // 按钮振动反馈持续时间(ms) panelAutoCloseTime: 5000, // 面板自动关闭时间(毫秒) chapterSwitchThreshold: 0.7 // 章节切换阈值 }; // 手柄状态 let gamepadState = { connected: false, gamepad: null, prevButtons: [], prevAxes: [], panelVisible: false, panelTimeout: null, chapterSwitchCooldown: false // 章节切换冷却状态 }; // 初始化手柄连接 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); updateStatusIndicator(); startGamepadLoop(); }); window.addEventListener("gamepaddisconnected", (e) => { gamepadState.connected = false; gamepadState.panelVisible = false; 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[0]) const leftStickX = gamepad.axes[0]; if (Math.abs(leftStickX) > config.chapterSwitchThreshold && !gamepadState.chapterSwitchCooldown) { if (leftStickX < -config.deadZone) { navigateToPreviousChapter(); } else if (leftStickX > config.deadZone) { navigateToNextChapter(); } // 添加冷却时间防止连续触发 gamepadState.chapterSwitchCooldown = true; setTimeout(() => { gamepadState.chapterSwitchCooldown = false; }, 500); } // 右摇杆 - 水平滚动 (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(); } // R1按钮(索引5)打开控制面板 if (buttonPressed(gamepad, 5) && !gamepadState.prevButtons[5]) { toggleInstructionsPanel(true); } } // 导航到上一章 function navigateToPreviousChapter() { const prevChapterBtn = document.querySelector('.nav-btn.relative'); if (prevChapterBtn && prevChapterBtn.textContent.includes('上一章')) { prevChapterBtn.click(); vibrate(); } } // 导航到下一章 function navigateToNextChapter() { const nextChapterBtn = document.querySelector('.nav-btn:not(.relative)'); if (nextChapterBtn && nextChapterBtn.textContent.includes('下一章')) { nextChapterBtn.click(); 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: 40px; height: 40px; border-radius: 50%; background-color: #4CAF50; box-shadow: 0 0 15px rgba(0, 255, 0, 0.7); z-index: 10000; transition: all 0.3s; cursor: pointer; display: flex; justify-content: center; align-items: center; opacity: 0.8; `; indicator.innerHTML = ` <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="white" stroke-width="2"/> <path d="M12 16V12" stroke="white" stroke-width="2" stroke-linecap="round"/> <path d="M12 8H12.01" stroke="white" stroke-width="2" stroke-linecap="round"/> </svg> `; document.body.appendChild(indicator); // 添加点击事件 indicator.addEventListener('click', () => toggleInstructionsPanel(true)); } // 切换控制面板显示状态 function toggleInstructionsPanel(show) { if (!gamepadState.connected) return; gamepadState.panelVisible = show !== undefined ? show : !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'; } } // 创建控制说明 function createInstructions() { const panel = document.createElement('div'); panel.id = 'gamepad-instructions'; panel.style.cssText = ` position: fixed; bottom: 80px; right: 20px; background: rgba(0, 0, 0, 0.9); 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 30px rgba(0, 0, 0, 0.7); border: 2px solid #4CAF50; backdrop-filter: blur(10px); transition: all 0.3s ease-out; opacity: 0; transform: translateY(20px) scale(0.95); `; panel.innerHTML = ` <div style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 22px; color: #aaa; transition: color 0.2s;" onmouseover="this.style.color='white'">×</div> <h3 style="margin:0 0 15px; color:#4CAF50; font-size:20px; border-bottom:1px solid #333; padding-bottom:10px; text-align:center;"> 起点阅读手柄控制 </h3> <div style="display: grid; grid-template-columns: auto 1fr; gap: 12px; align-items: center;"> <div style="background: #333; padding: 8px; border-radius: 8px; text-align: center;"> <div>←→</div> <div style="font-size: 12px; color: #aaa;">左摇杆</div> </div> <div>切换章节</div> <div style="background: #333; padding: 8px; border-radius: 8px; text-align: center;"> <div>↑↓</div> <div style="font-size: 12px; color: #aaa;">方向键</div> </div> <div>翻页</div> <div style="background: #333; padding: 8px; border-radius: 8px; text-align: center;"> <div>A</div> <div style="font-size: 12px; color: #aaa;">按钮</div> </div> <div>前进</div> <div style="background: #333; padding: 8px; border-radius: 8px; text-align: center;"> <div>B</div> <div style="font-size: 12px; color: #aaa;">按钮</div> </div> <div>后退</div> <div style="background: #333; padding: 8px; border-radius: 8px; text-align: center;"> <div>R1</div> <div style="font-size: 12px; color: #aaa;">按钮</div> </div> <div>打开本面板</div> </div> <div style="margin-top:20px; padding-top:15px; border-top:1px solid #333; text-align:center; font-size:13px; color:#888;"> 面板将在5秒后自动关闭 </div> `; document.body.appendChild(panel); // 添加关闭按钮事件 panel.querySelector('div').addEventListener('click', () => { toggleInstructionsPanel(false); }); } // 更新说明面板 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) scale(1)'; }, 10); } else { panel.style.opacity = '0'; panel.style.transform = 'translateY(20px) scale(0.95)'; 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)) { toggleInstructionsPanel(false); } }); } // 初始化 function init() { initGamepad(); createStatusIndicator(); createInstructions(); setupDocumentClickListener(); // 定期检查连接状态 setInterval(() => { updateStatusIndicator(); }, 1000); } // 启动脚本 init(); })();