View Transition Playground
Use the Proton Elements StackBlitz template to try the transition queue without cloning the repository.
- Open the linked template and replace the contents of
src/App.tsxwith the snippet below. - Save the file. StackBlitz will hot-reload and run the transition handlers in the browser.
- Tweak the animation timings or add more handlers to see how Proton keeps the previous view alive until all of them resolve.
src/App.tsx
import { Proton } from "@denshya/proton"
import "./style.css"
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
function App(this: Proton.Component) {
this.view.transitions = [
async (transit, previous, next) => {
const previousEl = this.inflator.inflate(previous) as HTMLElement
previousEl.dataset.state = "leaving"
await delay(140)
const nextEl = this.inflator.inflate(next) as HTMLElement
nextEl.dataset.state = "entering"
await transit()
requestAnimationFrame(() => {
nextEl.dataset.state = "active"
})
},
document.startViewTransition?.bind(document) as any,
]
const screens = ["Dashboard", "Billing", "Settings"]
let index = 0
if (this.view.current == null) {
this.view.set(<section className="screen">{screens[index]}</section>)
}
return (
<main className="app">
<header>
<h1>Proton View Transitions</h1>
<button
type="button"
onClick={() => {
index = (index + 1) % screens.length
this.view.setAsync(<section className="screen">{screens[index]}</section>)
}}
>
Next screen
</button>
</header>
<div className="stage">{this.view.current}</div>
</main>
)
}
export default App
src/style.css
:root {
color-scheme: dark;
font-family: system-ui, sans-serif;
background: radial-gradient(circle at top, #243b55 0%, #141e30 100%);
}
.app {
min-height: 100vh;
padding: 4rem;
display: grid;
gap: 3rem;
}
.stage {
position: relative;
height: 320px;
border-radius: 30px;
overflow: hidden;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3);
}
.screen {
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-size: 2.5rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
transition: transform 160ms ease, opacity 160ms ease;
}
.screen[data-state="active"] {
opacity: 1;
transform: translateX(0);
}
.screen[data-state="leaving"] {
opacity: 0;
transform: translateX(-12%);
}
.screen[data-state="entering"] {
opacity: 0;
transform: translateX(12%);
}
Tip: You can mix and match multiple handlers. Proton keeps running them in sequence and only resolves
setAsyncwhen every handler finishes.