Skip to content

Instantly share code, notes, and snippets.

@Qixingchen
Last active March 27, 2026 15:58
Show Gist options
  • Select an option

  • Save Qixingchen/c6819999537ad9bd874cc3eb968621da to your computer and use it in GitHub Desktop.

Select an option

Save Qixingchen/c6819999537ad9bd874cc3eb968621da to your computer and use it in GitHub Desktop.
放大B站视频
// ==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