您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在知识星球为视频添加一个美观的、不遮挡内容的布局缩放控制器。支持视频从中心突破边界放大,并自动推开上下文内容。
// ==UserScript== // @name 知识星球视频缩放 // @namespace http://tampermonkey.net/ // @version 1.1 // @description 在知识星球为视频添加一个美观的、不遮挡内容的布局缩放控制器。支持视频从中心突破边界放大,并自动推开上下文内容。 // @author Gemini、Claude、Lint // @match https://*.zsxq.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=zsxq.com // @grant GM_addStyle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- 全局变量 --- let currentTarget = null; // 当前正在控制的视频容器元素 let currentGallery = null; // 当前的 gallery 元素 // --- 样式定义 --- GM_addStyle(` /* 控制器容器的样式:固定在右下角,垂直紧凑设计 */ .video-zoom-control-container-fixed { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 12px; background-color: rgba(30, 30, 30, 0.85); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 16px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.25); z-index: 9999; transition: opacity 0.3s ease, transform 0.3s ease; transform: translateY(20px); opacity: 0; pointer-events: none; } /* 控制器显示时的样式 */ .video-zoom-control-container-fixed.visible { transform: translateY(0); opacity: 1; pointer-events: auto; } /* 标题样式 */ .controller-title { font-size: 13px; font-weight: 600; color: #ffffff; padding-bottom: 4px; border-bottom: 1px solid rgba(255, 255, 255, 0.2); width: 100%; text-align: center; } /* 控件行的样式 */ .control-row { display: flex; align-items: center; gap: 10px; } /* 控制器图标样式 */ .control-icon { display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 4px; border-radius: 6px; transition: background-color 0.2s ease; } .control-icon:hover { background-color: rgba(255, 255, 255, 0.1); } .control-icon svg { width: 18px; height: 18px; fill: #ffffff; transition: transform 0.2s ease; } .control-icon:hover svg { transform: scale(1.15); } /* 滑块样式 */ .video-zoom-control-container-fixed input[type="range"] { width: 110px; cursor: pointer; margin: 0; } /* 百分比显示样式 */ .video-zoom-control-container-fixed .zoom-percentage { font-size: 13px; font-weight: 600; color: #ffffff; min-width: 40px; text-align: center; font-family: monospace; } /* 视频缩放相关样式 (核心修改) */ .zsxq-video-resizer-target { /* 修改1: 缩放原点改为中心 */ transform-origin: center !important; /* 修改2: 为 transform 和 margin 添加平滑过渡效果 */ transition: transform 0.2s ease-in-out, margin 0.2s ease-in-out !important; } `); /** * 【核心修改】应用缩放到视频容器 * 通过 scale transform 放大视觉效果,同时用 margin 推开周围内容 * @param {HTMLElement} target - 目标元素(视频的直接父容器) * @param {number} scale - 缩放比例(0.5-3.0) */ function applyScale(target, scale) { if (!target) return; // 首次操作时,记录下元素的原始高度 if (!target.dataset.originalHeight) { target.dataset.originalHeight = target.getBoundingClientRect().height; } const originalHeight = parseFloat(target.dataset.originalHeight); // 计算因缩放产生的额外高度,并将其均分为上下外边距 let verticalMargin = 0; if (scale > 1) { // (缩放后的总高度 - 原始高度) / 2 verticalMargin = (originalHeight * (scale - 1)) / 2; } // 应用 transform 和 margin target.style.transform = `scale(${scale})`; target.style.marginTop = `${verticalMargin}px`; target.style.marginBottom = `${verticalMargin}px`; console.log(`ZSXQ Resizer: 应用缩放 ${Math.round(scale * 100)}%`); } /** * 【核心修改】重置视频尺寸 * @param {HTMLElement} target - 目标元素 */ function resetScale(target) { if (!target) return; // 恢复 transform 和 margin target.style.transform = 'scale(1)'; target.style.marginTop = '0px'; target.style.marginBottom = '0px'; console.log('ZSXQ Resizer: 重置视频尺寸'); } /** * 创建并初始化全局的缩放控制器 * @returns {object} 包含控制器各个部分的元素对象 */ function createGlobalControls() { const container = document.createElement('div'); container.className = 'video-zoom-control-container-fixed'; const title = document.createElement('div'); title.className = 'controller-title'; title.textContent = '视频尺寸缩放'; const controlRow = document.createElement('div'); controlRow.className = 'control-row'; const slider = document.createElement('input'); slider.type = 'range'; slider.min = 50; slider.max = 300; slider.value = 100; slider.step = 5; const percentageDisplay = document.createElement('span'); percentageDisplay.className = 'zoom-percentage'; percentageDisplay.textContent = '100%'; const resetIcon = document.createElement('div'); resetIcon.className = 'control-icon'; resetIcon.title = '恢复原始尺寸'; resetIcon.innerHTML = `<svg viewBox="0 0 24 24"><path d="M12 6v3l4-4l-4-4v3c-4.42 0-8 3.58-8 8c0 1.57.46 3.03 1.24 4.26L6.7 14.8c-.45-.83-.7-1.79-.7-2.8c0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8c0 3.31-2.69 6-6 6v-3l-4 4l4 4v-3c4.42 0 8-3.58 8-8c0-1.57-.46-3.03-1.24-4.26z"/></svg>`; controlRow.append(slider, percentageDisplay, resetIcon); container.append(title, controlRow); document.body.appendChild(container); // --- 事件监听 --- const updateScale = (value) => { if (currentTarget) { const scaleValue = parseInt(value); const scaleFactor = scaleValue / 100; percentageDisplay.textContent = `${scaleValue}%`; applyScale(currentTarget, scaleFactor); } }; slider.addEventListener('input', (e) => { updateScale(e.target.value); }); resetIcon.addEventListener('click', () => { if (currentTarget) { slider.value = 100; percentageDisplay.textContent = '100%'; resetScale(currentTarget); } }); return { container, slider, percentageDisplay }; } const controls = createGlobalControls(); /** * 为指定的视频容器添加缩放控制功能 * @param {HTMLElement} videoGallery - 包含 <video> 标签的 app-video-gallery 元素 */ function addZoomControls(videoGallery) { if (videoGallery.dataset.zoomControllerAdded) { return; } videoGallery.dataset.zoomControllerAdded = 'true'; const videoElement = videoGallery.querySelector('video'); if (!videoElement) { console.log('ZSXQ Resizer: 在 gallery 中未找到 video 元素'); return; } const targetToResize = videoElement.parentElement; if (!targetToResize) { console.log('ZSXQ Resizer: 未找到视频的父容器'); return; } // 【新增逻辑】在切换到新视频前,重置上一个视频的缩放状态 if (currentTarget && currentTarget !== targetToResize) { resetScale(currentTarget); } targetToResize.classList.add('zsxq-video-resizer-target'); // 设置为当前目标 currentTarget = targetToResize; currentGallery = videoGallery; // 重置控制器状态并应用到新目标 controls.slider.value = 100; controls.percentageDisplay.textContent = '100%'; resetScale(currentTarget); // 确保新目标是默认状态 // 显示控制器 controls.container.classList.add('visible'); console.log('ZSXQ Resizer: 已为视频添加缩放控制功能'); } /** * 检查并处理页面上所有尚未处理的视频 gallery */ function processVideoGalleries() { const galleries = document.querySelectorAll('app-video-gallery:not([data-zoom-controller-added])'); if (galleries.length > 0) { if (currentTarget) { controls.container.classList.remove('visible'); } // 处理最新的视频 const latestGallery = galleries[galleries.length - 1]; addZoomControls(latestGallery); } } /** * 隐藏控制器并清除目标 */ function hideControls() { if (currentTarget) { resetScale(currentTarget); } currentTarget = null; currentGallery = null; controls.container.classList.remove('visible'); } // --- 核心逻辑:使用 MutationObserver --- const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { let hasNewVideo = false; mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { const gallery = node.matches && node.matches('app-video-gallery') ? node : node.querySelector && node.querySelector('app-video-gallery'); if (gallery) { hasNewVideo = true; } } }); if (hasNewVideo) { setTimeout(processVideoGalleries, 100); } } } }); // 定期检查当前目标是否还在页面上 setInterval(() => { if (currentGallery && !document.body.contains(currentGallery)) { hideControls(); } }, 1000); // 首次加载时,先立即运行一次 setTimeout(processVideoGalleries, 200); // 开始监听整个文档的变化 observer.observe(document.body, { childList: true, subtree: true }); })();