您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
适用于改善WEB端哔哩哔哩的“稍后再看”功能。添加一个指定BV号跳转按钮,可自定义按钮文字和颜色;可拖拽、记忆位置、边缘半隐藏;右键悬浮按钮打开设置面板,左键直接跳转,且视频页自动点击“跳转播放”。
// ==UserScript== // @name 给哔哩哔哩(B站)添加一个智能悬浮按钮 // @namespace RANRAN // @version 1.0.5 // @description 适用于改善WEB端哔哩哔哩的“稍后再看”功能。添加一个指定BV号跳转按钮,可自定义按钮文字和颜色;可拖拽、记忆位置、边缘半隐藏;右键悬浮按钮打开设置面板,左键直接跳转,且视频页自动点击“跳转播放”。 // @author Gemini // @match *://*.bilibili.com/* // @exclude *://player.bilibili.com/* // @exclude *://passport.bilibili.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- 统一管理所有设置 --- const SETTINGS_KEY = 'custom_bilibili_button_settings_v6'; const defaultSettings = { bv: '', text: 'BV', color: '#fb7299', autoJump: true, position: { top: '200px', left: '95%' } }; let settings = Object.assign({}, defaultSettings, GM_getValue(SETTINGS_KEY, {})); // 1. 添加样式 (包含浅色和深色两套主题) GM_addStyle(` /* 悬浮按钮 (通用) */ .bv-jumper-button { position: fixed; width: 50px; height: 50px; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 16px; font-weight: bold; cursor: pointer; z-index: 99998; box-shadow: 0 4px 12px rgba(0,0,0,0.2); user-select: none; transition: all 0.3s ease-in-out; } .bv-jumper-button:hover { opacity: 1 !important; transform: scale(1.05) !important; } /* --- 设置面板 浅色主题 (默认) --- */ .settings-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 100000; display: flex; justify-content: center; align-items: center; } .settings-panel { font-family: sans-serif; background: #fff; border-radius: 8px; padding: 20px 25px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); } .settings-panel h3 { margin-top: 0; margin-bottom: 20px; text-align: center; color: #212121; } .settings-panel .form-group { margin-bottom: 15px; } .settings-panel label { display: block; margin-bottom: 5px; font-size: 14px; color: #333; } .settings-panel input[type="text"] { width: 100%; box-sizing: border-box; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; background-color: #fff; color: #212121; } .settings-panel .color-group { display: flex; align-items: center; gap: 10px; } .settings-panel input[type="color"] { width: 50px; height: 35px; padding: 2px; cursor: pointer; border-radius: 4px; border: 1px solid #ccc; } .settings-panel .checkbox-group { display: flex; align-items: center; user-select: none; } .settings-panel .checkbox-group label { margin: 0 0 0 8px; cursor: pointer; } .settings-panel input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; } .settings-panel .button-group { text-align: right; margin-top: 20px; } .settings-panel button { padding: 8px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin-left: 10px; transition: background-color 0.2s; } .settings-panel .save-btn { background-color: #00a1d6; color: white; } .settings-panel .save-btn:hover { background-color: #00b5e5; } .settings-panel .cancel-btn { background-color: #e7e7e7; color: #333; } .settings-panel .cancel-btn:hover { background-color: #ddd; } /* --- 设置面板 深色主题 (当 .dark 类存在时应用) --- */ .dark .settings-panel { background: #2f2f2f; } .dark .settings-panel h3, .dark .settings-panel label { color: #e0e0e0; } .dark .settings-panel input[type="text"] { background-color: #222; color: #eee; border: 1px solid #555; } .dark .settings-panel .cancel-btn { background-color: #555; color: #eee; } .dark .settings-panel .cancel-btn:hover { background-color: #666; } `); // 2. 创建按钮并应用设置 const jumpButton = document.createElement('div'); jumpButton.className = 'bv-jumper-button'; document.body.appendChild(jumpButton); function getContrastYIQ(hexcolor){ hexcolor = hexcolor.replace("#", ""); const r = parseInt(hexcolor.substr(0,2),16); const g = parseInt(hexcolor.substr(2,2),16); const b = parseInt(hexcolor.substr(4,2),16); const yiq = ((r*299)+(g*587)+(b*114))/1000; return (yiq >= 128) ? '#000000' : '#FFFFFF'; } function applySettings() { jumpButton.innerText = settings.text; jumpButton.style.backgroundColor = settings.color; jumpButton.style.color = getContrastYIQ(settings.color); jumpButton.style.top = settings.position.top; jumpButton.style.left = settings.position.left; updateButtonTitle(); } // --- 拖拽、吸附与位置记忆逻辑 (无改动) --- let isDragging = false, hasMoved = false, dragStartX, dragStartY; jumpButton.addEventListener('mousedown', (e) => { if (e.button !== 0) return; isDragging = true; hasMoved = false; jumpButton.style.opacity = '1'; jumpButton.style.transform = 'scale(1.05)'; jumpButton.style.transition = 'none'; const rect = jumpButton.getBoundingClientRect(); dragStartX = e.clientX - rect.left; dragStartY = e.clientY - rect.top; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); function onMouseMove(e) { if (!isDragging) return; const newLeft = e.clientX - dragStartX; const newTop = e.clientY - dragStartY; if (!hasMoved && (Math.abs(newLeft - jumpButton.offsetLeft) > 5 || Math.abs(newTop - jumpButton.offsetTop) > 5)) { hasMoved = true; } jumpButton.style.left = `${newLeft}px`; jumpButton.style.top = `${newTop}px`; } function onMouseUp() { if (!isDragging) return; isDragging = false; jumpButton.style.transition = 'all 0.3s ease-in-out'; snapToEdge(true); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } function snapToEdge(shouldSavePosition) { const rect = jumpButton.getBoundingClientRect(); const winWidth = window.innerWidth; const winHeight = window.innerHeight; let finalLeft = rect.left; let finalTop = rect.top; if (finalTop < 0) finalTop = 0; if (finalTop > winHeight - rect.height) finalTop = winHeight - rect.height; if (rect.left + rect.width / 2 < winWidth / 2) { finalLeft = 0; jumpButton.style.transform = `translateX(-50%)`; } else { finalLeft = winWidth - rect.width; jumpButton.style.transform = `translateX(50%)`; } jumpButton.style.opacity = '0.5'; jumpButton.style.top = `${finalTop}px`; jumpButton.style.left = `${finalLeft}px`; if (shouldSavePosition) { settings.position = { top: jumpButton.style.top, left: jumpButton.style.left }; GM_setValue(SETTINGS_KEY, settings); } } // --- 核心功能:跳转、右键设置 (无改动) --- jumpButton.addEventListener('click', (e) => { if (hasMoved) { e.stopPropagation(); return; } if (settings.bv) { window.open(`https://www.bilibili.com/video/${settings.bv}`, '_blank'); } else { showSettingsPanel(); } }); jumpButton.addEventListener('contextmenu', (e) => { e.preventDefault(); showSettingsPanel(); }); function updateButtonTitle() { jumpButton.title = settings.bv ? `左键跳转到: ${settings.bv}\n右键打开设置` : '还未设置BV号\n右键打开设置'; } // --- 设置面板功能 (核心修改点) --- function showSettingsPanel() { if (document.querySelector('.settings-modal-overlay')) return; const modalHTML = ` <div class="settings-panel"> <h3>悬浮按钮设置</h3> <div class="form-group"> <label for="setting-bv">跳转BV号:</label> <input type="text" id="setting-bv" placeholder="例如:BV1fb4y1d7iA"> </div> <div class="form-group color-group"> <div> <label for="setting-text">按钮文字:</label> <input type="text" id="setting-text" maxlength="3"> </div> <div> <label for="setting-color">颜色:</label> <input type="color" id="setting-color"> </div> </div> <div class="form-group checkbox-group"> <input type="checkbox" id="setting-autojump"> <label for="setting-autojump">自动点击“跳转播放”</label> </div> <div class="button-group"> <button class="cancel-btn">取消</button> <button class="save-btn">保存</button> </div> </div> `; const modalOverlay = document.createElement('div'); modalOverlay.className = 'settings-modal-overlay'; // --- 核心修改:侦测并应用深色模式 --- if (document.body.hasAttribute('dark')) { modalOverlay.classList.add('dark'); } modalOverlay.innerHTML = modalHTML; document.body.appendChild(modalOverlay); // 填充数据 document.getElementById('setting-bv').value = settings.bv; document.getElementById('setting-text').value = settings.text; document.getElementById('setting-color').value = settings.color; document.getElementById('setting-autojump').checked = settings.autoJump; // 绑定事件 const saveBtn = modalOverlay.querySelector('.save-btn'); const cancelBtn = modalOverlay.querySelector('.cancel-btn'); saveBtn.onclick = () => { settings.bv = document.getElementById('setting-bv').value.trim(); settings.text = document.getElementById('setting-text').value.trim() || 'BV'; settings.color = document.getElementById('setting-color').value; settings.autoJump = document.getElementById('setting-autojump').checked; GM_setValue(SETTINGS_KEY, settings); applySettings(); document.body.removeChild(modalOverlay); }; cancelBtn.onclick = () => document.body.removeChild(modalOverlay); modalOverlay.onclick = (e) => { if (e.target === modalOverlay) document.body.removeChild(modalOverlay); }; } // --- 初始化和自动点击功能 --- applySettings(); setTimeout(() => snapToEdge(false), 200); if (settings.autoJump && window.location.href.includes('/video/BV')) { const clickInterval = setInterval(() => { const jumpToPlayButton = document.evaluate("//span[contains(text(), '跳转播放')]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (jumpToPlayButton) { jumpToPlayButton.click(); clearInterval(clickInterval); } }, 500); setTimeout(() => clearInterval(clickInterval), 15000); } })();