MediaWiki:Gadget-UnihanTooltips.js:修订间差异
外观
小无编辑摘要 |
小无编辑摘要 |
||
| 第1行: | 第1行: | ||
(function() { | (function() { | ||
// | /*===== 初始化检查 =====*/ | ||
// Cookie 检查(保持原始逻辑) | |||
const dontLoad = mw.util.getParamValue("UTdontload"); | const dontLoad = mw.util.getParamValue("UTdontload"); | ||
if (dontLoad && !isNaN(dontLoad)) { | if (dontLoad && !isNaN(dontLoad)) { | ||
| 第10行: | 第8行: | ||
if (mw.cookie.get("UTdontload") === "1") return; | if (mw.cookie.get("UTdontload") === "1") return; | ||
/*===== 主逻辑 =====*/ | |||
(function() { | (function() { | ||
// | // 容器和目标命名空间检查 | ||
const bodyContent = mw.util.$content || document.body; | const bodyContent = mw.util.$content || document.body; | ||
const canonicalNamespace = mw.config.get('wgCanonicalNamespace'); | const canonicalNamespace = mw.config.get('wgCanonicalNamespace'); | ||
if (!['', 'Project', 'Help'].includes(canonicalNamespace)) return; | if (!['', 'Project', 'Help'].includes(canonicalNamespace)) return; | ||
// | // 触摸设备检测(优化后的逻辑) | ||
const isTouchscreen = window.matchMedia('(hover: none), (pointer: coarse)').matches || | |||
mw.config.get('wgDisplayResolution') === 'mobile' || | |||
'ontouchstart' in document.documentElement; | |||
const | const hoverDelay = isTouchscreen ? 0 : 200; | ||
/*===== 工具提示核心逻辑 =====*/ | |||
$(".inline-unihan").each(function() { | $(".inline-unihan").each(function() { | ||
let tooltipNode = null; | let tooltipNode = null; | ||
let hideTimer = null; | let hideTimer = null; | ||
let showTimer = null; | let showTimer = null; | ||
const element = this; | |||
function | // 保存原始title并添加高亮类 | ||
if (tooltipNode | const originalTitle = this.title; | ||
this.title = ""; | |||
$(this).addClass("UHTarget"); | |||
/*-- 工具提示控制函数 --*/ | |||
function hideTooltip() { | |||
if (!tooltipNode || tooltipNode.parentNode !== bodyContent) return; | |||
clearTimeout(hideTimer); | |||
hideTimer = setTimeout(() => { | |||
$(tooltipNode).animate({ opacity: 0 }, 100, () => { | |||
tooltipNode.parentNode.removeChild(tooltipNode); | |||
}); | |||
}, isTouchscreen ? 16 : 100); | |||
} | } | ||
function | function showTooltip() { | ||
if (!tooltipNode.parentNode || tooltipNode.parentNode.nodeType === 11) { | if (!tooltipNode.parentNode || tooltipNode.parentNode.nodeType === 11) { | ||
bodyContent.appendChild(tooltipNode); | bodyContent.appendChild(tooltipNode); | ||
} | } | ||
$(tooltipNode).stop().animate({ | $(tooltipNode).stop().animate({ opacity: 1 }, 100); | ||
clearTimeout(hideTimer); | clearTimeout(hideTimer); | ||
} | } | ||
// | /*-- 工具提示创建/更新 --*/ | ||
const | function createOrUpdateTooltip() { | ||
if (!tooltipNode) { | |||
// 首次创建工具提示 | |||
tooltipNode = document.createElement("ul"); | |||
tooltipNode.className = "unihantooltip"; | |||
// 内容容器 (第一个li) | |||
const contentLi = document.createElement("li"); | |||
// 设置齿轮图标 | |||
const settingsIcon = document.createElement("div"); | |||
settingsIcon.className = "UHsettings"; | |||
settingsIcon.title = "设置"; | |||
$(settingsIcon).on("click", (e) => { | |||
e.stopPropagation(); | |||
// 这里添加设置图标的点击处理逻辑 | |||
}); | |||
// 箭头元素 (第二个li) | |||
const arrowLi = document.createElement("li"); | |||
// 组装DOM结构 | |||
contentLi.appendChild(createContentElement()); | |||
contentLi.appendChild(settingsIcon); | |||
tooltipNode.appendChild(contentLi); | |||
tooltipNode.appendChild(arrowLi); | |||
// 非触摸设备的悬停处理 | |||
if (!isTouchscreen) { | |||
$(tooltipNode).on("mouseenter", showTooltip) | |||
.on("mouseleave", hideTooltip); | |||
} | |||
} else { | |||
// 更新已有内容 | |||
const contentContainer = tooltipNode.firstChild.firstChild; | |||
tooltipNode.firstChild.replaceChild( | |||
createContentElement(), | |||
contentContainer | |||
); | |||
} | |||
// 定位并显示 | |||
positionTooltip(); | |||
showTooltip(); | |||
} | |||
/*-- 内容生成函数 --*/ | |||
function createContentElement() { | |||
const container = document.createElement("div"); | |||
originalTitle.split("\n").forEach(line => { | |||
if (line.trim()) { | |||
const p = document.createElement("p"); | |||
p.textContent = line; | |||
container.appendChild(p); | |||
} | |||
}); | |||
return container; | |||
} | |||
// | /*-- 精确定位逻辑 (关键调整) --*/ | ||
$( | function positionTooltip() { | ||
const | const $element = $(element); | ||
const elementPos = $element.offset(); | |||
const elementHeight = $element.outerHeight(); | |||
const elementWidth = $element.outerWidth(); | |||
// 临时显示以获取尺寸 | |||
tooltipNode.style.display = 'block'; | |||
const tooltipWidth = tooltipNode.offsetWidth; | |||
const contentHeight = tooltipNode.firstChild.offsetHeight; | |||
const arrowHeight = 12; // 对应CSS中的箭头高度 | |||
// 视口边界检测 | |||
const viewport = { | |||
top: $(window).scrollTop(), | |||
bottom: $(window).scrollTop() + $(window).height(), | |||
left: 0, | |||
right: $(window).width() | |||
}; | |||
/*===== 垂直定位 =====*/ | |||
// 默认上方显示 (减内容高度和箭头高度) | |||
let top = elementPos.top - contentHeight - arrowHeight; | |||
// 检查是否需要翻转 (空间不足时) | |||
const needFlip = (top < viewport.top) || | |||
(elementPos.top + contentHeight > viewport.bottom); | |||
$(tooltipNode).toggleClass("UHflipped", needFlip); | |||
// 应用翻转偏移量 (13px对应CSS的padding-top) | |||
top = needFlip | |||
? elementPos.top + elementHeight + arrowHeight - 13 | |||
: top; | |||
// 确保不超出视口 | |||
top = Math.max(viewport.top, Math.min(top, viewport.bottom - contentHeight)); | |||
/*===== 水平定位 =====*/ | |||
// 中心对齐 (7px对应CSS箭头的margin-left) | |||
let left = elementPos.left + (elementWidth - tooltipWidth) / 2; | |||
// 边界保护 (左右各留10px安全边距) | |||
left = Math.max(viewport.left + 10, | |||
Math.min(left, viewport.right - tooltipWidth - 10)); | |||
/*===== 箭头定位 =====*/ | |||
const arrow = tooltipNode.lastChild; | |||
const arrowCenterOffset = elementPos.left - left + elementWidth / 2 - 7; | |||
$(arrow).css("margin-left", arrowCenterOffset + "px"); | |||
// 应用最终位置 | |||
$(tooltipNode).css({ | |||
top: top + "px", | |||
left: left + "px", | |||
display: '' // 恢复原始display | |||
}); | |||
} | |||
/*-- 触摸设备的外部点击处理 --*/ | |||
function setupClickOutsideHandler() { | |||
const handler = function(e) { | |||
if (!tooltipNode.contains(e.target) && e.target !== element) { | |||
hideTooltip(); | |||
$(document).off("click touchstart", handler); | |||
} | |||
}; | |||
$(document).on("click touchstart", handler); | |||
} | |||
/*===== 事件绑定 =====*/ | |||
$(element).on(isTouchscreen ? 'click' : 'mouseenter mouseleave', function(e) { | |||
// 触摸设备处理 | |||
if (isTouchscreen && e.type === 'click') { | if (isTouchscreen && e.type === 'click') { | ||
e.preventDefault(); | e.preventDefault(); | ||
if (!tooltipNode || tooltipNode.parentNode !== bodyContent) { | if (!tooltipNode || tooltipNode.parentNode !== bodyContent) { | ||
createOrUpdateTooltip(); | |||
setupClickOutsideHandler(); | |||
} | } | ||
return; | |||
} | } | ||
// 鼠标设备处理 | |||
if (e.type === 'mouseleave') { | if (e.type === 'mouseleave') { | ||
clearTimeout(showTimer); | clearTimeout(showTimer); | ||
hideTooltip(); | |||
return; | return; | ||
} | } | ||
// mouseenter处理 | |||
showTimer = setTimeout( | clearTimeout(showTimer); | ||
showTimer = setTimeout(createOrUpdateTooltip, hoverDelay); | |||
}); | }); | ||
}); | }); | ||
})(); | })(); | ||
})(); | })(); | ||
2025年11月2日 (日) 18:14的最新版本
(function() {
/*===== 初始化检查 =====*/
// Cookie 检查(保持原始逻辑)
const dontLoad = mw.util.getParamValue("UTdontload");
if (dontLoad && !isNaN(dontLoad)) {
mw.cookie.set("UTdontload", "1", { path: "/", expires: parseInt(dontLoad) });
}
if (mw.cookie.get("UTdontload") === "1") return;
/*===== 主逻辑 =====*/
(function() {
// 容器和目标命名空间检查
const bodyContent = mw.util.$content || document.body;
const canonicalNamespace = mw.config.get('wgCanonicalNamespace');
if (!['', 'Project', 'Help'].includes(canonicalNamespace)) return;
// 触摸设备检测(优化后的逻辑)
const isTouchscreen = window.matchMedia('(hover: none), (pointer: coarse)').matches ||
mw.config.get('wgDisplayResolution') === 'mobile' ||
'ontouchstart' in document.documentElement;
const hoverDelay = isTouchscreen ? 0 : 200;
/*===== 工具提示核心逻辑 =====*/
$(".inline-unihan").each(function() {
let tooltipNode = null;
let hideTimer = null;
let showTimer = null;
const element = this;
// 保存原始title并添加高亮类
const originalTitle = this.title;
this.title = "";
$(this).addClass("UHTarget");
/*-- 工具提示控制函数 --*/
function hideTooltip() {
if (!tooltipNode || tooltipNode.parentNode !== bodyContent) return;
clearTimeout(hideTimer);
hideTimer = setTimeout(() => {
$(tooltipNode).animate({ opacity: 0 }, 100, () => {
tooltipNode.parentNode.removeChild(tooltipNode);
});
}, isTouchscreen ? 16 : 100);
}
function showTooltip() {
if (!tooltipNode.parentNode || tooltipNode.parentNode.nodeType === 11) {
bodyContent.appendChild(tooltipNode);
}
$(tooltipNode).stop().animate({ opacity: 1 }, 100);
clearTimeout(hideTimer);
}
/*-- 工具提示创建/更新 --*/
function createOrUpdateTooltip() {
if (!tooltipNode) {
// 首次创建工具提示
tooltipNode = document.createElement("ul");
tooltipNode.className = "unihantooltip";
// 内容容器 (第一个li)
const contentLi = document.createElement("li");
// 设置齿轮图标
const settingsIcon = document.createElement("div");
settingsIcon.className = "UHsettings";
settingsIcon.title = "设置";
$(settingsIcon).on("click", (e) => {
e.stopPropagation();
// 这里添加设置图标的点击处理逻辑
});
// 箭头元素 (第二个li)
const arrowLi = document.createElement("li");
// 组装DOM结构
contentLi.appendChild(createContentElement());
contentLi.appendChild(settingsIcon);
tooltipNode.appendChild(contentLi);
tooltipNode.appendChild(arrowLi);
// 非触摸设备的悬停处理
if (!isTouchscreen) {
$(tooltipNode).on("mouseenter", showTooltip)
.on("mouseleave", hideTooltip);
}
} else {
// 更新已有内容
const contentContainer = tooltipNode.firstChild.firstChild;
tooltipNode.firstChild.replaceChild(
createContentElement(),
contentContainer
);
}
// 定位并显示
positionTooltip();
showTooltip();
}
/*-- 内容生成函数 --*/
function createContentElement() {
const container = document.createElement("div");
originalTitle.split("\n").forEach(line => {
if (line.trim()) {
const p = document.createElement("p");
p.textContent = line;
container.appendChild(p);
}
});
return container;
}
/*-- 精确定位逻辑 (关键调整) --*/
function positionTooltip() {
const $element = $(element);
const elementPos = $element.offset();
const elementHeight = $element.outerHeight();
const elementWidth = $element.outerWidth();
// 临时显示以获取尺寸
tooltipNode.style.display = 'block';
const tooltipWidth = tooltipNode.offsetWidth;
const contentHeight = tooltipNode.firstChild.offsetHeight;
const arrowHeight = 12; // 对应CSS中的箭头高度
// 视口边界检测
const viewport = {
top: $(window).scrollTop(),
bottom: $(window).scrollTop() + $(window).height(),
left: 0,
right: $(window).width()
};
/*===== 垂直定位 =====*/
// 默认上方显示 (减内容高度和箭头高度)
let top = elementPos.top - contentHeight - arrowHeight;
// 检查是否需要翻转 (空间不足时)
const needFlip = (top < viewport.top) ||
(elementPos.top + contentHeight > viewport.bottom);
$(tooltipNode).toggleClass("UHflipped", needFlip);
// 应用翻转偏移量 (13px对应CSS的padding-top)
top = needFlip
? elementPos.top + elementHeight + arrowHeight - 13
: top;
// 确保不超出视口
top = Math.max(viewport.top, Math.min(top, viewport.bottom - contentHeight));
/*===== 水平定位 =====*/
// 中心对齐 (7px对应CSS箭头的margin-left)
let left = elementPos.left + (elementWidth - tooltipWidth) / 2;
// 边界保护 (左右各留10px安全边距)
left = Math.max(viewport.left + 10,
Math.min(left, viewport.right - tooltipWidth - 10));
/*===== 箭头定位 =====*/
const arrow = tooltipNode.lastChild;
const arrowCenterOffset = elementPos.left - left + elementWidth / 2 - 7;
$(arrow).css("margin-left", arrowCenterOffset + "px");
// 应用最终位置
$(tooltipNode).css({
top: top + "px",
left: left + "px",
display: '' // 恢复原始display
});
}
/*-- 触摸设备的外部点击处理 --*/
function setupClickOutsideHandler() {
const handler = function(e) {
if (!tooltipNode.contains(e.target) && e.target !== element) {
hideTooltip();
$(document).off("click touchstart", handler);
}
};
$(document).on("click touchstart", handler);
}
/*===== 事件绑定 =====*/
$(element).on(isTouchscreen ? 'click' : 'mouseenter mouseleave', function(e) {
// 触摸设备处理
if (isTouchscreen && e.type === 'click') {
e.preventDefault();
if (!tooltipNode || tooltipNode.parentNode !== bodyContent) {
createOrUpdateTooltip();
setupClickOutsideHandler();
}
return;
}
// 鼠标设备处理
if (e.type === 'mouseleave') {
clearTimeout(showTimer);
hideTooltip();
return;
}
// mouseenter处理
clearTimeout(showTimer);
showTimer = setTimeout(createOrUpdateTooltip, hoverDelay);
});
});
})();
})();