您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Create a draggable, resizable overlay to cover subtitles on any website
// ==UserScript== // @name Subtitle Overlay Tool 字幕遮盖工具 // @namespace http://tampermonkey.net/ // @license MIT // @version 1.2 // @description Create a draggable, resizable overlay to cover subtitles on any website // @author Fei // @match *://*/* // @grant none // ==/UserScript== (function() { 'use strict'; let overlay = null; let isVisible = false; let isDragging = false; let isResizing = false; let currentResizeHandle = null; let dragOffset = { x: 0, y: 0 }; let resizeStartSize = { width: 0, height: 0 }; let resizeStartPos = { left: 0, top: 0 }; let resizeStartMouse = { x: 0, y: 0 }; let opacity = 1.0; // Start fully opaque // Create the overlay element function createOverlay() { overlay = document.createElement('div'); overlay.id = 'subtitle-overlay'; overlay.style.cssText = ` position: fixed; bottom: 60px; left: 50%; transform: translateX(-50%); width: 800px; height: 200px; background: linear-gradient(135deg, rgba(20, 20, 20, 0.95), rgba(10, 10, 10, 0.98)); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 16px; cursor: move; z-index: 2147483647; display: none; user-select: none; box-sizing: border-box; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 8px 16px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05); transition: all 0.3s cubic-bezier(0.4, 0.0, 0.2, 1); `; // Create resize handles for all four corners const resizeHandles = [ { position: 'top-left', cursor: 'nw-resize', style: 'top: -4px; left: -4px; border-radius: 50% 0 50% 0;' }, { position: 'top-right', cursor: 'ne-resize', style: 'top: -4px; right: -4px; border-radius: 0 50% 0 50%;' }, { position: 'bottom-left', cursor: 'sw-resize', style: 'bottom: -4px; left: -4px; border-radius: 50% 0 50% 0;' }, { position: 'bottom-right', cursor: 'se-resize', style: 'bottom: -4px; right: -4px; border-radius: 0 50% 0 50%;' } ]; resizeHandles.forEach(handle => { const resizeHandle = document.createElement('div'); resizeHandle.className = 'resize-handle'; resizeHandle.dataset.position = handle.position; resizeHandle.style.cssText = ` position: absolute; width: 16px; height: 16px; background: linear-gradient(135deg, rgba(99, 102, 241, 0.6), rgba(168, 85, 247, 0.6)); border: 2px solid rgba(255, 255, 255, 0.2); cursor: ${handle.cursor}; ${handle.style} transition: all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1); opacity: 0; transform: scale(0.8); `; overlay.appendChild(resizeHandle); }); // Show resize handles on hover overlay.addEventListener('mouseenter', () => { overlay.querySelectorAll('.resize-handle').forEach(handle => { handle.style.opacity = '1'; handle.style.transform = 'scale(1)'; }); }); overlay.addEventListener('mouseleave', () => { if (!isResizing && !isDragging) { overlay.querySelectorAll('.resize-handle').forEach(handle => { handle.style.opacity = '0'; handle.style.transform = 'scale(0.8)'; }); } }); // Create close button const closeButton = document.createElement('div'); closeButton.innerHTML = '✕'; closeButton.className = 'close-button'; closeButton.style.cssText = ` position: absolute; top: 12px; right: 12px; width: 32px; height: 32px; color: rgba(255, 255, 255, 0.7); font-size: 14px; font-weight: 500; cursor: pointer; text-align: center; line-height: 32px; border-radius: 50%; background: rgba(255, 255, 255, 0.08); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); opacity: 0.7; transition: all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1); z-index: 10; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; `; closeButton.onmouseover = () => { closeButton.style.opacity = '1'; closeButton.style.background = 'rgba(239, 68, 68, 0.2)'; closeButton.style.color = 'rgba(255, 255, 255, 0.9)'; closeButton.style.transform = 'scale(1.05)'; }; closeButton.onmouseout = () => { closeButton.style.opacity = '0.7'; closeButton.style.background = 'rgba(255, 255, 255, 0.08)'; closeButton.style.color = 'rgba(255, 255, 255, 0.7)'; closeButton.style.transform = 'scale(1)'; }; closeButton.onclick = hideOverlay; overlay.appendChild(closeButton); // Create info text const infoText = document.createElement('div'); infoText.innerHTML = ` <div class="title">Subtitle Blocker</div> <div class="instructions"> <span class="instruction-item">🔀 Drag to move</span> <span class="instruction-item">📐 Resize from corners</span> <span class="instruction-item">🎚️ Scroll to adjust opacity</span> <span class="instruction-item">⌨️ Ctrl+B to toggle</span> </div> `; infoText.className = 'info-text'; infoText.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; pointer-events: none; opacity: 0.8; `; // Add styles for title and instructions const style = document.createElement('style'); style.textContent = ` #subtitle-overlay .title { color: rgba(255, 255, 255, 0.9); font-size: 18px; font-weight: 600; margin-bottom: 12px; letter-spacing: 0.5px; } #subtitle-overlay .instructions { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; max-width: 400px; margin: 0 auto; } #subtitle-overlay .instruction-item { color: rgba(255, 255, 255, 0.6); font-size: 12px; font-weight: 400; display: flex; align-items: center; gap: 6px; padding: 4px 8px; background: rgba(255, 255, 255, 0.05); border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.05); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); } @media (max-width: 600px) { #subtitle-overlay .instructions { flex-direction: column; gap: 8px; } #subtitle-overlay .instruction-item { font-size: 10px; padding: 3px 6px; } #subtitle-overlay .title { font-size: 16px; margin-bottom: 8px; } } `; document.head.appendChild(style); overlay.appendChild(infoText); document.body.appendChild(overlay); attachEventListeners(); handleFullscreenEvents(); } function updateOpacity() { if (overlay) { // Create a more sophisticated opacity system with glassmorphism const alpha = Math.max(0.1, opacity); const gradientAlpha1 = Math.max(0.05, alpha * 0.95); const gradientAlpha2 = Math.max(0.1, alpha * 0.98); overlay.style.background = `linear-gradient(135deg, rgba(20, 20, 20, ${gradientAlpha1}), rgba(10, 10, 10, ${gradientAlpha2}))`; // Adjust border and shadow opacity based on main opacity const borderOpacity = Math.max(0.02, alpha * 0.1); const shadowOpacity = Math.max(0.1, alpha * 0.4); overlay.style.borderColor = `rgba(255, 255, 255, ${borderOpacity})`; overlay.style.boxShadow = ` 0 20px 40px rgba(0, 0, 0, ${shadowOpacity}), 0 8px 16px rgba(0, 0, 0, ${shadowOpacity * 0.5}), inset 0 1px 0 rgba(255, 255, 255, ${borderOpacity * 0.5}) `; } } function showOpacityFeedback(percentage) { // Remove existing feedback if any const existingFeedback = document.getElementById('opacity-feedback'); if (existingFeedback) { existingFeedback.remove(); } // Create opacity feedback indicator const feedback = document.createElement('div'); feedback.id = 'opacity-feedback'; feedback.innerHTML = `${percentage}%`; feedback.style.cssText = ` position: absolute; top: 12px; left: 12px; padding: 6px 12px; background: rgba(0, 0, 0, 0.8); color: white; border-radius: 8px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; font-size: 12px; font-weight: 500; z-index: 10; pointer-events: none; border: 1px solid rgba(255, 255, 255, 0.2); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); opacity: 0; transform: scale(0.8); transition: all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1); `; overlay.appendChild(feedback); // Animate in requestAnimationFrame(() => { feedback.style.opacity = '1'; feedback.style.transform = 'scale(1)'; }); // Remove after delay setTimeout(() => { if (feedback && feedback.parentNode) { feedback.style.opacity = '0'; feedback.style.transform = 'scale(0.8)'; setTimeout(() => { if (feedback && feedback.parentNode) { feedback.remove(); } }, 200); } }, 1500); } function handleFullscreenEvents() { // Listen for fullscreen changes document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('webkitfullscreenchange', handleFullscreenChange); document.addEventListener('mozfullscreenchange', handleFullscreenChange); document.addEventListener('MSFullscreenChange', handleFullscreenChange); } function handleFullscreenChange() { if (!overlay) return; // Get the fullscreen element const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; if (fullscreenElement) { // Entering fullscreen - move overlay to fullscreen element try { fullscreenElement.appendChild(overlay); // Ensure overlay maintains its properties in fullscreen overlay.style.position = 'absolute'; overlay.style.zIndex = '2147483647'; // If overlay was visible before fullscreen, keep it visible if (isVisible) { overlay.style.display = 'block'; } } catch (e) { // Fallback: keep in body but ensure maximum z-index overlay.style.position = 'fixed'; overlay.style.zIndex = '2147483647'; if (isVisible) { overlay.style.display = 'block'; } } } else { // Exiting fullscreen - move overlay back to body if (overlay.parentNode !== document.body) { document.body.appendChild(overlay); } overlay.style.position = 'fixed'; overlay.style.zIndex = '2147483647'; // Ensure overlay remains visible if it was visible before if (isVisible) { overlay.style.display = 'block'; } } } function attachEventListeners() { const resizeHandles = overlay.querySelectorAll('.resize-handle'); const closeButton = overlay.querySelector('.close-button'); // Mouse down on overlay overlay.addEventListener('mousedown', function(e) { // Check if clicking on a resize handle const resizeHandle = e.target.closest('.resize-handle'); if (resizeHandle) { startResize(e, resizeHandle.dataset.position); } else if (e.target !== closeButton && !e.target.closest('.close-button')) { startDrag(e); } }); // Mouse move document.addEventListener('mousemove', function(e) { if (isDragging) { drag(e); } else if (isResizing) { resize(e); } }); // Mouse up document.addEventListener('mouseup', function() { isDragging = false; isResizing = false; currentResizeHandle = null; if (overlay) { overlay.style.cursor = 'move'; } }); // Prevent text selection while dragging document.addEventListener('selectstart', function(e) { if (isDragging || isResizing) { e.preventDefault(); } }); // Scroll wheel opacity adjustment overlay.addEventListener('wheel', function(e) { e.preventDefault(); e.stopPropagation(); const delta = e.deltaY > 0 ? -0.05 : 0.05; opacity = Math.max(0.1, Math.min(1.0, opacity + delta)); updateOpacity(); // Visual feedback - briefly show current opacity showOpacityFeedback(Math.round(opacity * 100)); }, { passive: false }); } function startDrag(e) { isDragging = true; const rect = overlay.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; overlay.style.cursor = 'grabbing'; } function drag(e) { if (!isDragging) return; const x = e.clientX - dragOffset.x; const y = e.clientY - dragOffset.y; // Keep overlay within viewport bounds const maxX = window.innerWidth - overlay.offsetWidth; const maxY = window.innerHeight - overlay.offsetHeight; const boundedX = Math.max(0, Math.min(x, maxX)); const boundedY = Math.max(0, Math.min(y, maxY)); overlay.style.left = boundedX + 'px'; overlay.style.top = boundedY + 'px'; overlay.style.transform = 'none'; } function startResize(e, position) { e.stopPropagation(); isResizing = true; currentResizeHandle = position; const rect = overlay.getBoundingClientRect(); resizeStartSize.width = rect.width; resizeStartSize.height = rect.height; resizeStartPos.left = rect.left; resizeStartPos.top = rect.top; resizeStartMouse.x = e.clientX; resizeStartMouse.y = e.clientY; // Set appropriate cursor const cursors = { 'top-left': 'nw-resize', 'top-right': 'ne-resize', 'bottom-left': 'sw-resize', 'bottom-right': 'se-resize' }; overlay.style.cursor = cursors[position]; } function resize(e) { if (!isResizing || !currentResizeHandle) return; const deltaX = e.clientX - resizeStartMouse.x; const deltaY = e.clientY - resizeStartMouse.y; let newWidth = resizeStartSize.width; let newHeight = resizeStartSize.height; let newLeft = resizeStartPos.left; let newTop = resizeStartPos.top; // Apply resize logic based on which handle is being dragged switch (currentResizeHandle) { case 'top-left': newWidth = Math.max(200, resizeStartSize.width - deltaX); newHeight = Math.max(100, resizeStartSize.height - deltaY); newLeft = resizeStartPos.left + (resizeStartSize.width - newWidth); newTop = resizeStartPos.top + (resizeStartSize.height - newHeight); break; case 'top-right': newWidth = Math.max(200, resizeStartSize.width + deltaX); newHeight = Math.max(100, resizeStartSize.height - deltaY); newTop = resizeStartPos.top + (resizeStartSize.height - newHeight); break; case 'bottom-left': newWidth = Math.max(200, resizeStartSize.width - deltaX); newHeight = Math.max(100, resizeStartSize.height + deltaY); newLeft = resizeStartPos.left + (resizeStartSize.width - newWidth); break; case 'bottom-right': newWidth = Math.max(200, resizeStartSize.width + deltaX); newHeight = Math.max(100, resizeStartSize.height + deltaY); break; } // Apply bounds checking newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - newWidth)); newTop = Math.max(0, Math.min(newTop, window.innerHeight - newHeight)); overlay.style.width = newWidth + 'px'; overlay.style.height = newHeight + 'px'; overlay.style.left = newLeft + 'px'; overlay.style.top = newTop + 'px'; overlay.style.transform = 'none'; } function showOverlay() { if (!overlay) createOverlay(); overlay.style.display = 'block'; isVisible = true; console.log('Subtitle overlay shown'); } function hideOverlay() { if (overlay) { overlay.style.display = 'none'; overlay.style.cursor = 'move'; } isVisible = false; isDragging = false; isResizing = false; currentResizeHandle = null; // Hide resize handles when overlay is hidden if (overlay) { overlay.querySelectorAll('.resize-handle').forEach(handle => { handle.style.opacity = '0'; handle.style.transform = 'scale(0.8)'; }); } console.log('Subtitle overlay hidden'); } function toggleOverlay() { if (isVisible) { hideOverlay(); } else { showOverlay(); } } // Enhanced keyboard shortcut listener with multiple approaches function setupKeyboardListeners() { // Method 1: Standard keydown event document.addEventListener('keydown', handleKeyDown, true); // Method 2: Window-level keydown event (for fullscreen) window.addEventListener('keydown', handleKeyDown, true); // Method 3: Body-level keydown event if (document.body) { document.body.addEventListener('keydown', handleKeyDown, true); } // Method 4: Document element keydown event if (document.documentElement) { document.documentElement.addEventListener('keydown', handleKeyDown, true); } } function handleKeyDown(e) { // Check for Ctrl+B combination if (e.ctrlKey && (e.key.toLowerCase() === 'b' || e.keyCode === 66)) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); console.log('Ctrl+B detected, toggling overlay'); toggleOverlay(); return false; } } // Initialize keyboard listeners when DOM is ready function initializeKeyboardListeners() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setupKeyboardListeners); } else { setupKeyboardListeners(); } // Also setup on window load as backup window.addEventListener('load', setupKeyboardListeners); // Set up a periodic check to re-establish listeners (for dynamic content) setInterval(() => { setupKeyboardListeners(); }, 5000); } // YouTube-specific handling function handleYouTubeSpecifics() { if (window.location.hostname.includes('youtube.com')) { // YouTube often captures keyboard events, so we need to be more aggressive const ytPlayer = document.getElementById('movie_player') || document.querySelector('.html5-video-player') || document.querySelector('video'); if (ytPlayer) { ytPlayer.addEventListener('keydown', handleKeyDown, true); // Also listen on the video element itself const video = ytPlayer.querySelector('video') || ytPlayer; if (video && video.tagName === 'VIDEO') { video.addEventListener('keydown', handleKeyDown, true); } } // Listen for YouTube's navigation changes let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; setTimeout(setupKeyboardListeners, 1000); // Re-setup after navigation } }).observe(document, { subtree: true, childList: true }); } } // Initialize everything function initialize() { console.log('Subtitle Overlay Tool loaded. Press Ctrl+B to toggle overlay. Scroll over overlay to adjust opacity.'); initializeKeyboardListeners(); handleYouTubeSpecifics(); // Create overlay initially (hidden) createOverlay(); hideOverlay(); } // Start initialization if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize); } else { initialize(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址