From de33ab6dda501c288d453b59827f26e9ae36671d Mon Sep 17 00:00:00 2001 From: Jairus Joer Date: Sat, 25 Oct 2025 15:42:09 +0200 Subject: [PATCH] feat: add configuration option for enabling browser-native view transitions --- quartz.config.ts | 1 + quartz/cfg.ts | 6 ++++++ quartz/components/renderPage.tsx | 2 +- quartz/components/scripts/spa.inline.ts | 3 +-- quartz/components/scripts/util.ts | 13 +++++++++---- quartz/components/styles/backlinks.scss | 1 + quartz/components/styles/comments.scss | 3 +++ quartz/components/styles/darkmode.scss | 1 + quartz/components/styles/explorer.scss | 3 ++- quartz/components/styles/footer.scss | 1 + quartz/components/styles/graph.scss | 2 ++ quartz/components/styles/readermode.scss | 1 + quartz/components/styles/recentNotes.scss | 2 ++ quartz/components/styles/toc.scss | 2 ++ 14 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 quartz/components/styles/comments.scss diff --git a/quartz.config.ts b/quartz.config.ts index b3db3d60d..70bb89a2e 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -11,6 +11,7 @@ const config: QuartzConfig = { pageTitle: "Quartz 4", pageTitleSuffix: "", enableSPA: true, + enableViewTransitions: true, enablePopovers: true, analytics: { provider: "plausible", diff --git a/quartz/cfg.ts b/quartz/cfg.ts index 57dff5c75..7dc6e015d 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -56,6 +56,12 @@ export interface GlobalConfiguration { pageTitleSuffix?: string /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ enableSPA: boolean + /** + * `enableSPA: true` required; Whether to enable browser-native view transitions for single-page-app style rendering. + * + * Customizing your animations: https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#customizing_your_animations. + */ + enableViewTransitions: boolean /** Whether to display Wikipedia-style popovers when hovering over links */ enablePopovers: boolean /** Analytics mode */ diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx index 3ebfe4879..a71cc92e3 100644 --- a/quartz/components/renderPage.tsx +++ b/quartz/components/renderPage.tsx @@ -235,7 +235,7 @@ export function renderPage( const doc = ( - +
{LeftComponent} diff --git a/quartz/components/scripts/spa.inline.ts b/quartz/components/scripts/spa.inline.ts index d827394d0..225648d49 100644 --- a/quartz/components/scripts/spa.inline.ts +++ b/quartz/components/scripts/spa.inline.ts @@ -128,9 +128,8 @@ async function _navigate(url: URL, isBack: boolean = false) { } notifyNav(getFullSlug(window)) + delete announcer.dataset.persist }) - - delete announcer.dataset.persist } async function navigate(url: URL, isBack: boolean = false) { diff --git a/quartz/components/scripts/util.ts b/quartz/components/scripts/util.ts index a0bb5da86..c3ab774c3 100644 --- a/quartz/components/scripts/util.ts +++ b/quartz/components/scripts/util.ts @@ -46,10 +46,15 @@ export async function fetchCanonical(url: URL): Promise { } /** - * Wraps a DOM update in a View Transition if supported by the browser. - * Falls back to immediate execution if the API is unavailable. - * @param callback - The function containing DOM updates to animate + * Wraps a DOM update in a View Transition if supported by the browser and enabled in config. + * Falls back to immediate execution if the API is unavailable or disabled. */ export function startViewTransition(callback: () => void): void { - document.startViewTransition?.(() => callback()) ?? callback() + const enableViewTransitions = document.body.dataset.viewTransitions === "true" + + if (enableViewTransitions && document.startViewTransition) { + document.startViewTransition(() => callback()) + } else { + callback() + } } diff --git a/quartz/components/styles/backlinks.scss b/quartz/components/styles/backlinks.scss index 478e118d2..b3e094a7e 100644 --- a/quartz/components/styles/backlinks.scss +++ b/quartz/components/styles/backlinks.scss @@ -2,6 +2,7 @@ .backlinks { flex-direction: column; + view-transition-name: backlinks; & > h3 { font-size: 1rem; diff --git a/quartz/components/styles/comments.scss b/quartz/components/styles/comments.scss new file mode 100644 index 000000000..285cbadf2 --- /dev/null +++ b/quartz/components/styles/comments.scss @@ -0,0 +1,3 @@ +.giscus { + view-transition-name: giscus; +} diff --git a/quartz/components/styles/darkmode.scss b/quartz/components/styles/darkmode.scss index b328743d4..6c5980fff 100644 --- a/quartz/components/styles/darkmode.scss +++ b/quartz/components/styles/darkmode.scss @@ -9,6 +9,7 @@ margin: 0; text-align: inherit; flex-shrink: 0; + view-transition-name: darkmode; & svg { position: absolute; diff --git a/quartz/components/styles/explorer.scss b/quartz/components/styles/explorer.scss index 8d9410044..84a859670 100644 --- a/quartz/components/styles/explorer.scss +++ b/quartz/components/styles/explorer.scss @@ -30,9 +30,10 @@ display: flex; flex-direction: column; overflow-y: hidden; - min-height: 1.2rem; flex: 0 1 auto; + view-transition-name: explorer; + &.collapsed { flex: 0 1 1.2rem; & .fold { diff --git a/quartz/components/styles/footer.scss b/quartz/components/styles/footer.scss index 9c8dbf8c1..cf696f4bb 100644 --- a/quartz/components/styles/footer.scss +++ b/quartz/components/styles/footer.scss @@ -2,6 +2,7 @@ footer { text-align: left; margin-bottom: 4rem; opacity: 0.7; + view-transition-name: footer; & ul { list-style: none; diff --git a/quartz/components/styles/graph.scss b/quartz/components/styles/graph.scss index cb1b7b464..23a4a6f65 100644 --- a/quartz/components/styles/graph.scss +++ b/quartz/components/styles/graph.scss @@ -1,6 +1,8 @@ @use "../../styles/variables.scss" as *; .graph { + view-transition-name: graph; + & > h3 { font-size: 1rem; margin: 0; diff --git a/quartz/components/styles/readermode.scss b/quartz/components/styles/readermode.scss index 79332c3b6..d42eabbb8 100644 --- a/quartz/components/styles/readermode.scss +++ b/quartz/components/styles/readermode.scss @@ -9,6 +9,7 @@ margin: 0; text-align: inherit; flex-shrink: 0; + view-transition-name: readermode; & svg { position: absolute; diff --git a/quartz/components/styles/recentNotes.scss b/quartz/components/styles/recentNotes.scss index 726767198..ee206455c 100644 --- a/quartz/components/styles/recentNotes.scss +++ b/quartz/components/styles/recentNotes.scss @@ -1,4 +1,6 @@ .recent-notes { + view-transition-name: recent-notes; + & > h3 { margin: 0.5rem 0 0 0; font-size: 1rem; diff --git a/quartz/components/styles/toc.scss b/quartz/components/styles/toc.scss index 6a7723bdc..1705c8c28 100644 --- a/quartz/components/styles/toc.scss +++ b/quartz/components/styles/toc.scss @@ -6,6 +6,8 @@ overflow-y: hidden; min-height: 1.4rem; flex: 0 0.5 auto; + view-transition-name: toc; + &:has(button.toc-header.collapsed) { flex: 0 1 1.4rem; }