mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 10:54:06 -06:00
Merge a295d46018 into c99c8070f2
This commit is contained in:
commit
72ff7134af
@ -23,6 +23,7 @@ This part of the configuration concerns anything that can affect the whole site.
|
|||||||
- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site.
|
- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site.
|
||||||
- `pageTitleSuffix`: a string added to the end of the page title. This only applies to the browser tab title, not the title shown at the top of the page.
|
- `pageTitleSuffix`: a string added to the end of the page title. This only applies to the browser tab title, not the title shown at the top of the page.
|
||||||
- `enableSPA`: whether to enable [[SPA Routing]] on your site.
|
- `enableSPA`: whether to enable [[SPA Routing]] on your site.
|
||||||
|
- `enableViewTransitions`: whether to enable browser-native [[View Transitions]] on your site.
|
||||||
- `enablePopovers`: whether to enable [[popover previews]] on your site.
|
- `enablePopovers`: whether to enable [[popover previews]] on your site.
|
||||||
- `analytics`: what to use for analytics on your site. Values can be
|
- `analytics`: what to use for analytics on your site. Values can be
|
||||||
- `null`: don't use analytics;
|
- `null`: don't use analytics;
|
||||||
|
|||||||
63
docs/features/View Transitions.md
Normal file
63
docs/features/View Transitions.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: "View Transitions"
|
||||||
|
---
|
||||||
|
|
||||||
|
View Transitions provide smooth, animated page transitions when navigating between pages in Quartz. This progressively enhances the [[SPA Routing]] experience by utilizing the browser-native [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) where available.
|
||||||
|
|
||||||
|
When navigating between pages, if the View Transitions API is supported and enabled, Quartz will wrap the content update in a view transition. This creates smooth, customizable animations as the page content changes, providing a more polished and app-like experience.
|
||||||
|
|
||||||
|
Under the hood, Quartz uses [[SPA Routing]] to intercept link clicks and perform client-side navigation. The View Transitions feature builds on top of this by wrapping the DOM updates in `document.startViewTransition()`, allowing the browser to automatically animate the changes between the old and new page states.
|
||||||
|
|
||||||
|
> [!info]
|
||||||
|
> View Transitions require [[SPA Routing]] to be enabled in the [[configuration]]. Browsers that don't support the View Transitions API will fall back to instant page updates without animations.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
Quartz assigns appropriate `view-transition-name` properties to its component, allowing you to create custom animations for different parts of the page.
|
||||||
|
|
||||||
|
### Styling Transitions
|
||||||
|
|
||||||
|
You can customize the behavior of these transitions by adding CSS to your `quartz/styles/custom.scss` file. For example:
|
||||||
|
|
||||||
|
```scss title="quartz/styles/custom.scss"
|
||||||
|
::view-transition-old(root),
|
||||||
|
::view-transition-new(root) {
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced customization options, see the [MDN documentation on customizing view transition animations](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#customizing_your_animations).
|
||||||
|
|
||||||
|
### Programmatic Transitions
|
||||||
|
|
||||||
|
When [[creating components|creating your own components]], you can use the `startViewTransition` utility function to wrap DOM updates in a view transition. This ensures your custom scripts respect your view transition settings.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { startViewTransition } from "./scripts/util"
|
||||||
|
|
||||||
|
// Wrap your DOM update in a view transition
|
||||||
|
startViewTransition(() => {
|
||||||
|
// Your DOM updates here
|
||||||
|
element.textContent = "New content"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The `startViewTransition` function will automatically use the View Transitions API if the browser is supported, otherwise it will fallback to eager execution.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
View Transitions require both [[SPA Routing]] and the View Transitions feature to be enabled in the [[configuration]]:
|
||||||
|
|
||||||
|
1. Enable [[SPA Routing]]: set the `enableSPA` field to `true`
|
||||||
|
2. Enable View Transitions: set the `enableViewTransitions` field to `true`
|
||||||
|
|
||||||
|
```typescript title="quartz.config.ts"
|
||||||
|
const config: QuartzConfig = {
|
||||||
|
configuration: {
|
||||||
|
// ...
|
||||||
|
enableSPA: true,
|
||||||
|
enableViewTransitions: true,
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
@ -11,6 +11,7 @@ const config: QuartzConfig = {
|
|||||||
pageTitle: "Quartz 4",
|
pageTitle: "Quartz 4",
|
||||||
pageTitleSuffix: "",
|
pageTitleSuffix: "",
|
||||||
enableSPA: true,
|
enableSPA: true,
|
||||||
|
enableViewTransitions: false,
|
||||||
enablePopovers: true,
|
enablePopovers: true,
|
||||||
analytics: {
|
analytics: {
|
||||||
provider: "plausible",
|
provider: "plausible",
|
||||||
|
|||||||
@ -61,6 +61,12 @@ export interface GlobalConfiguration {
|
|||||||
pageTitleSuffix?: string
|
pageTitleSuffix?: string
|
||||||
/** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */
|
/** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */
|
||||||
enableSPA: boolean
|
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 */
|
/** Whether to display Wikipedia-style popovers when hovering over links */
|
||||||
enablePopovers: boolean
|
enablePopovers: boolean
|
||||||
/** Analytics mode */
|
/** Analytics mode */
|
||||||
|
|||||||
@ -262,7 +262,7 @@ export function renderPage(
|
|||||||
const doc = (
|
const doc = (
|
||||||
<html lang={lang} dir={direction}>
|
<html lang={lang} dir={direction}>
|
||||||
<Head {...componentData} />
|
<Head {...componentData} />
|
||||||
<body data-slug={slug}>
|
<body data-slug={slug} data-view-transitions={cfg.enableViewTransitions}>
|
||||||
<div id="quartz-root" class="page">
|
<div id="quartz-root" class="page">
|
||||||
<Body {...componentData}>
|
<Body {...componentData}>
|
||||||
{LeftComponent}
|
{LeftComponent}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import micromorph from "micromorph"
|
import micromorph from "micromorph"
|
||||||
import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path"
|
import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path"
|
||||||
import { fetchCanonical } from "./util"
|
import { fetchCanonical, startViewTransition } from "./util"
|
||||||
|
|
||||||
// adapted from `micromorph`
|
// adapted from `micromorph`
|
||||||
// https://github.com/natemoo-re/micromorph
|
// https://github.com/natemoo-re/micromorph
|
||||||
@ -102,32 +102,34 @@ async function _navigate(url: URL, isBack: boolean = false) {
|
|||||||
html.body.appendChild(announcer)
|
html.body.appendChild(announcer)
|
||||||
|
|
||||||
// morph body
|
// morph body
|
||||||
micromorph(document.body, html.body)
|
startViewTransition(() => {
|
||||||
|
micromorph(document.body, html.body)
|
||||||
|
|
||||||
// scroll into place and add history
|
// scroll into place and add history
|
||||||
if (!isBack) {
|
if (!isBack) {
|
||||||
if (url.hash) {
|
if (url.hash) {
|
||||||
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
|
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
|
||||||
el?.scrollIntoView()
|
el?.scrollIntoView()
|
||||||
} else {
|
} else {
|
||||||
window.scrollTo({ top: 0 })
|
window.scrollTo({ top: 0 })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// now, patch head, re-executing scripts
|
// now, patch head, re-executing scripts
|
||||||
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
|
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
|
||||||
elementsToRemove.forEach((el) => el.remove())
|
elementsToRemove.forEach((el) => el.remove())
|
||||||
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
|
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
|
||||||
elementsToAdd.forEach((el) => document.head.appendChild(el))
|
elementsToAdd.forEach((el) => document.head.appendChild(el))
|
||||||
|
|
||||||
// delay setting the url until now
|
// delay setting the url until now
|
||||||
// at this point everything is loaded so changing the url should resolve to the correct addresses
|
// at this point everything is loaded so changing the url should resolve to the correct addresses
|
||||||
if (!isBack) {
|
if (!isBack) {
|
||||||
history.pushState({}, "", url)
|
history.pushState({}, "", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyNav(getFullSlug(window))
|
notifyNav(getFullSlug(window))
|
||||||
delete announcer.dataset.persist
|
delete announcer.dataset.persist
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function navigate(url: URL, isBack: boolean = false) {
|
async function navigate(url: URL, isBack: boolean = false) {
|
||||||
|
|||||||
@ -44,3 +44,17 @@ export async function fetchCanonical(url: URL): Promise<Response> {
|
|||||||
const [_, redirect] = text.match(canonicalRegex) ?? []
|
const [_, redirect] = text.match(canonicalRegex) ?? []
|
||||||
return redirect ? fetch(`${new URL(redirect, url)}`) : res
|
return redirect ? fetch(`${new URL(redirect, url)}`) : res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
const enableViewTransitions = document.body.dataset.viewTransitions === "true"
|
||||||
|
|
||||||
|
if (enableViewTransitions && document.startViewTransition) {
|
||||||
|
document.startViewTransition(() => callback())
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
.backlinks {
|
.backlinks {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
view-transition-name: backlinks;
|
||||||
|
|
||||||
& > h3 {
|
& > h3 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|||||||
3
quartz/components/styles/comments.scss
Normal file
3
quartz/components/styles/comments.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.giscus {
|
||||||
|
view-transition-name: giscus;
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
view-transition-name: darkmode;
|
||||||
|
|
||||||
& svg {
|
& svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -30,9 +30,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
|
||||||
min-height: 1.2rem;
|
min-height: 1.2rem;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
|
view-transition-name: explorer;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
flex: 0 1 1.2rem;
|
flex: 0 1 1.2rem;
|
||||||
& .fold {
|
& .fold {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ footer {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
margin-bottom: 4rem;
|
margin-bottom: 4rem;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
view-transition-name: footer;
|
||||||
|
|
||||||
& ul {
|
& ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
@use "../../styles/variables.scss" as *;
|
@use "../../styles/variables.scss" as *;
|
||||||
|
|
||||||
.graph {
|
.graph {
|
||||||
|
&:not(:has(.active)) {
|
||||||
|
view-transition-name: graph;
|
||||||
|
}
|
||||||
|
|
||||||
& > h3 {
|
& > h3 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -64,6 +68,7 @@
|
|||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
@media all and not ($desktop) {
|
@media all and not ($desktop) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
view-transition-name: readermode;
|
||||||
|
|
||||||
& svg {
|
& svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
.recent-notes {
|
.recent-notes {
|
||||||
|
view-transition-name: recent-notes;
|
||||||
|
|
||||||
& > h3 {
|
& > h3 {
|
||||||
margin: 0.5rem 0 0 0;
|
margin: 0.5rem 0 0 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
min-height: 1.4rem;
|
min-height: 1.4rem;
|
||||||
flex: 0 0.5 auto;
|
flex: 0 0.5 auto;
|
||||||
|
view-transition-name: toc;
|
||||||
|
|
||||||
&:has(button.toc-header.collapsed) {
|
&:has(button.toc-header.collapsed) {
|
||||||
flex: 0 1 1.4rem;
|
flex: 0 1 1.4rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,10 @@
|
|||||||
@use "./syntax.scss";
|
@use "./syntax.scss";
|
||||||
@use "./callouts.scss";
|
@use "./callouts.scss";
|
||||||
|
|
||||||
|
::view-transition {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
text-size-adjust: none;
|
text-size-adjust: none;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user