您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Manual blur anywhere. Client-side.
// ==UserScript== // @name Universal Manual Blur // @namespace https://greasyfork.org/en/users/1451802 // @version 1.0 // @icon https://www.svgrepo.com/show/416283/blur-drop-water.svg // @author NormalRandomPeople (https://github.com/NormalRandomPeople) // @description Manual blur anywhere. Client-side. // @match *://*/* // @grant GM_addStyle // @run-at document-idle // @compatible chrome // @compatible firefox // @compatible opera // @compatible edge // @compatible brave // @license MIT // ==/UserScript== (function () { 'use strict'; const DEFAULT_SLIDER_VALUE = 40; const REGION_BG_MIN_ALPHA = 0.02; const UI_ZINDEX = 2147483647; const HANDLE_SIZE = 12; const css = ` .umb-toolbar { position: fixed; right: 12px; bottom: 12px; z-index: ${UI_ZINDEX}; background: rgba(16,16,16,0.88); color: #fff; border-radius: 10px; padding: 8px; font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; display:flex; gap:8px; align-items:center; box-shadow: 0 8px 24px rgba(0,0,0,0.5); backdrop-filter: blur(6px); transition: box-shadow .18s ease, transform .12s linear, background .18s ease; } .umb-toolbar .umb-toggle-edit { transition: box-shadow .18s ease, transform .12s linear; } .umb-toolbar.umb-editing .umb-toggle-edit { box-shadow: 0 4px 18px rgba(255,60,60,0.28), inset 0 0 6px rgba(255,60,60,0.06); transform: translateY(-1px); border-radius: 6px; } .umb-toolbar button, .umb-toolbar select { background:#222; color:#fff; border: none; padding:6px 8px; border-radius:6px; cursor:pointer; } .umb-toolbar input[type=range] { width:140px; } .umb-help { font-size:12px; color:#ddd; margin-left:8px; } .umb-toolbar.umb-collapsed { width:44px; height:44px; padding:6px; gap:0; justify-content:center; } .umb-toolbar.umb-collapsed > *:not(.umb-toggle-collapse) { display:none; } .umb-toggle-collapse { font-weight:700; padding:6px 8px; } .umb-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: ${UI_ZINDEX - 1}; } .umb-region { position: absolute; box-sizing: border-box; background: rgba(255,255,255,${REGION_BG_MIN_ALPHA}); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); border: 2px dashed rgba(255,255,255,0.6); pointer-events: none; overflow: visible; } .umb-region.ellipse { border-radius: 50%; } .umb-region.editing { border-style: solid; box-shadow: 0 6px 20px rgba(0,0,0,0.6); } .umb-handle { position: absolute; width: ${HANDLE_SIZE}px; height: ${HANDLE_SIZE}px; background: #fff; border-radius: 3px; border: 1px solid #333; box-shadow: 0 2px 6px rgba(0,0,0,0.4); transform: translate(-50%,-50%); cursor: nwse-resize; z-index: ${UI_ZINDEX + 1}; } .umb-handle.hidden { display: none; } .umb-bubble { position: fixed; z-index: ${UI_ZINDEX + 2}; background: rgba(0,0,0,0.88); color: #fff; padding:8px 10px; border-radius:8px; font-size:13px; pointer-events: auto; max-width: 320px; box-shadow: 0 10px 30px rgba(0,0,0,0.6); } .umb-bubble small { display:block; color:#bbb; margin-top:6px; font-size:12px; } `; try { GM_addStyle(css); } catch (e) { const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s); } function mapSliderToBlurAndAlpha(sliderValue) { const v = Math.max(0, Math.min(100, Number(sliderValue) || 0)); const blurPx = Math.round(Math.pow(v / 100, 1.1) * 90); const alpha = Math.min(0.65, Math.max(REGION_BG_MIN_ALPHA, 0.02 + blurPx / 180)); return { blurPx, alpha }; } const toolbar = document.createElement('div'); toolbar.className = 'umb-toolbar'; toolbar.innerHTML = ` <button class="umb-toggle-collapse" id="umb-collapse-btn">⏵</button> <button class="umb-toggle-edit" id="umb-toggle-edit">Enter edit</button> <button id="umb-new-rect">New rectangle</button> <button id="umb-new-ellipse">New ellipse</button> <label class="umb-tag">Blur: <input id="umb-global-blur" type="range" min="0" max="100" value="${DEFAULT_SLIDER_VALUE}" /></label> <button id="umb-clear-all">Clear all</button> <button id="umb-help-btn">Help</button> `; document.body.appendChild(toolbar); const btnEdit = toolbar.querySelector('#umb-toggle-edit'); const btnNewRect = toolbar.querySelector('#umb-new-rect'); const btnNewEllipse = toolbar.querySelector('#umb-new-ellipse'); const inputGlobalBlur = toolbar.querySelector('#umb-global-blur'); const btnClear = toolbar.querySelector('#umb-clear-all'); const btnHelp = toolbar.querySelector('#umb-help-btn'); const btnCollapse = toolbar.querySelector('#umb-collapse-btn'); const overlay = document.createElement('div'); overlay.className = 'umb-overlay'; overlay.style.top = '0px'; overlay.style.left = '0px'; document.body.appendChild(overlay); function updateOverlaySize() { overlay.style.width = Math.max(document.documentElement.scrollWidth, window.innerWidth) + 'px'; overlay.style.height = Math.max(document.documentElement.scrollHeight, window.innerHeight) + 'px'; } window.addEventListener('resize', updateOverlaySize); window.addEventListener('scroll', updateOverlaySize); const mo = new MutationObserver(updateOverlaySize); mo.observe(document.documentElement, { childList: true, subtree: true, attributes: true }); updateOverlaySize(); let regions = []; let editing = false; let selectedRegion = null; let isPointerDown = false; let drawMode = null; let drawStart = null; let perRegionBubble = null; let helpBubble = null; function createRegion({ left = 100, top = 100, width = 240, height = 120, sliderValue = DEFAULT_SLIDER_VALUE, shape = 'rect' } = {}) { const el = document.createElement('div'); el.className = 'umb-region' + (shape === 'ellipse' ? ' ellipse' : ''); el.style.left = left + 'px'; el.style.top = top + 'px'; el.style.width = width + 'px'; el.style.height = height + 'px'; el.style.pointerEvents = editing ? 'auto' : 'none'; el.style.zIndex = UI_ZINDEX - 0; overlay.appendChild(el); const region = { el, left, top, width, height, sliderValue, shape, handles: null }; applyMappedStyleToRegion(region); el.addEventListener('pointerdown', regionPointerDown); el.addEventListener('dblclick', () => { region.shape = region.shape === 'rect' ? 'ellipse' : 'rect'; el.classList.toggle('ellipse', region.shape === 'ellipse'); applyMappedStyleToRegion(region); }); regions.push(region); return region; } function applyMappedStyleToRegion(region) { const { blurPx, alpha } = mapSliderToBlurAndAlpha(region.sliderValue); region.el.style.left = region.left + 'px'; region.el.style.top = region.top + 'px'; region.el.style.width = Math.max(2, region.width) + 'px'; region.el.style.height = Math.max(2, region.height) + 'px'; region.el.style.background = `rgba(255,255,255,${alpha})`; if (blurPx === 0) { region.el.style.backdropFilter = 'none'; region.el.style.webkitBackdropFilter = 'none'; } else { region.el.style.backdropFilter = `blur(${blurPx}px)`; region.el.style.webkitBackdropFilter = `blur(${blurPx}px)`; } region.el.classList.toggle('ellipse', region.shape === 'ellipse'); } function setGlobalBlurToRegions(sliderValue) { regions.forEach(r => { r.sliderValue = sliderValue; applyMappedStyleToRegion(r); }); } function selectRegion(region) { if (selectedRegion && selectedRegion !== region) deselectRegion(selectedRegion); selectedRegion = region; if (!region) return; region.el.classList.add('editing'); addHandles(region); showPerRegionBubble(region); } function deselectRegion(region) { if (!region) return; region.el.classList.remove('editing'); removeHandles(region); hidePerRegionBubble(); if (selectedRegion === region) selectedRegion = null; } function addHandles(region) { removeHandles(region); const pos = ['nw','n','ne','e','se','s','sw','w']; region.handles = {}; pos.forEach(p => { const h = document.createElement('div'); h.className = 'umb-handle'; h.dataset.pos = p; h.style.pointerEvents = editing ? 'auto' : 'none'; overlay.appendChild(h); region.handles[p] = h; h.addEventListener('pointerdown', handlePointerDown); }); positionHandles(region); } function removeHandles(region) { if (!region || !region.handles) return; for (const k in region.handles) { const h = region.handles[k]; if (h && h.parentNode) h.parentNode.removeChild(h); } region.handles = null; } function positionHandles(region) { if (!region.handles) return; const x = region.left, y = region.top, w = region.width, h = region.height; const centers = { nw: [x, y], n: [x + w/2, y], ne: [x + w, y], e: [x + w, y + h/2], se: [x + w, y + h], s: [x + w/2, y + h], sw: [x, y + h], w: [x, y + h/2] }; for (const p in centers) { const [cx, cy] = centers[p]; const el = region.handles[p]; el.style.left = (cx - window.scrollX) + 'px'; el.style.top = (cy - window.scrollY) + 'px'; el.style.display = editing ? '' : 'none'; el.style.pointerEvents = editing ? 'auto' : 'none'; el.style.position = 'fixed'; } } let activeAction = null; overlay.addEventListener('pointerdown', (ev) => { if (!editing) return; if (ev.target !== overlay) return; if (!drawMode) return; isPointerDown = true; const docX = ev.pageX, docY = ev.pageY; drawStart = { x: docX, y: docY }; const region = createRegion({ left: docX, top: docY, width: 2, height: 2, sliderValue: Number(inputGlobalBlur.value), shape: drawMode === 'ellipse' ? 'ellipse' : 'rect' }); activeAction = { type: 'draw', region, startX: docX, startY: docY }; selectRegion(region); ev.preventDefault(); }); function regionPointerDown(ev) { if (!editing) return; ev.stopPropagation(); isPointerDown = true; const el = ev.currentTarget; const region = regions.find(r => r.el === el); if (!region) return; selectRegion(region); const docX = ev.pageX, docY = ev.pageY; activeAction = { type: 'move', region, startX: docX, startY: docY, origLeft: region.left, origTop: region.top }; el.setPointerCapture(ev.pointerId); ev.preventDefault(); } function handlePointerDown(ev) { if (!editing) return; ev.stopPropagation(); isPointerDown = true; const handle = ev.currentTarget; const region = regions.find(r => r.handles && Object.values(r.handles).includes(handle)); if (!region) return; selectRegion(region); activeAction = { type: 'resize', region, handlePos: handle.dataset.pos, startX: ev.pageX, startY: ev.pageY, orig: { left: region.left, top: region.top, width: region.width, height: region.height } }; handle.setPointerCapture(ev.pointerId); ev.preventDefault(); } window.addEventListener('pointermove', (ev) => { if (!editing || !isPointerDown || !activeAction) return; const a = activeAction; const dx = ev.pageX - a.startX, dy = ev.pageY - a.startY; if (a.type === 'draw') { const left = Math.min(a.startX, ev.pageX); const top = Math.min(a.startY, ev.pageY); const w = Math.max(2, Math.abs(ev.pageX - a.startX)); const h = Math.max(2, Math.abs(ev.pageY - a.startY)); a.region.left = left; a.region.top = top; a.region.width = w; a.region.height = h; applyMappedStyleToRegion(a.region); positionHandles(a.region); } else if (a.type === 'move') { a.region.left = a.origLeft + dx; a.region.top = a.origTop + dy; applyMappedStyleToRegion(a.region); positionHandles(a.region); } else if (a.type === 'resize') { const orig = a.orig; let { left, top, width, height } = orig; const minSize = 6; const hp = a.handlePos; if (hp.includes('n')) { const newTop = top + dy; const newHeight = height - dy; if (newHeight > minSize) { top = newTop; height = newHeight; } } if (hp.includes('s')) { const newHeight = height + dy; if (newHeight > minSize) { height = newHeight; } } if (hp.includes('w')) { const newLeft = left + dx; const newWidth = width - dx; if (newWidth > minSize) { left = newLeft; width = newWidth; } } if (hp.includes('e')) { const newWidth = width + dx; if (newWidth > minSize) { width = newWidth; } } a.region.left = left; a.region.top = top; a.region.width = width; a.region.height = height; applyMappedStyleToRegion(a.region); positionHandles(a.region); } ev.preventDefault(); }); window.addEventListener('pointerup', (ev) => { if (!editing) return; if (!isPointerDown) return; isPointerDown = false; if (activeAction && activeAction.type === 'draw') { const r = activeAction.region; if (r.width < 6 || r.height < 6) removeRegion(r); else { applyMappedStyleToRegion(r); positionHandles(r); positionPerRegionBubble(r); } } else if (activeAction && activeAction.type === 'move') { if (activeAction.region) positionPerRegionBubble(activeAction.region); } else if (activeAction && activeAction.type === 'resize') { if (activeAction.region) positionPerRegionBubble(activeAction.region); } if (activeAction && activeAction.region && activeAction.region.el) { try { activeAction.region.el.releasePointerCapture(ev.pointerId); } catch (e) {} } activeAction = null; }); function removeRegion(region) { if (!region) return; if (region.el && region.el.parentNode) region.el.parentNode.removeChild(region.el); removeHandles(region); if (selectedRegion === region) selectedRegion = null; regions = regions.filter(r => r !== region); hidePerRegionBubble(); } window.addEventListener('keydown', (ev) => { if (ev.key === 'Escape') { if (editing) toggleEdit(false); } if ((ev.key === 'Delete' || ev.key === 'Backspace') && selectedRegion) { removeRegion(selectedRegion); } if ((ev.key === '+' || ev.key === '=') && selectedRegion) { selectedRegion.sliderValue = Math.min(100, (selectedRegion.sliderValue || DEFAULT_SLIDER_VALUE) + 5); applyMappedStyleToRegion(selectedRegion); updatePerRegionBubbleSlider(selectedRegion); } if ((ev.key === '-' || ev.key === '_') && selectedRegion) { selectedRegion.sliderValue = Math.max(0, (selectedRegion.sliderValue || DEFAULT_SLIDER_VALUE) - 5); applyMappedStyleToRegion(selectedRegion); updatePerRegionBubbleSlider(selectedRegion); } }); function toggleEdit(state = !editing) { editing = state; overlay.style.pointerEvents = editing ? 'auto' : 'none'; regions.forEach(r => { r.el.style.pointerEvents = editing ? 'auto' : 'none'; if (r.handles) { for (const k in r.handles) { r.handles[k].style.display = editing ? '' : 'none'; r.handles[k].style.pointerEvents = editing ? 'auto' : 'none'; } } }); btnEdit.textContent = editing ? 'Exit edit' : 'Enter edit'; if (editing) { toolbar.classList.add('umb-editing'); btnCollapse.title = 'Collapse toolbar (you are in edit mode)'; } else { toolbar.classList.remove('umb-editing'); btnCollapse.title = 'Collapse toolbar'; } if (!editing && selectedRegion) deselectRegion(selectedRegion); if (helpBubble) positionHelpBubble(); } btnEdit.addEventListener('click', () => toggleEdit(!editing)); btnNewRect.addEventListener('click', () => { const left = window.scrollX + (window.innerWidth - 320) / 2; const top = window.scrollY + (window.innerHeight - 160) / 2; const r = createRegion({ left, top, width: 320, height: 160, sliderValue: Number(inputGlobalBlur.value), shape: 'rect' }); applyMappedStyleToRegion(r); positionHandles(r); selectRegion(r); if (!editing) toggleEdit(true); }); btnNewEllipse.addEventListener('click', () => { const left = window.scrollX + (window.innerWidth - 240) / 2; const top = window.scrollY + (window.innerHeight - 240) / 2; const r = createRegion({ left, top, width: 240, height: 240, sliderValue: Number(inputGlobalBlur.value), shape: 'ellipse' }); applyMappedStyleToRegion(r); positionHandles(r); selectRegion(r); if (!editing) toggleEdit(true); }); inputGlobalBlur.addEventListener('input', () => { const v = Number(inputGlobalBlur.value); setGlobalBlurToRegions(v); }); btnClear.addEventListener('click', () => { if (!confirm('Clear all blur regions?')) return; for (const r of [...regions]) removeRegion(r); }); function showPerRegionBubble(region) { hidePerRegionBubble(); perRegionBubble = document.createElement('div'); perRegionBubble.className = 'umb-bubble'; perRegionBubble.innerHTML = ` <div><strong>Region</strong></div> <div style="margin-top:8px;">Blur: <input id="umb-region-blur" type="range" min="0" max="100" value="${region.sliderValue}" /></div> <small>Double-click shape to toggle rect/ellipse • Drag to move • Handles to resize</small> `; document.body.appendChild(perRegionBubble); const slider = perRegionBubble.querySelector('#umb-region-blur'); slider.addEventListener('input', () => { region.sliderValue = Number(slider.value); applyMappedStyleToRegion(region); }); function positionBubble() { if (!perRegionBubble || !region) return; const vx = region.left - window.scrollX; const vy = region.top - window.scrollY; const bubbleW = perRegionBubble.offsetWidth || 220; const bubbleH = perRegionBubble.offsetHeight || 80; let left = vx + region.width + 12; let top = vy; if (left + bubbleW > window.innerWidth - 12) { left = vx - bubbleW - 12; } if (left < 12) left = 12; if (top + bubbleH > window.innerHeight - 12) top = Math.max(12, window.innerHeight - bubbleH - 12); perRegionBubble.style.left = left + 'px'; perRegionBubble.style.top = top + 'px'; } positionBubble(); window.addEventListener('scroll', positionBubble); window.addEventListener('resize', positionBubble); perRegionBubble._positionHandler = positionBubble; } function updatePerRegionBubbleSlider(region) { if (!perRegionBubble) return; const slider = perRegionBubble.querySelector('#umb-region-blur'); if (slider) slider.value = region.sliderValue; } function hidePerRegionBubble() { if (!perRegionBubble) return; window.removeEventListener('scroll', perRegionBubble._positionHandler); window.removeEventListener('resize', perRegionBubble._positionHandler); perRegionBubble.remove(); perRegionBubble = null; } function positionPerRegionBubble(region) { if (!perRegionBubble || !region) return; const vx = region.left - window.scrollX; const vy = region.top - window.scrollY; const bubbleW = perRegionBubble.offsetWidth || 220; const bubbleH = perRegionBubble.offsetHeight || 80; let left = vx + region.width + 12; let top = vy; if (left + bubbleW > window.innerWidth - 12) { left = vx - bubbleW - 12; } if (left < 12) left = 12; if (top + bubbleH > window.innerHeight - 12) top = Math.max(12, window.innerHeight - bubbleH - 12); perRegionBubble.style.left = left + 'px'; perRegionBubble.style.top = top + 'px'; } btnCollapse.addEventListener('click', () => { const collapsed = toolbar.classList.toggle('umb-collapsed'); btnCollapse.textContent = collapsed ? '⏴' : '⏵'; if (collapsed && helpBubble) hideHelpBubble(); if (!collapsed && helpBubble) positionHelpBubble(); }); btnHelp.addEventListener('click', () => { if (helpBubble) { hideHelpBubble(); return; } helpBubble = document.createElement('div'); helpBubble.className = 'umb-bubble'; helpBubble.innerHTML = ` <strong>Universal Manual Blur - Help</strong> <div style="margin-top:6px; color:#ddd; font-size:13px;"> • Enter edit to create and edit blur regions (New rectangle / New ellipse).<br/> • Drag on empty page (in edit + after New) to draw a region.<br/> • Drag region to move; use handles to resize; double-click to toggle rect/ellipse.<br/> • Use the global slider or per-region slider (visible when a region is selected).<br/> • Exit edit to interact with the page; regions remain but are non-interactive.<br/> • Right-click the toolbar to copy a JSON snapshot of regions.<br/> • Ctrl+Shift+e to paste/import a snapshot<br/> </div> `; document.body.appendChild(helpBubble); positionHelpBubble(); window.addEventListener('scroll', positionHelpBubble); window.addEventListener('resize', positionHelpBubble); }); function positionHelpBubble() { if (!helpBubble) return; const rect = toolbar.getBoundingClientRect(); const bw = helpBubble.offsetWidth || 320; let left = Math.max(8, rect.left + rect.width/2 - bw/2); let top = rect.top - helpBubble.offsetHeight - 10; if (toolbar.classList.contains('umb-collapsed') || top < 8) { top = rect.bottom + 10; } helpBubble.style.left = left + 'px'; helpBubble.style.top = top + 'px'; } function hideHelpBubble() { if (!helpBubble) return; window.removeEventListener('scroll', positionHelpBubble); window.removeEventListener('resize', positionHelpBubble); helpBubble.remove(); helpBubble = null; } function updateAllHandles() { regions.forEach(r => positionHandles(r)); if (perRegionBubble && selectedRegion) perRegionBubble._positionHandler && perRegionBubble._positionHandler(); } window.addEventListener('scroll', updateAllHandles); window.addEventListener('resize', updateAllHandles); toolbar.addEventListener('contextmenu', (ev) => { ev.preventDefault(); const exportData = regions.map(r => ({ left: r.left, top: r.top, width: r.width, height: r.height, sliderValue: r.sliderValue, shape: r.shape })); prompt('Copy JSON snapshot (paste with Ctrl+Shift+e):', JSON.stringify(exportData)); }); window.addEventListener('keydown', (ev) => { if (ev.ctrlKey && ev.shiftKey && ev.key.toLowerCase() === 'e') { const raw = prompt('Paste JSON snapshot to import blur regions:'); if (!raw) return; try { const arr = JSON.parse(raw); if (!Array.isArray(arr)) throw new Error('Not an array'); for (const r of [...regions]) removeRegion(r); for (const it of arr) { const rr = createRegion({ left: it.left, top: it.top, width: it.width, height: it.height, sliderValue: it.sliderValue || DEFAULT_SLIDER_VALUE, shape: it.shape || 'rect' }); applyMappedStyleToRegion(rr); positionHandles(rr); } alert('Imported ' + arr.length + ' regions.'); } catch (e) { alert('Import failed: ' + e.message); } } }); function supportsBackdropFilter() { const test = document.createElement('div'); test.style.webkitBackdropFilter = 'blur(2px)'; test.style.backdropFilter = 'blur(2px)'; return (test.style.webkitBackdropFilter.length > 0) || (test.style.backdropFilter.length > 0); } if (!supportsBackdropFilter()) { const warn = document.createElement('div'); warn.style.position = 'fixed'; warn.style.left = '12px'; warn.style.bottom = '12px'; warn.style.zIndex = UI_ZINDEX; warn.style.background = 'rgba(200,60,60,0.95)'; warn.style.color = '#fff'; warn.style.padding = '8px 10px'; warn.style.borderRadius = '8px'; warn.style.fontFamily = 'system-ui, Arial'; warn.style.fontSize = '13px'; warn.textContent = 'Notice: your browser may not support backdrop-filter; blur may not appear. Use Chromium, Edge or Firefox for best results.'; document.body.appendChild(warn); setTimeout(() => { try { warn.remove(); } catch (e) {} }, 12000); } window.addEventListener('beforeunload', () => { try { overlay.remove(); toolbar.remove(); } catch (e) {} }); })();