feat: 添加站点卡片悬停提示功能

- 为所有站点卡片添加 data-tooltip 属性,包含完整的标题和描述信息
- tooltip 显示逻辑:
  * 鼠标悬停在整个卡片上即可触发(触发区域大,操作自然)
  * 跟随鼠标移动,实时更新位置
  * 智能边界检测,避免 tooltip 超出视口范围
  * 鼠标移出时自动隐藏
- 解决文本截断问题,用户可通过悬停查看完整内容

实现:
- 模板层:在 site-card.hbs 中为卡片添加 data-tooltip 属性
- 交互层:在 script.js 中实现 tooltip 的创建、显示、移动和隐藏逻辑
- 样式层:通过 CSS 类控制 tooltip 的可见性和位置

Issue: #31
This commit is contained in:
rbetree
2026-01-03 18:02:37 +08:00
parent 3473aaebd7
commit 2bebefbfe8
3 changed files with 592 additions and 428 deletions

View File

@@ -274,6 +274,7 @@ window.MeNav = {
newSite.href = siteUrl;
newSite.title = siteName + (siteDescription ? ' - ' + siteDescription : '');
newSite.setAttribute('data-tooltip', siteName + (siteDescription ? ' - ' + siteDescription : '')); // 添加自定义 tooltip
if (/^https?:\/\//i.test(siteUrl)) {
newSite.target = '_blank';
newSite.rel = 'noopener';
@@ -1909,3 +1910,73 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
});
// Tooltip functionality for truncated text
document.addEventListener('DOMContentLoaded', () => {
// Create tooltip element
const tooltip = document.createElement('div');
tooltip.className = 'custom-tooltip';
document.body.appendChild(tooltip);
let activeElement = null;
// Show tooltip on hover
document.addEventListener('mouseover', (e) => {
const target = e.target.closest('[data-tooltip]');
if (target) {
const tooltipText = target.getAttribute('data-tooltip');
if (tooltipText) {
activeElement = target;
tooltip.textContent = tooltipText;
tooltip.classList.add('visible');
updateTooltipPosition(e);
}
}
});
// Move tooltip with cursor
document.addEventListener('mousemove', (e) => {
if (activeElement) {
updateTooltipPosition(e);
}
});
// Hide tooltip on mouse out
document.addEventListener('mouseout', (e) => {
const target = e.target.closest('[data-tooltip]');
if (target && target === activeElement) {
// Check if we really left the element (not just went to a child)
if (!target.contains(e.relatedTarget)) {
activeElement = null;
tooltip.classList.remove('visible');
}
}
});
function updateTooltipPosition(e) {
// Position tooltip 15px below/right of cursor
const x = e.clientX + 15;
const y = e.clientY + 15;
// Boundary checks to keep inside viewport
const rect = tooltip.getBoundingClientRect();
const winWidth = window.innerWidth;
const winHeight = window.innerHeight;
let finalX = x;
let finalY = y;
// If tooltip goes off right edge
if (x + rect.width > winWidth) {
finalX = e.clientX - rect.width - 10;
}
// If tooltip goes off bottom edge
if (y + rect.height > winHeight) {
finalY = e.clientY - rect.height - 10;
}
tooltip.style.left = finalX + 'px';
tooltip.style.top = finalY + 'px';
}
});