Last active
March 27, 2026 15:58
-
-
Save Qixingchen/c6819999537ad9bd874cc3eb968621da to your computer and use it in GitHub Desktop.
放大B站视频
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Bilibili 视频放大与移动 | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.7 | |
| // @description 在Bilibili视频页面支持滚轮缩放和拖拽移动视频 | |
| // @author You | |
| // @match https://www.bilibili.com/video/* | |
| // @match https://live.bilibili.com/* | |
| // @match https://www.youtube.com/watch* | |
| // @updateURL https://gist.github.com/Qixingchen/c6819999537ad9bd874cc3eb968621da/raw/bilibili-video-zoom-pan.user.js | |
| // @downloadURL https://gist.github.com/Qixingchen/c6819999537ad9bd874cc3eb968621da/raw/bilibili-video-zoom-pan.user.js | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // 创建UI | |
| let ui = null; | |
| let video = null; | |
| let playerContainer = null; | |
| let scale = 1; | |
| let translateX = 0; | |
| let translateY = 0; | |
| let isDraggingVideo = false; | |
| let isDraggingPanel = false; | |
| let hasDragged = false; | |
| let dragStartX = 0, dragStartY = 0; | |
| let videoStartX = 0, videoStartY = 0; | |
| let videoInitialX = 0, videoInitialY = 0; | |
| let panelRight = 10, panelTop = 10; | |
| function createUI() { | |
| if (ui) return; | |
| // 样式 | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| #bilibili-zoom-ui { | |
| position: fixed; | |
| z-index: 2147483647; | |
| pointer-events: none; | |
| } | |
| #bilibili-zoom-btn { | |
| position: absolute; | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 50%; | |
| background: #00a1d6; | |
| color: white; | |
| border: none; | |
| cursor: pointer; | |
| font-size: 14px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.4); | |
| pointer-events: auto; | |
| transition: transform 0.2s; | |
| } | |
| #bilibili-zoom-btn:hover { | |
| transform: scale(1.1); | |
| background: #00b5e5; | |
| } | |
| #bilibili-zoom-panel { | |
| position: absolute; | |
| top: 44px; | |
| right: 0; | |
| background: rgba(0,0,0,0.9); | |
| padding: 10px; | |
| border-radius: 8px; | |
| color: white; | |
| display: none; | |
| flex-direction: column; | |
| gap: 6px; | |
| pointer-events: auto; | |
| min-width: 120px; | |
| } | |
| #bilibili-zoom-panel input { | |
| width: 50px; | |
| text-align: center; | |
| background: rgba(255,255,255,0.1); | |
| border: 1px solid #00a1d6; | |
| color: white; | |
| border-radius: 4px; | |
| padding: 4px; | |
| } | |
| .bilibili-zoom-row { | |
| display: flex; | |
| gap: 4px; | |
| justify-content: center; | |
| } | |
| .bilibili-zoom-row button { | |
| background: rgba(0,161,214,0.8); | |
| border: none; | |
| color: white; | |
| padding: 4px 10px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| } | |
| .bilibili-zoom-row button:hover { | |
| background: #00a1d6; | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| // UI容器 | |
| ui = document.createElement('div'); | |
| ui.id = 'bilibili-zoom-ui'; | |
| ui.innerHTML = ` | |
| <button id="bilibili-zoom-btn">🔍</button> | |
| <div id="bilibili-zoom-panel"> | |
| <div style="text-align:center;font-weight:bold;color:#00a1d6;margin-bottom:4px;">视频缩放</div> | |
| <div class="bilibili-zoom-row"> | |
| <button data-zoom="out">−</button> | |
| <button data-zoom="reset">↺</button> | |
| <button data-zoom="in">+</button> | |
| </div> | |
| <div class="bilibili-zoom-row" style="align-items:center;gap:4px;"> | |
| <input type="number" id="bilibili-zoom-val" value="100" min="50" max="500"> | |
| <span>%</span> | |
| </div> | |
| <div style="font-size:10px;color:#aaa;text-align:center;">Shift+滚轮缩放</div> | |
| </div> | |
| `; | |
| document.body.appendChild(ui); | |
| // 事件绑定 | |
| const btn = ui.querySelector('#bilibili-zoom-btn'); | |
| const panel = ui.querySelector('#bilibili-zoom-panel'); | |
| const input = ui.querySelector('#bilibili-zoom-val'); | |
| // 按钮拖拽 | |
| btn.addEventListener('mousedown', (e) => { | |
| isDraggingPanel = true; | |
| hasDragged = false; | |
| dragStartX = e.clientX; | |
| dragStartY = e.clientY; | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }); | |
| // 点击展开/收起 | |
| btn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| if (hasDragged) return; | |
| const isHidden = panel.style.display === 'none' || !panel.style.display; | |
| panel.style.display = isHidden ? 'flex' : 'none'; | |
| btn.textContent = isHidden ? '✕' : '🔍'; | |
| }); | |
| // 缩放按钮 | |
| ui.querySelector('[data-zoom="out"]').addEventListener('click', () => { | |
| scale = Math.max(0.5, scale * 0.9); | |
| updateVideo(); | |
| }); | |
| ui.querySelector('[data-zoom="reset"]').addEventListener('click', () => { | |
| scale = 1; | |
| translateX = 0; | |
| translateY = 0; | |
| updateVideo(); | |
| }); | |
| ui.querySelector('[data-zoom="in"]').addEventListener('click', () => { | |
| scale = Math.min(5, scale * 1.1); | |
| updateVideo(); | |
| }); | |
| // 输入框 | |
| input.addEventListener('change', () => { | |
| let v = parseInt(input.value); | |
| if (isNaN(v) || v < 50) v = 50; | |
| if (v > 500) v = 500; | |
| scale = v / 100; | |
| updateVideo(); | |
| }); | |
| input.addEventListener('keydown', (e) => e.stopPropagation()); | |
| } | |
| function updateVideo() { | |
| if (!video) return; | |
| video.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; | |
| video.style.cursor = scale > 1 ? 'grab' : 'default'; | |
| const input = document.getElementById('bilibili-zoom-val'); | |
| if (input) input.value = Math.round(scale * 100); | |
| } | |
| function mountUI() { | |
| if (!ui) return; | |
| // 找到当前全屏元素或 body | |
| const fullscreenElement = document.fullscreenElement || | |
| document.webkitFullscreenElement || | |
| document.mozFullScreenElement; | |
| const target = fullscreenElement || document.body; | |
| // 如果已经在正确的父元素中,不需要移动 | |
| if (ui.parentElement === target) return; | |
| target.appendChild(ui); | |
| } | |
| function updateUIPosition() { | |
| if (!ui || !playerContainer) return; | |
| // 检查是否全屏 | |
| const fullscreenElement = document.fullscreenElement || | |
| document.webkitFullscreenElement || | |
| document.mozFullScreenElement; | |
| let rect; | |
| if (fullscreenElement) { | |
| // 全屏时,使用全屏元素的尺寸 | |
| rect = fullscreenElement.getBoundingClientRect(); | |
| } else { | |
| rect = playerContainer.getBoundingClientRect(); | |
| } | |
| // 按钮位置相对于播放器或全屏容器 | |
| ui.style.left = (rect.right - panelRight - 36) + 'px'; | |
| ui.style.top = (rect.top + panelTop) + 'px'; | |
| } | |
| function init() { | |
| video = document.querySelector('video'); | |
| if (!video) { | |
| setTimeout(init, 1000); | |
| return; | |
| } | |
| // 查找播放器容器 | |
| playerContainer = video.closest('.bpx-player-video-wrap, .bilibili-player-video-wrap, [class*="video-wrap"]'); | |
| if (!playerContainer) { | |
| playerContainer = video.parentElement; | |
| } | |
| createUI(); | |
| mountUI(); | |
| updateUIPosition(); | |
| // 视频容器样式 | |
| playerContainer.style.overflow = 'hidden'; | |
| video.style.transition = 'transform 0.1s'; | |
| // 滚轮缩放 | |
| playerContainer.addEventListener('wheel', (e) => { | |
| if (e.shiftKey) { | |
| e.preventDefault(); | |
| // 1% 步进调整 | |
| const delta = e.deltaY > 0 ? -0.01 : 0.01; | |
| scale = Math.max(0.5, Math.min(5, scale + delta)); | |
| updateVideo(); | |
| } | |
| }, { passive: false }); | |
| // 视频拖拽 | |
| video.addEventListener('mousedown', (e) => { | |
| if (scale > 1) { | |
| isDraggingVideo = true; | |
| videoStartX = e.clientX; | |
| videoStartY = e.clientY; | |
| videoInitialX = translateX; | |
| videoInitialY = translateY; | |
| video.style.cursor = 'grabbing'; | |
| e.preventDefault(); | |
| } | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| if (isDraggingVideo) { | |
| translateX = videoInitialX + (e.clientX - videoStartX); | |
| translateY = videoInitialY + (e.clientY - videoStartY); | |
| updateVideo(); | |
| } | |
| if (isDraggingPanel) { | |
| const dx = e.clientX - dragStartX; | |
| const dy = e.clientY - dragStartY; | |
| if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasDragged = true; | |
| panelRight -= dx; | |
| panelTop += dy; | |
| dragStartX = e.clientX; | |
| dragStartY = e.clientY; | |
| updateUIPosition(); | |
| } | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| isDraggingVideo = false; | |
| isDraggingPanel = false; | |
| if (video) video.style.cursor = scale > 1 ? 'grab' : 'default'; | |
| }); | |
| // 双击重置 | |
| video.addEventListener('dblclick', (e) => { | |
| if (e.target.closest('#bilibili-zoom-ui')) return; | |
| scale = 1; | |
| translateX = 0; | |
| translateY = 0; | |
| updateVideo(); | |
| }); | |
| // 使用 requestAnimationFrame 持续更新位置 | |
| function loop() { | |
| updateUIPosition(); | |
| requestAnimationFrame(loop); | |
| } | |
| loop(); | |
| // 监听全屏变化,重新挂载UI到全屏元素 | |
| ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange'].forEach(event => { | |
| document.addEventListener(event, () => { | |
| setTimeout(() => { | |
| mountUI(); | |
| updateUIPosition(); | |
| }, 100); | |
| }); | |
| }); | |
| console.log('[Bilibili Zoom] 已加载'); | |
| } | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', init); | |
| } else { | |
| init(); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment