feat: add heading flash animation on hash change

This commit is contained in:
2026-07-14 15:38:39 +08:00
parent d78d15d8ec
commit a934901cce
3 changed files with 70 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
/**
* useHeadingFlash — watches location.hash and applies a fading highlight
* animation to the target heading element on every hash change.
*
* Handles page refresh / initial load by retrying up to ~500ms until the
* target element exists in the DOM.
*/
import { createEffect, onCleanup } from "solid-js";
import { useLocation } from "@solidjs/router";
const FLASH_CLASS = "heading-flash";
const MAX_RETRIES = 10;
const RETRY_INTERVAL = 50;
function flashElement(el: HTMLElement) {
// Re-trigger the animation: remove and re-add the class
el.classList.remove(FLASH_CLASS);
void el.offsetWidth; // force reflow
el.classList.add(FLASH_CLASS);
}
export function useHeadingFlash() {
const location = useLocation();
createEffect(() => {
const hash = location.hash;
if (!hash) return;
const id = decodeURIComponent(hash.startsWith("#") ? hash.slice(1) : hash);
if (!id) return;
let retries = 0;
let timer: ReturnType<typeof setTimeout>;
const tryFlash = () => {
const el = document.getElementById(id);
if (el) {
flashElement(el);
el.addEventListener("animationend", function onEnd() {
el.classList.remove(FLASH_CLASS);
el.removeEventListener("animationend", onEnd);
}, { once: true });
} else if (retries < MAX_RETRIES) {
retries++;
timer = setTimeout(tryFlash, RETRY_INTERVAL);
}
};
// Delay slightly so Solid has a chance to flush DOM updates from
// a concurrent SPA navigation before we look for the element.
timer = setTimeout(tryFlash, 0);
onCleanup(() => clearTimeout(timer));
});
}