Show definition popups on mouseover
This commit is contained in:
parent
d3b047cab2
commit
180832b84c
2 changed files with 185 additions and 0 deletions
139
script.js
139
script.js
|
|
@ -351,3 +351,142 @@ window.addEventListener("hashchange", () => {
|
||||||
tocList?.querySelector(`a[href="#${id}"]`)?.scrollIntoView({ block: "nearest" });
|
tocList?.querySelector(`a[href="#${id}"]`)?.scrollIntoView({ block: "nearest" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Entry popup preview on hover
|
||||||
|
const popup = document.createElement("div");
|
||||||
|
popup.className = "entry-popup";
|
||||||
|
popup.hidden = true;
|
||||||
|
document.body.appendChild(popup);
|
||||||
|
|
||||||
|
let hoverTimer = null;
|
||||||
|
let closeTimer = null;
|
||||||
|
const HOVER_DELAY = 500;
|
||||||
|
const CLOSE_DELAY = 500;
|
||||||
|
|
||||||
|
const getEntryContent = (id) => {
|
||||||
|
const titleEl = document.getElementById(id);
|
||||||
|
if (!titleEl) return null;
|
||||||
|
|
||||||
|
const allTitles = Array.from(document.querySelectorAll(".entry-title"));
|
||||||
|
const titleIndex = allTitles.indexOf(titleEl);
|
||||||
|
const nextTitle = allTitles[titleIndex + 1] || null;
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStartBefore(titleEl);
|
||||||
|
if (nextTitle) {
|
||||||
|
range.setEndBefore(nextTitle);
|
||||||
|
} else {
|
||||||
|
range.setEndAfter(contentEl.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = range.cloneContents();
|
||||||
|
|
||||||
|
const clonedTitle = content.querySelector(".entry-title");
|
||||||
|
if (clonedTitle) {
|
||||||
|
clonedTitle.removeAttribute("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
const positionPopup = (linkRect) => {
|
||||||
|
const pad = 8;
|
||||||
|
const popupRect = popup.getBoundingClientRect();
|
||||||
|
const vw = window.innerWidth;
|
||||||
|
const vh = window.innerHeight;
|
||||||
|
|
||||||
|
let top = linkRect.top - popupRect.height - pad;
|
||||||
|
let left = linkRect.left;
|
||||||
|
|
||||||
|
if (top < pad) {
|
||||||
|
top = linkRect.bottom + pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left + popupRect.width > vw - pad) {
|
||||||
|
left = vw - popupRect.width - pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left < pad) {
|
||||||
|
left = pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.style.top = `${top}px`;
|
||||||
|
popup.style.left = `${left}px`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showPopup = (link) => {
|
||||||
|
const href = link.getAttribute("href");
|
||||||
|
if (!href?.startsWith("#entry-")) return;
|
||||||
|
|
||||||
|
const id = href.slice(1);
|
||||||
|
const content = getEntryContent(id);
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
popup.innerHTML = "";
|
||||||
|
popup.appendChild(content);
|
||||||
|
popup.hidden = false;
|
||||||
|
|
||||||
|
popup.style.top = "0";
|
||||||
|
popup.style.left = "0";
|
||||||
|
popup.classList.remove("visible");
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
popup.scrollTop = 0;
|
||||||
|
const linkRect = link.getBoundingClientRect();
|
||||||
|
positionPopup(linkRect);
|
||||||
|
popup.classList.add("visible");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const hidePopup = () => {
|
||||||
|
popup.classList.remove("visible");
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!popup.classList.contains("visible")) {
|
||||||
|
popup.hidden = true;
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearTimers = () => {
|
||||||
|
clearTimeout(hoverTimer);
|
||||||
|
clearTimeout(closeTimer);
|
||||||
|
hoverTimer = null;
|
||||||
|
closeTimer = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEntryLink = (el) =>
|
||||||
|
el?.tagName === "A" &&
|
||||||
|
el.classList.contains("entry-link") &&
|
||||||
|
el.getAttribute("href")?.startsWith("#entry-");
|
||||||
|
|
||||||
|
contentEl?.addEventListener("mouseenter", (e) => {
|
||||||
|
if (isMobile()) return;
|
||||||
|
if (!isEntryLink(e.target)) return;
|
||||||
|
|
||||||
|
clearTimers();
|
||||||
|
hoverTimer = setTimeout(() => showPopup(e.target), HOVER_DELAY);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
contentEl?.addEventListener("mouseleave", (e) => {
|
||||||
|
if (isMobile()) return;
|
||||||
|
if (!isEntryLink(e.target)) return;
|
||||||
|
|
||||||
|
clearTimers();
|
||||||
|
closeTimer = setTimeout(hidePopup, CLOSE_DELAY);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
popup.addEventListener("mouseenter", () => {
|
||||||
|
clearTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.addEventListener("mouseleave", () => {
|
||||||
|
clearTimers();
|
||||||
|
closeTimer = setTimeout(hidePopup, CLOSE_DELAY);
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.addEventListener("click", (e) => {
|
||||||
|
if (e.target.tagName === "A") {
|
||||||
|
clearTimers();
|
||||||
|
hidePopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
46
style.css
46
style.css
|
|
@ -267,6 +267,52 @@ body.toc-collapsed .toc-toggle::before {
|
||||||
grid-template-columns: 0 minmax(0, 1fr);
|
grid-template-columns: 0 minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Entry popup preview */
|
||||||
|
.entry-popup {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 300;
|
||||||
|
max-width: min(420px, 90vw);
|
||||||
|
max-height: min(320px, 60vh);
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--paper);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
box-shadow:
|
||||||
|
0 8px 32px var(--shadow),
|
||||||
|
0 2px 8px rgba(45, 34, 26, 0.1);
|
||||||
|
border: 1px solid rgba(70, 50, 36, 0.15);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
transition:
|
||||||
|
opacity 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-popup.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-popup .entry-title {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-popup p {
|
||||||
|
margin: 0 0 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-popup p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-popup .entry-link {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.page,
|
.page,
|
||||||
.hero,
|
.hero,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue