您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hold the LEFT mouse button on a video/Reel to play at 2x while holding; release to restore speed/state. Robust: works when cursor is over overlays, pointer-capture kept, and enforces 2x while holding.
当前为
// ==UserScript== // @name Instagram — 2x while long left-click (hold) — robust // @namespace https://gf.qytechs.cn/ // @version 1.2.0 // @description Hold the LEFT mouse button on a video/Reel to play at 2x while holding; release to restore speed/state. Robust: works when cursor is over overlays, pointer-capture kept, and enforces 2x while holding. // @match *://www.instagram.com/* // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; // ---------- CONFIG ---------- const CONFIG = { speed: 2.0, // playbackRate while holding holdMs: 450, // ms to hold left button before triggering showOverlay: true, // show small "2x" overlay while active overlayOffset: { x: 12, y: 12 }, // overlay offset from pointer moveCancelPx: 12, // cancel if pointer moves this many pixels before trigger enforceIntervalMs: 180, // how often to re-set playbackRate while active overlayStyle: { position: 'absolute', padding: '6px 8px', background: 'rgba(0,0,0,0.72)', color: '#fff', borderRadius: '6px', fontSize: '13px', zIndex: 2147483647, pointerEvents: 'none', transition: 'opacity .12s linear' } }; // ---------------------------- let holdTimer = null; let active = false; // whether 2x mode is active let targetVideo = null; // the video we changed let savedRate = 1; // previous playbackRate let savedPaused = true; // whether video was paused before trigger let pointerId = null; let pointerDownTarget = null; // element that received pointerdown (for pointer capture) let startX = 0, startY = 0; let suppressClick = false; // whether to suppress the actual click (if long-press triggered) let overlayEl = null; let enforcerId = null; // create overlay element (lazy) function makeOverlay() { if (!CONFIG.showOverlay) return null; const d = document.createElement('div'); d.textContent = `${CONFIG.speed}×`; Object.assign(d.style, CONFIG.overlayStyle); d.style.display = 'none'; document.body.appendChild(d); return d; } function showOverlayAt(x, y) { if (!CONFIG.showOverlay) return; if (!overlayEl) overlayEl = makeOverlay(); if (!overlayEl) return; overlayEl.style.left = (x + CONFIG.overlayOffset.x) + 'px'; overlayEl.style.top = (y + CONFIG.overlayOffset.y) + 'px'; overlayEl.style.display = 'block'; overlayEl.style.opacity = '1'; } function hideOverlay() { if (!overlayEl) return; overlayEl.style.opacity = '0'; setTimeout(() => { if (overlayEl) overlayEl.style.display = 'none'; }, 140); } // Find a <video> element from point (robust when overlays cover video) function findVideoFromPoint(x, y) { if (!document.elementsFromPoint) { // fallback: elementFromPoint + search const el = document.elementFromPoint(x, y); return findVideoFromElement(el); } const elems = document.elementsFromPoint(x, y); for (const el of elems) { const v = findVideoFromElement(el); if (v) return v; } return null; } // Find video given an element (search children / ancestors) function findVideoFromElement(el) { try { if (!el) return null; if (el.tagName && el.tagName.toLowerCase() === 'video') return el; // if element contains a video if (el.querySelector) { const inside = el.querySelector('video'); if (inside) return inside; } // if inside a video ancestor if (el.closest) { const anc = el.closest('video'); if (anc) return anc; } } catch (e) { // ignore cross-origin or DOM exceptions } return null; } function startEnforcer() { // repeatedly set playbackRate while active to counteract page scripts if (enforcerId) clearInterval(enforcerId); enforcerId = setInterval(() => { try { if (active && targetVideo) targetVideo.playbackRate = CONFIG.speed; } catch (e) {} }, CONFIG.enforceIntervalMs); } function stopEnforcer() { if (enforcerId) { clearInterval(enforcerId); enforcerId = null; } } function triggerLongPress(video, clientX, clientY) { if (!video) return; targetVideo = video; savedRate = video.playbackRate || 1; savedPaused = !!video.paused; try { video.playbackRate = CONFIG.speed; // try to play so audio/visual effect is immediate (may be blocked by autoplay policy) video.play().catch(() => { /* ignore play rejection */ }); } catch (e) {} active = true; suppressClick = true; showOverlayAt(clientX, clientY); startEnforcer(); } function restoreState() { if (!active || !targetVideo) { active = false; targetVideo = null; savedRate = 1; savedPaused = true; hideOverlay(); stopEnforcer(); return; } try { targetVideo.playbackRate = savedRate; if (savedPaused) { try { targetVideo.pause(); } catch (_) {} } } catch (e) {} active = false; targetVideo = null; savedRate = 1; savedPaused = true; hideOverlay(); stopEnforcer(); // suppressClick remains true until a click event occurs and clears it } // pointerdown handler (capture) function onPointerDown(e) { // only care primary/left button if (e.button !== 0) return; // record pointerId & start position pointerId = e.pointerId ?? null; startX = e.clientX; startY = e.clientY; pointerDownTarget = e.target; // attempt pointer capture on the element that received the down event try { if (pointerDownTarget && pointerDownTarget.setPointerCapture) pointerDownTarget.setPointerCapture(pointerId); } catch (er) {} // find video element under the pointer (robust) const v = findVideoFromPoint(e.clientX, e.clientY); if (!v) return; // prevent default to reduce interference from site handlers (but allow selection if move) e.preventDefault && e.preventDefault(); // start hold timer holdTimer = setTimeout(() => { holdTimer = null; triggerLongPress(v, e.clientX, e.clientY); }, CONFIG.holdMs); } function onPointerMove(e) { // If different pointer, ignore if (pointerId != null && e.pointerId !== pointerId) return; if (holdTimer) { const dx = e.clientX - startX; const dy = e.clientY - startY; const dist = Math.sqrt(dx * dx + dy * dy); if (dist > CONFIG.moveCancelPx) { clearTimeout(holdTimer); holdTimer = null; } } if (active && CONFIG.showOverlay) { showOverlayAt(e.clientX, e.clientY); } } function onPointerUp(e) { if (pointerId != null && e.pointerId !== pointerId) return; if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; } if (active) restoreState(); // release pointer capture if we set it try { if (pointerDownTarget && pointerDownTarget.releasePointerCapture) pointerDownTarget.releasePointerCapture(pointerId); } catch (_) {} pointerId = null; pointerDownTarget = null; } function onPointerCancel(e) { if (pointerId != null && e.pointerId !== pointerId) return; if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; } if (active) restoreState(); try { if (pointerDownTarget && pointerDownTarget.releasePointerCapture) pointerDownTarget.releasePointerCapture(pointerId); } catch (_) {} pointerId = null; pointerDownTarget = null; } // in case the element loses pointer capture unexpectedly function onLostPointerCapture(e) { // only respond if this was our pointer if (pointerId != null && e.pointerId !== pointerId) return; if (active) restoreState(); if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; } pointerId = null; pointerDownTarget = null; } // click handler to suppress the click that would otherwise toggle pause/play function onClickCapture(e) { if (suppressClick) { e.preventDefault && e.preventDefault(); e.stopImmediatePropagation && e.stopImmediatePropagation(); suppressClick = false; if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; } return; } } // Add event listeners (capture) document.addEventListener('pointerdown', onPointerDown, { passive: false, capture: true }); document.addEventListener('pointermove', onPointerMove, { passive: false, capture: true }); document.addEventListener('pointerup', onPointerUp, { passive: false, capture: true }); document.addEventListener('pointercancel', onPointerCancel, { passive: false, capture: true }); document.addEventListener('click', onClickCapture, { passive: false, capture: true }); // listen for lostpointercapture at document level (some elements will fire it) document.addEventListener('lostpointercapture', onLostPointerCapture, { capture: true }); // safety listener for mouseup in case environment lacks pointer events document.addEventListener('mouseup', (e) => { if (e.button !== 0) return; if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; } if (active) restoreState(); if (pointerDownTarget && pointerDownTarget.releasePointerCapture) { try { pointerDownTarget.releasePointerCapture(pointerId); } catch (_) {} } pointerId = null; pointerDownTarget = null; }, { passive: false, capture: true }); // Cleanup on navigation/unload window.addEventListener('beforeunload', () => { if (holdTimer) clearTimeout(holdTimer); if (active) restoreState(); if (overlayEl && overlayEl.parentNode) overlayEl.parentNode.removeChild(overlayEl); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址