// ==UserScript==
// @name 悬停预览
// @version 3.1
// @description 提升网页浏览效率,一键悬停即现浏览器原生小窗预览,实现200毫秒快速预读,小窗智能复用,资源利用最大化。体验高效浏览,从这里开始。
// @author hiisme
// @match *://*/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @namespace https://greasyfork.org/users/217852
// ==/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 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), 200);
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 = debounce(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();
};
// 注册事件监听器
document.addEventListener('mouseover', handleMouseOver, true);
document.addEventListener('mouseout', handleMouseOut, true);
window.addEventListener('focus', handleWindowFocus);
document.addEventListener('scroll', handleDocumentScrollOrClick, true);
document.addEventListener('click', handleDocumentScrollOrClick, true);
// 页面卸载时清理
window.addEventListener('beforeunload', cleanup);
})();