diff --git a/quartz/components/scripts/spa.inline.ts b/quartz/components/scripts/spa.inline.ts index 1790bca..7900286 100644 --- a/quartz/components/scripts/spa.inline.ts +++ b/quartz/components/scripts/spa.inline.ts @@ -1,5 +1,5 @@ import micromorph from "micromorph" -import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path" +import {FullSlug, getFullSlug, normalizeRelativeURLs, RelativeURL} from "../../util/path" // adapted from `micromorph` // https://github.com/natemoo-re/micromorph @@ -44,6 +44,9 @@ window.addCleanup = (fn) => cleanupFns.add(fn) let p: DOMParser async function navigate(url: URL, isBack: boolean = false) { + // Add loading class at the start of navigation + document.body.classList.add('loading') + p = p || new DOMParser() const contents = await fetch(`${url}`) .then((res) => { @@ -54,7 +57,7 @@ async function navigate(url: URL, isBack: boolean = false) { window.location.assign(url) } }) - .catch(() => { + .catch((error) => { window.location.assign(url) }) @@ -83,6 +86,33 @@ async function navigate(url: URL, isBack: boolean = false) { // morph body micromorph(document.body, html.body) + // 在 morph 完成后添加动画 + const headerElements = document.querySelectorAll('.page-header, .banner-wrapper') + const articleElements = document.querySelectorAll('article') + + // 先处理 header 元素 + headerElements.forEach((element) => { + if (element instanceof HTMLElement) { + element.classList.remove('page-transition') + void element.offsetHeight + element.classList.add('page-transition') + } + }) + + // 然后处理文章内容 + articleElements.forEach((element) => { + if (element instanceof HTMLElement) { + element.classList.remove('page-transition') + void element.offsetHeight + element.classList.add('page-transition') + } + }) + + // 移除 loading 类 + setTimeout(() => { + document.body.classList.remove('loading') + }, 100) + // scroll into place and add history if (!isBack) { if (url.hash) { @@ -100,7 +130,6 @@ async function navigate(url: URL, isBack: boolean = false) { elementsToAdd.forEach((el) => document.head.appendChild(el)) // delay setting the url until now - // at this point everything is loaded so changing the url should resolve to the correct addresses if (!isBack) { history.pushState({}, "", url) } diff --git a/quartz/styles/custom.scss b/quartz/styles/custom.scss index 00e6adf..9733aca 100644 --- a/quartz/styles/custom.scss +++ b/quartz/styles/custom.scss @@ -597,4 +597,46 @@ a.internal.broken-link { display: inline-flex; align-items: center; gap: 4px; +} + + +// 统一的页面切换动画基础样式 +.page-transition { + animation-fill-mode: both; + will-change: opacity, transform; + backface-visibility: hidden; // 防止闪烁 + perspective: 1000px; // 提高性能 +} + +// Header 区域动画(包括 banner) +.page-header.page-transition, +.banner-wrapper.page-transition { + animation: fadeInDown 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +// 内容区域动画 +article.page-transition { + animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +// 从上方滑入(用于 header) +@keyframes fadeInDown { + 0% { + opacity: 0; + transform: translateY(-10px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +// 淡入(用于内容) +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } } \ No newline at end of file