// ==UserScript== // @name 悬停预览 // @version 3.2 // @description 提升网页浏览效率,一键悬停即现浏览器原生小窗预览,实现200毫秒快速预读,小窗智能复用,资源利用最大化。体验高效浏览,从这里开始。 // @author hiisme // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @namespace https://greasyfork.org/users/217852 // @downloadURL none // ==/UserScript== (function () { 'use strict'; let hoverTimeoutId = null; let prefetchTimeoutId = null; let popupWindow = null; let popupWindowRect = null; let isMouseOverPopup = false; // 读取设置或设置默认值 const hoverDelay = GM_getValue('hoverDelay', 1200); const windowWidth = GM_getValue('windowWidth', 690); const windowHeight = GM_getValue('windowHeight', 400); // 注册菜单命令以更改设置 GM_registerMenuCommand('设置悬停延迟时间 (毫秒)', () => { const delay = prompt('请输入悬停延迟时间(毫秒):', hoverDelay); if (delay !== null) { const parsedDelay = parseInt(delay, 10) || 1200; GM_setValue('hoverDelay', parsedDelay); alert(`悬停延迟时间设置为 ${parsedDelay} 毫秒。`); } }); GM_registerMenuCommand('设置小窗大小', () => { const width = prompt('请输入窗口宽度:', windowWidth); const height = prompt('请输入窗口高度:', windowHeight); if (width !== null && height !== null) { const parsedWidth = parseInt(width, 10) || 690; const parsedHeight = parseInt(height, 10) || 400; GM_setValue('windowWidth', parsedWidth); GM_setValue('windowHeight', parsedHeight); alert(`窗口大小设置为 ${parsedWidth}x${parsedHeight}。`); } }); // 预取链接 const prefetchLink = async (url) => { // 清除之前的预取链接 clearTimeout(prefetchTimeoutId); // 删除之前同样链接的预取 document.querySelectorAll(`.tm-prefetch[href="${url}"]`).forEach(link => link.remove()); return new Promise((resolve) => { const linkElement = document.createElement('link'); linkElement.rel = 'prefetch'; linkElement.href = url; linkElement.className = 'tm-prefetch'; linkElement.onload = () => resolve(true); linkElement.onerror = () => resolve(false); document.head.appendChild(linkElement); }); }; // 创建或更新小窗 const createOrUpdatePopupWindow = async (url, x, y) => { if (popupWindow && !popupWindow.closed) { if (popupWindow.location.href !== url) { popupWindow.location.href = url; } popupWindow.moveTo(x, y); } else { popupWindow = window.open(url, 'popupWindow', `width=${windowWidth},height=${windowHeight},top=${y},left=${x},scrollbars=yes,resizable=yes`); } if (popupWindow) { await new Promise((resolve) => { popupWindow.addEventListener('load', () => { // 计算小窗的位置和大小 popupWindowRect = { left: popupWindow.screenX, top: popupWindow.screenY, right: popupWindow.screenX + popupWindow.innerWidth, bottom: popupWindow.screenY + popupWindow.innerHeight }; resolve(); }); }); // 确保当鼠标进入小窗时不关闭它 popupWindow.addEventListener('focus', () => { isMouseOverPopup = true; }); popupWindow.addEventListener('blur', () => { isMouseOverPopup = false; closePopupWindow(); }); } }; // 关闭小窗 const closePopupWindow = () => { if (popupWindow && !popupWindow.closed && !isMouseOverPopup) { // 延迟关闭以确认鼠标真的在外面 setTimeout(() => { if (!isMouseOverPopup) { popupWindow.close(); popupWindow = null; popupWindowRect = null; } }, 200); // 延迟时间,确保鼠标不会快速返回 } }; // 防抖动函数 const debounce = (fn, delay) => { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn.apply(this, args), delay); }; }; // 节流函数 const throttle = (fn, limit) => { let lastFn, lastRan; return (...args) => { if (!lastRan) { fn.apply(this, args); lastRan = Date.now(); } else { clearTimeout(lastFn); lastFn = setTimeout(() => { if (Date.now() - lastRan >= limit) { fn.apply(this, args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; }; // 处理鼠标悬停事件 const handleMouseOver = async (event) => { // 检查事件是否发生在主窗口中 if (window.name === 'popupWindow') return; // 防止在小窗中触发悬停行为 const linkElement = event.target.closest('a'); if (linkElement && linkElement.href) { clearTimeout(hoverTimeoutId); clearTimeout(prefetchTimeoutId); // 200ms 后预取链接 prefetchTimeoutId = setTimeout(() => prefetchLink(linkElement.href), 1000); hoverTimeoutId = setTimeout(async () => { const { clientX: x, clientY: y } = event; await createOrUpdatePopupWindow(linkElement.href, x + 10, y + 10); }, hoverDelay); } }; // 处理鼠标移出事件 const handleMouseOut = (event) => { clearTimeout(hoverTimeoutId); clearTimeout(prefetchTimeoutId); // 移除预取链接 document.querySelectorAll('.tm-prefetch').forEach(link => link.remove()); if (popupWindow && !popupWindow.closed && popupWindowRect && !isMouseOverPopup) { const { clientX: x, clientY: y } = event; const outsidePopupWindow = ( x < popupWindowRect.left || x > popupWindowRect.right || y < popupWindowRect.top || y > popupWindowRect.bottom ); if (outsidePopupWindow) { closePopupWindow(); } } }; // 处理窗口聚焦事件 const handleWindowFocus = () => { closePopupWindow(); }; // 处理滚动和点击事件 const handleDocumentScrollOrClick = throttle(closePopupWindow, 100); // 清理资源和事件监听器 const cleanup = () => { clearTimeout(hoverTimeoutId); clearTimeout(prefetchTimeoutId); document.querySelectorAll('.tm-prefetch').forEach(link => link.remove()); document.removeEventListener('mouseover', handleMouseOver, true); document.removeEventListener('mouseout', handleMouseOut, true); window.removeEventListener('focus', handleWindowFocus); document.removeEventListener('scroll', handleDocumentScrollOrClick, true); document.removeEventListener('click', handleDocumentScrollOrClick, true); closePopupWindow(); }; // 注册事件监听器 const addEventListeners = () => { document.addEventListener('mouseover', handleMouseOver, { capture: true, passive: true }); document.addEventListener('mouseout', handleMouseOut, { capture: true, passive: true }); window.addEventListener('focus', handleWindowFocus); document.addEventListener('scroll', handleDocumentScrollOrClick, { capture: true, passive: true }); document.addEventListener('click', handleDocumentScrollOrClick, true); }; // 页面卸载时清理 window.addEventListener('beforeunload', cleanup); // 初始事件监听器 addEventListeners(); })();