mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-20 19:34:05 -06:00
feat(navigation): improve hash anchor scrolling with header-aware positioning and visual feedback
- Add scrollToElementWithBuffer and highlightElement utility functions to util.ts - Implement header-aware scroll positioning to prevent content hiding behind fixed headers - Add temporary highlight effect for scrolled-to elements to improve user experience - Refactor SPA and popover scripts to use shared utility functions (DRY principle) - Support configurable buffer space and highlight duration for different use cases Fixes content being obscured by page header when navigating to hash anchors. Enhances UX with visual feedback showing which heading was targeted.
This commit is contained in:
parent
467896413f
commit
c803941cdd
@ -1,6 +1,7 @@
|
||||
import { computePosition, flip, inline, shift } from "@floating-ui/dom"
|
||||
import { fetchCanonical, scrollInContainerToElement } from "./util"
|
||||
|
||||
import { normalizeRelativeURLs } from "../../util/path"
|
||||
import { fetchCanonical } from "./util"
|
||||
|
||||
const p = new DOMParser()
|
||||
let activeAnchor: HTMLAnchorElement | null = null
|
||||
@ -33,8 +34,8 @@ async function mouseEnterHandler(
|
||||
const targetAnchor = `#popover-internal-${hash.slice(1)}`
|
||||
const heading = popoverInner.querySelector(targetAnchor) as HTMLElement | null
|
||||
if (heading) {
|
||||
// leave ~12px of buffer when scrolling to a heading
|
||||
popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" })
|
||||
// Use utility function to scroll with buffer and highlight
|
||||
scrollInContainerToElement(popoverInner, heading, 20, true, "instant")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import micromorph from "micromorph"
|
||||
import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path"
|
||||
import { fetchCanonical } from "./util"
|
||||
import { fetchCanonical, scrollToElementWithBuffer } from "./util"
|
||||
|
||||
import micromorph from "micromorph"
|
||||
|
||||
// adapted from `micromorph`
|
||||
// https://github.com/natemoo-re/micromorph
|
||||
@ -108,7 +109,9 @@ async function _navigate(url: URL, isBack: boolean = false) {
|
||||
if (!isBack) {
|
||||
if (url.hash) {
|
||||
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
|
||||
el?.scrollIntoView()
|
||||
if (el) {
|
||||
scrollToElementWithBuffer(el)
|
||||
}
|
||||
} else {
|
||||
window.scrollTo({ top: 0 })
|
||||
}
|
||||
@ -155,7 +158,9 @@ function createRouter() {
|
||||
|
||||
if (isSamePage(url) && url.hash) {
|
||||
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
|
||||
el?.scrollIntoView()
|
||||
if (el) {
|
||||
scrollToElementWithBuffer(el)
|
||||
}
|
||||
history.pushState({}, "", url)
|
||||
return
|
||||
}
|
||||
|
||||
@ -44,3 +44,83 @@ export async function fetchCanonical(url: URL): Promise<Response> {
|
||||
const [_, redirect] = text.match(canonicalRegex) ?? []
|
||||
return redirect ? fetch(`${new URL(redirect, url)}`) : res
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights an element with a temporary background color effect
|
||||
* @param el - The element to highlight
|
||||
* @param duration - How long to show the highlight in milliseconds (default: 2000)
|
||||
* @param color - The highlight color (default: uses CSS variable --highlight)
|
||||
*/
|
||||
export function highlightElement(el: HTMLElement, duration: number = 2000, color: string = 'var(--highlight, #ffeb3b40)') {
|
||||
// Store original styles
|
||||
const originalBackground = el.style.backgroundColor
|
||||
const originalTransition = el.style.transition
|
||||
|
||||
// Apply highlight styles
|
||||
el.style.transition = 'background-color 0.3s ease'
|
||||
el.style.backgroundColor = color
|
||||
|
||||
// Remove highlight after specified duration
|
||||
setTimeout(() => {
|
||||
el.style.backgroundColor = originalBackground
|
||||
// Remove transition after background fades back
|
||||
setTimeout(() => {
|
||||
el.style.transition = originalTransition
|
||||
}, 300)
|
||||
}, duration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to an element with buffer space from the page header
|
||||
* @param el - The element to scroll to
|
||||
* @param buffer - Additional buffer space in pixels (default: 20)
|
||||
* @param highlight - Whether to highlight the element after scrolling (default: true)
|
||||
*/
|
||||
export function scrollToElementWithBuffer(el: HTMLElement, buffer: number = 20, highlight: boolean = true) {
|
||||
// Get the height of the header to calculate buffer
|
||||
const header = document.querySelector('.page-header') as HTMLElement
|
||||
const headerHeight = header ? header.offsetHeight : 0
|
||||
const totalOffset = headerHeight + buffer
|
||||
|
||||
// Calculate the target position
|
||||
const elementTop = el.offsetTop
|
||||
const targetPosition = elementTop - totalOffset
|
||||
|
||||
// Scroll to the calculated position
|
||||
window.scrollTo({
|
||||
top: Math.max(0, targetPosition),
|
||||
behavior: 'smooth'
|
||||
})
|
||||
|
||||
// Add highlight effect if requested
|
||||
if (highlight) {
|
||||
highlightElement(el)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls within a container element to a target element with buffer
|
||||
* @param container - The container element to scroll within
|
||||
* @param target - The target element to scroll to
|
||||
* @param buffer - Buffer space in pixels (default: 20)
|
||||
* @param highlight - Whether to highlight the element after scrolling (default: true)
|
||||
* @param behavior - Scroll behavior (default: 'instant')
|
||||
*/
|
||||
export function scrollInContainerToElement(
|
||||
container: HTMLElement,
|
||||
target: HTMLElement,
|
||||
buffer: number = 20,
|
||||
highlight: boolean = true,
|
||||
behavior: ScrollBehavior = 'instant'
|
||||
) {
|
||||
const targetPosition = target.offsetTop - buffer
|
||||
container.scroll({
|
||||
top: Math.max(0, targetPosition),
|
||||
behavior
|
||||
})
|
||||
|
||||
// Add highlight effect if requested
|
||||
if (highlight) {
|
||||
highlightElement(target)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user