// ==UserScript== // @name 简易拖拽 // @namespace f+drag // @version 2026.06.16 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEVElEQVR4nO2bTagOURjHf68UYqkoZSmKRErdnVLqlq+SYmNjr1iIlVKUbESxtfCVciUbX90UCtlIlOjKR/mK6+u63Pu+Y3Fm3Ge+zjkzc87MyP3X033fuec8z3N+c+bMmXPm7QRBwH+tIAj+mkH9wEhoa7wnptc5YAx4XbSibG8QBHRkwzudjq5uD5AFtIVzNA8FbyEwBJwBhgv6OAVsFd9HgJm2lVMn2rIH9IAgYc9sgwJzgc8ZPgIUgPkFfGXl8tO2crIHmADMz0k6AF5Yxjym8SHtmG0bcuyXVeUCABYbEp5lEW9Hok4PeAUMoAAmfR6x8HlJk9O4qbItAFPjp1okCvHu+jmnzDvigGx0QpObFoINgH6N8yjJMfE5auQvYI+IdVbUGTU0aESUPS+Obwd+JGL3wli6HHMhmAAcMji2sb4w1rA4tt8A4KAoOxweM/VCk2VCNwEwkbWxi2GscXFslQEAGYnfd5CLEcCUxP8LTywy9NSBD4A7FetnAkiXSo8BTyhP/I1wXbUHVM1FjkeZ7Q2CIHM0XxQGXpiTaBd1F+gHlqMGxFHUre2lRUOLaBFqLrIeddudBuwDVgPXNPX2osYVszTzgAfk031v4dpFD8hT1mwwsh3aIAVngjc0gW4bkvQF4Lcmpy3GIAUBAFzPCWYaMH0ByDv7my1ilAIA6gksGXCFIZYvAE8zcunT1pBBSgIANar+Dm2nRSyfY8CtMI9PmE9EPEgFAEXlE0BpmSZC/50mATSdQNOaBNB0Ak1rEkDTCWRoGhMrP2UsCP92gbeopfhctREAqD2HshbVnwLMQU3ZD+cFahOAaCGl7PN/0qR2kTd7bdFM0Ic+ihx6UG0muALVnbrEp61n3ebsVLOZ2DDpAEdTJSx7wBD67tYj/Szehh4A6vqP8vha5mFIrstHNka8J0R2StRrCwBEHuNFAdwk3sDLGWWSS+mnw+NtAhDdHrtFAciG6TY3PiXKbia+2/NPAtjNRAN+WATJWqlpPQDdXWCt+HzPIsgC4FGVLJuQ64nQEuChY59epQMwKD4vK+BzKXA3caxboH69KjAIbijoOtpT+FIpQTcqfRdI7s1pn6xarNIAQE13JYRN9eTsVJUAQBqC1S5Mi1QZAKhX0SQE4z5ci+QEAMB34hB2+8vZqZwBADWqSwgH/OTsVE4BQBqC3csIzck5AFCvw0gIg/rijcoLAFArRBLCLXc5O5U3AJCGYHpzpAl5BQDqAUhCeFw9Z6fyDgDgKnEIz6s6dKhaAABcIQ5hyIVTB6oNAKifs0gIb/TFa1GtAACOE4dg816hT9UOANQmhITwwXWAAmoEAKj9OAnBZnHVhxoDALCN5iE0CgDSEEZ8BstQLgDb3/5U1UnUA9RA+H0GakdpI2rTdbqnuN9Qy3r5v3GsqQdE6iN/86QOa+wSkFpZU2OzbKypS0DqLnABWBd+r4N8dAmcTv2jxjPfSrXpHaFG9AfMF2OPkJ/rhQAAAABJRU5ErkJggg== // @description 拖动文字、图片、链接时新窗口打开(拖拽距离超过15px,时间在1.5秒内) // @author f+ // @match *://*/* // @grant GM_openInTab // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getValue // @grant GM_setValue // @noframes // ==/UserScript== (function () { "use strict"; console.log(`superDrag-${location.href}`); // 配置常量 const CONFIG = { STORAGE_KEY: "openInForeground", LABELS: { true: "☀ 前台打开(当前)", false: "☾ 后台打开(当前)", }, MIN_DISTANCE: 15, MAX_DURATION: 1500, ANGLE_TOLERANCE: 4, SEARCH_ENGINE: "https://www.bing.com/search?q={q}&FORM=PORE", IMAGE_EXTENSIONS: [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".avif", ".svg"], TOOLTIP: { TEXT: "超级拖拽", COLORS: "rgb(0, 105, 235)", }, }; // ───── / 工具函数 / ────────────────────────────────────────────────────── // const isImageLink = (href) => { if (!href) return false; const lower = href.toLowerCase(); return CONFIG.IMAGE_EXTENSIONS.some((ext) => lower.endsWith(ext)); }; const containsNodeDeep = (container, node) => { if (!container || !node) return false; if (container.contains?.(node)) return true; if (container.shadowRoot) return containsNodeDeep(container.shadowRoot, node); let current = node; while (current) { if (current === container) return true; current = current.host || current.parentNode; } return false; }; // ───── / 菜单 / ────────────────────────────────────────────────────── // const menu = (() => { let openInForeground = GM_getValue(CONFIG.STORAGE_KEY, false); let menuCommandId = registerMenu(); function registerMenu() { const label = CONFIG.LABELS[openInForeground]; return GM_registerMenuCommand(label, toggleMode, { autoClose: true }); } function toggleMode() { GM_unregisterMenuCommand(menuCommandId); openInForeground = !openInForeground; GM_setValue(CONFIG.STORAGE_KEY, openInForeground); menuCommandId = registerMenu(); console.log(`[SuperDrag] 已切换为:${openInForeground ? "前台打开" : "后台打开"}`); } return { get foreground() { return openInForeground; }, }; })(); // ───── / 提示 / ────────────────────────────────────────────────────── // const tooltip = (() => { let el = null; const create = () => { if (el) return; el = document.createElement("div"); el.style.cssText = ` position: fixed; z-index: 2147483647; pointer-events: none; padding: 20px 24px 8px 24px; border-radius: 6px; font-size: 18px; font-weight: 500; color: #fff; background: ${CONFIG.TOOLTIP.COLORS}; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); white-space: nowrap; opacity: 0; transform: translateX(-50%) translateY(-80%); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; box-shadow: 0 4px 12px rgba(0,0,0,0.15), 0 0 0 1px rgba(255,255,255,0.1); top: 0px; left: 50%; letter-spacing: 0.5px; `; el.textContent = CONFIG.TOOLTIP.TEXT; document.body.appendChild(el); }; return { create, show() { if (!el) return; el.style.opacity = "1"; el.style.transform = "translateX(-50%) translateY(-20%)"; }, hide() { if (!el) return; el.style.opacity = "0"; el.style.transform = "translateX(-50%) translateY(-80%)"; }, }; })(); // ───── / URL解析 / ────────────────────────────────────────────────────── // const parseUrl = (event) => { // 获取拖动的链接元素 const target = event.composedPath?.()[0] || event.target; // 从元素层级解析 if (target?.closest) { const link = target.closest("a"); const img = target.closest("img"); if (isImageLink(link?.href)) return link.href; if (img?.src) return img.src; if (link?.href) return link.href; } // 从选中文本解析 const selection = window.getSelection(); if (!selection?.toString().trim()) return null; if (!containsNodeDeep(selection.anchorNode, target)) return null; const text = selection.toString().trim(); if (/^https?:\/\//i.test(text)) return text; if (/^[a-z0-9][-a-z0-9]*\.[a-z]{2,}/i.test(text)) return `https://${text}`; return CONFIG.SEARCH_ENGINE.replace("{q}", encodeURIComponent(text)); }; // ───── / 拖拽状态 / ────────────────────────────────────────────────────── // const drag = (() => { let startX = null; let startY = null; let startTime = null; let url = null; return { // 重置状态 reset() { startY = void 0; startX = void 0; startTime = void 0; url = void 0; }, // 记录拖拽起始 start(event) { url = parseUrl(event); if (!url) return false; startY = event.clientY; startX = event.clientX; startTime = performance.now(); return true }, // 验证 evaluate(event) { if (!url) return null; const dx = Math.abs(event.clientX - startX); const dy = Math.abs(event.clientY - startY); const duration = performance.now() - startTime; const isVertical = dy > CONFIG.MIN_DISTANCE && dx * CONFIG.ANGLE_TOLERANCE < dy; const inTime = duration < CONFIG.MAX_DURATION; return isVertical && inTime ? url : null; }, }; })(); // ───── / 监听 / ────────────────────────────────────────────────────── // // 监听拖放开始事件 document.addEventListener("dragstart", function (event) { const result = drag.start(event); if (result) { tooltip.create() } }); // 监听拖放移动事件 document.addEventListener("dragover", function (event) { const url = drag.evaluate(event); if (url) { tooltip.show(); } else { tooltip.hide(); } }); // 监听拖放结束事件 document.addEventListener("dragend", function (event) { const url = drag.evaluate(event); if (url) { console.log(`超级拖拽: ${url}`); // 阻止默认的拖放行为 if (event.cancelable) event.preventDefault(); event.stopImmediatePropagation(); // 在后台打开链接 GM_openInTab(url, { active: menu.foreground }); } // 重置状态 drag.reset(); tooltip.hide(); }); })();