Repository: domchristie/turn
Branch: main
Commit: ca7fece32f15
Files: 38
Total size: 69.2 KB
Directory structure:
gitextract_h2hckinu/
├── .gitignore
├── LICENSE
├── README.md
├── examples/
│ ├── animate-restore/
│ │ ├── index.html
│ │ ├── one.html
│ │ └── turn.css
│ ├── animations-only/
│ │ ├── index.html
│ │ ├── one.html
│ │ └── turn.css
│ ├── dynamic-view-transitions/
│ │ ├── index.html
│ │ ├── one.html
│ │ ├── script.js
│ │ └── two.html
│ ├── fallback/
│ │ ├── index.html
│ │ ├── one.html
│ │ └── turn.css
│ ├── hybrid-advanced/
│ │ ├── index.html
│ │ ├── one.html
│ │ └── turn.css
│ ├── hybrid-basic/
│ │ ├── index.html
│ │ ├── one.html
│ │ └── turn.css
│ ├── theme.css
│ └── view-transitions-only/
│ ├── index.html
│ └── one.html
├── index.html
├── one.html
├── package.json
├── rollup.config.js
├── src/
│ ├── animation-turn.js
│ ├── animations.js
│ ├── base-turn.js
│ ├── controller.js
│ ├── helpers.js
│ ├── null-turn.js
│ ├── turn.js
│ └── view-transition-turn.js
└── turn.css
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
dist
node_modules
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Dom Christie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Turn
Animate page transitions in [Turbo Drive](https://turbo.hotwired.dev/) apps.
## Installation
Install [@hotwired/turbo](https://www.npmjs.com/package/@hotwired/turbo), then
```
npm install @domchristie/turn
```
## Usage
1. Include Turbo, `dist/turn.js` and `dist/turn.css` however you build your JavaScript & CSS
2. Add `data-turn-enter` and `data-turn-exit` to the elements you wish to animate (optional if you're only using View Transitions)
3. `import Turn from '@domchristie/turn'` and call `Turn.start()` in your application JavaScript
4. Navigate between pages … ✨
## Customizing Animations
Turn adds `turn-before-exit`, `turn-exit`, and `turn-enter` classes to the HTML element at the appropriate times. Apply your own animations by scoping your animation rules with this selector. For example:
```css
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: MY_ANIMATE_OUT;
animation-duration: .3s;
animation-fill-mode: forwards;
}
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: MY_ANIMATE_IN;
animation-duration: .6s;
animation-fill-mode: forwards;
}
@keyframes MY_ANIMATE_OUT {
…
}
@keyframes MY_ANIMATE_IN {
…
}
```
This is how `turn.css` is organized, so you may want to get rid of that file altogether. `animation-fill-mode: forwards` is recommended to prevent your transitions from jumping back.
### Custom Class Names
The values set in the `data-turn-exit` and `data-turn-enter` attributes will be applied as class names to that element. This lets you customize animations for each element. Styles should still be scoped by `html.turn-exit` and `html.turn-enter`.
## Class List
The following class names are added to the HTML element at various time during a navigation lifecycle.
### `turn-view-transitions`
Added on start if the device supports View Transitions, and View Transitions are enabled.
### `turn-no-view-transitions`
Added on start if the device does not support View Transitions, or View Transitions are disabled.
### `turn-advance`
Added when a visit starts and the action is `advance`. You'll probably want to scope most animations with this class. Removed when the navigation has completed and all animations have ended.
### `turn-restore`
Added when a visit starts and the action is `restore`. Given the number of ways it's possible to navigate back/forward (back button, swipe left/right, `history.back()`), it's generally recommended that restoration visits are not animated. Removed when the navigation has completed and all animations have ended.
### `turn-replace`
Added when a visit starts and the action is `replace`. Removed when the navigation has completed and all animations have ended.
### `turn-before-exit`
Added when a visit starts. Useful for optimising animations with `will-change`. (See `turn.css` for an example.) Removed when the exit animations start.
### `turn-exit`
Added when a visit starts. Use this to scope exit animations. Removed when the exit animations complete.
### `turn-before-transition` (if View Transitions supported & enabled)
Added after the exit animations and any request has completed, but before the View Transitions have taken their snapshot. Useful when combining View Transitions with custom exit/enter animations. To avoid a flash of content, use this class to target exited elements, and style them in their final "exit" state. (See `turn.css` for an example.) Removed when the transition starts.
### `turn-transition` (if View Transitions supported & enabled)
Adding during a View Transition. Useful when combining View Transitions with custom exit/enter animations.
### `turn-enter`
Added after any requests have completed and previous animations/transitions have completed. Removed once the animations have completed.
## Events
`event.details` may contain:
- `action`: the action of the visit (`advance` or `restore`)
- `initiator`: the element the visit was triggered from (an `a`, `form`, or `html` element if a Back/Forward navigation)
- `referrer`: the URL the page is transitioning from
- `url`: the URL the page is transitioning to
- `newBody`: the incoming `<body>` that will be transitioned to
### `turn:before-exit`
Dispatched before exit animations are started. `event.detail` includes:
- `action`
- `initiator`
- `referrer`
- `url`
### `turn:before-transition`
Dispatched before a View Transition is started (after exit animations if present). Ideal for setting up `view-transition-name`s before the View Transition performs its capturing. `event.detail` includes:
- `action`
- `initiator`
- `newBody`
- `referrer`
### `turn:before-enter`
Dispatched before enter animations are started (after Vire Transitions if present). `event.detail` includes:
- `action`
- `initiator`
- `newBody`
- `referrer`
### `turn:enter`
Dispatched after the all transitions and animations have completed. `event.detail` includes:
- `action`
- `referrer`
- `url`
- `timing`: the visit's timing metrics
### Usage with Tailwind CSS
Define animations in `tailwind.config.js`, and add a plugin that scopes the styles, e.g.:
```js
const plugin = require('tailwindcss/plugin')
module.exports = {
theme: {
extend: {
animation: {
exit: 'fade-out-up 0.3s cubic-bezier(0.65, 0.05, 0.35, 1) forwards',
enter: 'fade-in-up 0.6s cubic-bezier(0.65, 0.05, 0.35, 1) forwards'
},
keyframes: {
'fade-out-up': {/* … */},
'fade-in-up': {/* … */}
}
}
},
plugins: [
plugin(function ({ addVariant }) {
addVariant('turn-exit', 'html.turn-exit &')
addVariant('turn-enter', 'html.turn-enter &')
})
]
}
```
Then in your HTML:
```html
<main data-turn-exit="turn-exit:animate-exit" data-turn-enter="turn-enter:animate-enter">
<!-- … -->
</main>
```
## Disabling Animations
Add `data-turn="false"` to the `<body>` to opt out of animations from that page.
(This currently rather basic, but is limited by the information available in Turbo events. Hopefully improvable if [`turbo:visit` events are fired on the initiating element](https://github.com/hotwired/turbo/pull/750).)
## Tip & Tricks
### 1. Animate Changes
Avoid animating the whole `body`. Animations should target elements that change on navigation. So avoid animating persistent headers and instead animate the `main` element or just the panels/cards within it.
### 2. Nesting
Nesting animating elements draws attention and brings screens to life. Add `data-turn-exit`/`data-turn-enter` attributes to elements such as headings and key images within an animating container. The compound animation effects means they'll exit faster, and enter slower than other elements. For example:
```html
<main data-turn-exit data-turn-enter>
<h1 data-turn-exit data-turn-enter>Hello, world!</h1>
</main>
```
### 3. Optimizing Animations
Jumpy exit animations can be prevented using the `will-change` CSS property. Turn adds a `turn-before-exit` class to the HTML element just before adding the exit classes. This provides an opportunity to notify the browser of upcoming changes. For example, by default `turn.css` does the following:
```css
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
```
### 4. Loading Spinner
Exit animations on slow requests can leave users with a blank screen. Improve the experience with a loading spinner that appears a short time after the exit animation. For example, if your exit animation take 600ms, add a spinner that starts appearing 700ms after that by using `transition-delay`. This spinner can live permanently in the `body` and only transition when the `turn-exit` class is applied:
```css
.spinner {
position: fixed;
top: 15%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 100ms;
}
html.turn-exit .spinner {
opacity: 1;
transition-delay: 700ms
}
```
## Not seeing animations?
Check your device preferences to see if you have requested reduced motion. Turn will only animate transitions when the `prefers-reduced-motion` media query does not match `reduce`.
## How does it work?
Turn adds exit and enter classes at the appropriate times like so:
1. on `turbo:visit` add the exit classes
2. pause `turbo:before-render` _(wait for exit animations to complete before resuming)_
3. on `turbo:render`, remove exit classes and add the enter classes
4. on `turbo:load`, wait for the enter animations to complete before removing the enter classes
## Credits
Default fade in/out animations adapted from [Jon Yablonski](https://jonyablonski.com/)'s [Humane By Design](https://humanebydesign.com/).
## License
Copyright © 2021+ Dom Christie and released under the MIT license.
================================================
FILE: examples/animate-restore/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animate Restore</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
addEventListener('click', function (event) {
if (event.target.dataset?.back != null) {
event.preventDefault()
document.documentElement.dataset.animateRestore = true
window.history.back()
}
})
addEventListener('turn:enter', function () {
document.documentElement.removeAttribute('data-animate-restore')
})
</script>
</head>
<body>
<nav>
<a href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn/blob/main/examples/animations-only/">Source</a>
</nav>
<main data-turbo="false" data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Turn: Animate Restore</h1>
<div class="timelines">
<div class="animation-timeline" style="grid-template-columns: 1fr 1fr">
<div class="animation animation-exit">Exit</div>
<div class="animation animation-enter">Enter</div>
</div>
</div>
<p>This demonstrates how to animate Back/Forward navigations with <a href="https://github.com/domchristie/turn">Turn</a> (without View Transitions). Tap the Lorem/Back links in the navigation bar to view, then use the browser's Back/Forward buttons to see the difference.</p>
<p><a href="../view-transitions-only">Next example</a> or <a href="../../">back to examples</a></p>
<p>Our Back link includes a <code style="font-size: 0.875em">data-back</code> attribute. When this is clicked, we prevent the default, apply a <code style="font-size: 0.875em">data-animate-restore</code> attribute to the HTML element, and navigate back via the history API. Then once the animations have completed, we tidy up.</p>
<pre><code>addEventListener('click', function (event) {
if (event.target.dataset?.back != null) {
event.preventDefault()
document.documentElement.dataset.animateRestore = true
window.history.back()
}
})
addEventListener('turn:enter', function (event) {
document.documentElement.removeAttribute('data-animate-restore')
})</code></pre>
<hr>
<p>To style the animations, we use the <code style="font-size: 0.875em">data-animate-restore</code> attribute to apply the standard animations.</p>
<pre><code>html[data-animate-restore].turn-restore.turn-exit [data-turn-exit],
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html[data-animate-restore].turn-restore.turn-enter [data-turn-enter],
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}</code></pre>
</main>
</body>
</html>
================================================
FILE: examples/animate-restore/one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animate Restore</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
addEventListener('turbo:click', function (event) {
if (event.target.dataset?.back != null) {
event.detail.originalEvent.preventDefault()
event.preventDefault()
document.documentElement.dataset.animateRestore = true
window.history.back()
}
})
addEventListener('turn:enter', function (event) {
document.documentElement.removeAttribute('data-animate-restore')
})
</script>
</head>
<body>
<nav>
<a href="index.html" data-back>Back</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Lorem</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/animate-restore/turn.css
================================================
[data-turn-exit],
[data-turn-enter] {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-fill-mode: forwards;
}
html[data-animate-restore].turn-restore.turn-exit [data-turn-exit],
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html[data-animate-restore].turn-restore.turn-enter [data-turn-enter],
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
@keyframes fade-out-up {
0% {
opacity: 1;
transform: translateZ(0)
}
100% {
opacity: 0;
transform: translate3d(0, -4rem, 0)
}
}
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translate3d(0, 4rem, 0)
}
100% {
opacity: 1;
transform: translateZ(0)
}
}
================================================
FILE: examples/animations-only/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html" aria-current>Turn</a>
<a href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn/blob/main/examples/animations-only/">Source</a>
</nav>
<main data-turbo="false" data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Turn: Basic</h1>
<div class="timelines">
<div class="animation-timeline" style="grid-template-columns: 1fr 1fr">
<div class="animation animation-exit">Exit</div>
<div class="animation animation-enter">Enter</div>
</div>
</div>
<p>This demonstrates page animations with <a href="https://github.com/domchristie/turn">Turn</a> (without View Transitions). Tap the Turn/Lorem links in the navigation bar to view.</p>
<p><a href="../view-transitions-only">Next example</a> or <a href="../../">back to examples</a></p>
<pre><code>/* Set up animation timings for targeted elements */
[data-turn-exit],
[data-turn-enter] {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-fill-mode: forwards;
}
/* Apply exit animations */
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
/* Apply enter animations */
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
/* For smoothness */
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
/* (Define animation keyframes here) */</code></pre>
</main>
</body>
</html>
================================================
FILE: examples/animations-only/one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html">Turn</a>
<a aria-current href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Lorem</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/animations-only/turn.css
================================================
/* Temporary fix for https: //github.com/domchristie/turn/issues/13 */
.turn-view-transitions.turn-transition [data-turn-enter],
.turn-view-transitions.turn-before-transition [data-turn-enter] {
opacity: 0;
}
[data-turn-exit],
[data-turn-enter] {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-fill-mode: forwards;
}
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
@keyframes fade-out-up {
0% {
opacity: 1;
transform: translateZ(0)
}
100% {
opacity: 0;
transform: translate3d(0, -4rem, 0)
}
}
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translate3d(0, 4rem, 0)
}
100% {
opacity: 1;
transform: translateZ(0)
}
}
================================================
FILE: examples/dynamic-view-transitions/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
<script src="script.js"></script>
</head>
<body>
<nav>
<a href="index.html" aria-current>Videos</a>
<a href="https://github.com/domchristie/turn/blob/main/examples/view-transitions-only/">Source</a>
</nav>
<main>
<h1>Turn: Dynamic Names</h1>
<ul class="videos">
<li>
<a
href="one.html"
style="font-family: system-ui; font-weight: 800; display: block"
id="video_1_title"
data-transition="video_1 video_1_title"
data-view-transition-name="title">Big Buck Bunny</a>
<video
controls
src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
poster="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg"
id="video_1"
data-turbo-permanent
data-view-transition-name="video"></video>
</li>
<li>
<a
href="two.html"
id="video_2_title"
style="font-family: system-ui; font-weight: 800; display: block"
data-transition="video_2 video_2_title"
data-view-transition-name="title">Elephants Dream</a>
<video
controls
src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"
poster="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg"
id="video_2"
data-turbo-permanent
data-view-transition-name="video"></video>
</li>
</ul>
<form action="one.html" method="get" data-transition="video_1 video_1_title"><input type="submit"></form>
<p hidden id="no-view-transitions"><em>Your browser does not support View Transitions, so you'll not see any animations.</em></p>
</main>
</body>
</html>
================================================
FILE: examples/dynamic-view-transitions/one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
<script src="script.js"></script>
</head>
<body>
<nav>
<a href="index.html" data-transition="video_1 video_1_title">Videos</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main>
<h1 id="video_1_title" data-view-transition-name="title">Big Buck Bunny</h1>
<video id="video_1" data-turbo-permanent data-view-transition-name="video" controls src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" poster="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg"></video>
</main>
</body>
</html>
================================================
FILE: examples/dynamic-view-transitions/script.js
================================================
window.addEventListener('turn:before-transition', function ({ detail }) {
let { referrer, action, initiator, newBody } = detail
// For restoration visits, make a _reasonable_ guess at which link/form might have
// visited the current page (`referrer`), then apply those names.
if (action === 'restore') {
const selector = 'a[data-transition], form[data-transition]'
initiator = [...newBody.querySelectorAll(selector)].find(
i => i.href === referrer || i.action === referrer
) || document.documentElement
} else {
reset()
}
apply(initiator)
apply(initiator, newBody)
})
function apply (initiator, body = document.body) {
if (!initiator.dataset.transition) return
const viewTransitionIds = initiator.dataset.transition.split(' ')
viewTransitionIds.forEach(function (id) {
const element = body.querySelector(`[id='${id}']`)
if (element) {
element.style.viewTransitionName = element.dataset.viewTransitionName
}
})
}
function reset () {
document.querySelectorAll('[data-view-transition-name]').forEach(function (e) {
e.style.viewTransitionName = ''
})
}
================================================
FILE: examples/dynamic-view-transitions/two.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
<script src="script.js"></script>
</head>
<body>
<nav>
<a href="index.html" data-transition="video_2 video_2_title">Videos</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main>
<h1 id="video_2_title" data-view-transition-name="title">Elephants Dream</h1>
<video id="video_2" data-turbo-permanent data-view-transition-name="video" controls src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4" poster="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg"></video>
</main>
</body>
</html>
================================================
FILE: examples/fallback/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html" aria-current>Turn</a>
<a href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn/blob/main/examples/fallback/">Source</a>
</nav>
<main data-turbo="false" data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Turn: Fallback</h1>
<div class="timelines">
<div class="animation-timeline" style="grid-template-columns: 1fr">
<div class="animation animation-transition">View Transition</div>
</div>
<div class="or">or</div>
<div class="animation-timeline" style="grid-template-columns: 1fr 1fr">
<div class="animation animation-exit">Exit</div>
<div class="animation animation-enter">Enter</div>
</div>
</div>
<p>This demonstrates how <a href="https://github.com/domchristie/turn">Turn</a> can be used with View Transitions, falling back to custom CSS animations if not supported.</p>
<p><a href="../hybrid-basic/">Next example</a> or <a href="../../">back to examples</a></p>
<pre><code>/* (Excerpt) */
/* Scope animations with `html.turn-no-view-transitions` */
html.turn-no-view-transitions.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html.turn-no-view-transitions.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}</code></pre>
<hr>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/fallback/one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html">Turn</a>
<a aria-current href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Lorem</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/fallback/turn.css
================================================
[data-turn-exit],
[data-turn-enter] {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-fill-mode: forwards;
}
html.turn-no-view-transitions.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html.turn-no-view-transitions.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
html.turn-no-view-transitions.turn-before-exit [data-turn-exit],
html.turn-no-view-transitions.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
@keyframes fade-out-up {
0% {
opacity: 1;
transform: translateZ(0)
}
100% {
opacity: 0;
transform: translate3d(0, -4rem, 0)
}
}
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translate3d(0, 4rem, 0)
}
100% {
opacity: 1;
transform: translateZ(0)
}
}
================================================
FILE: examples/hybrid-advanced/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html" aria-current>Turn</a>
<a href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn/blob/main/examples/hybrid-advanced/">Source</a>
</nav>
<main data-turbo="false" data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Turn: Hybrid Advanced</h1>
<div class="timelines">
<div class="animation-timeline" style="grid-template-columns: 1fr 1fr">
<div class="animation animation-exit">Exit</div>
<div class="animation animation-transition">View Transition</div>
</div>
<div class="or">or</div>
<div class="animation-timeline" style="grid-template-columns: 1fr 1fr">
<div class="animation animation-exit">Exit</div>
<div class="animation animation-enter">Enter</div>
</div>
</div>
<p>This is similar to the <a href="../hybrid-basic/">Hybrid Basic example</a>, but instead of waiting for the View Transition to complete, the transition handles enter animations. </p>
<p><a href="../../">Back to Examples</a></p>
<pre><code>/* (Excerpt) */
/* Apply exit animations */
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
/* Only animate enter when View Transitions not supported */
html.turn-no-view-transitions.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
/* Apply enter animations to new View Transition elements */
html.turn-advance::view-transition-new(root),
html.turn-advance::view-transition-new(heading) {
animation-name: fade-in-up;
animation-duration: .6s;
}</code></pre>
<hr>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/hybrid-advanced/one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html">Turn</a>
<a aria-current href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Lorem</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/hybrid-advanced/turn.css
================================================
[data-turn-exit],
[data-turn-enter] {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-fill-mode: forwards;
}
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html.turn-no-view-transitions.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
html.turn-advance::view-transition-new(root),
html.turn-advance::view-transition-new(heading) {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-name: fade-in-up;
animation-duration: .6s;
}
html.turn-advance.turn-transition::view-transition-new(heading) {
--offset: 8rem;
}
html.turn-advance.turn-before-transition [data-turn-exit],
html.turn-no-view-transitions.turn-advance.turn-transition [data-turn-exit] {
opacity: 0;
}
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
@keyframes fade-out-up {
0% {
opacity: 1;
transform: translateZ(0)
}
100% {
opacity: 0;
transform: translate3d(0, calc(var(--offset, 4rem) * -1), 0)
}
}
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translate3d(0, var(--offset, 4rem), 0)
}
100% {
opacity: 1;
transform: translateZ(0)
}
}
================================================
FILE: examples/hybrid-basic/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html" aria-current>Turn</a>
<a href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn/blob/main/examples/hybrid-basic/">Source</a>
</nav>
<main data-turbo="false" data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Turn: Hybrid Basic</h1>
<div class="animation-timeline" style="grid-template-columns: 1fr 1fr 1fr">
<div class="animation animation-exit">Exit</div>
<div class="animation animation-transition">(View Transition)</div>
<div class="animation animation-enter">Enter</div>
</div>
<p>This demonstrates using both View Transitions and standard CSS animations. Standard animations are used for exit and enter, whereas View Transitions are used to transition the nav. Exit animations are applied immediately, then once the new page has loaded, it waits for the View Transition to complete, before performing the enter animation. Browsers that do not support View Transitions will still see the exit/enter animations, but the nav won't be transitioned.</p>
<p><a href="../hybrid-advanced/">Next example</a> or <a href="../../">back to examples</a></p>
<pre><code>/* (Excerpt) */
/* Apply exit/enter animations as before */
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
/* Hide flash of content during transition */
html.turn-advance.turn-before-transition [data-turn-exit],
html.turn-advance.turn-transition [data-turn-exit] {
opacity: 0;
}</code></pre>
<hr>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/hybrid-basic/one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="./turn.css">
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html">Turn</a>
<a aria-current href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main data-turn-enter data-turn-exit>
<h1 data-turn-enter data-turn-exit>Lorem</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/hybrid-basic/turn.css
================================================
[data-turn-exit],
[data-turn-enter] {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-fill-mode: forwards;
}
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
html.turn-advance.turn-before-transition [data-turn-exit],
html.turn-advance.turn-transition [data-turn-exit] {
opacity: 0;
}
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
@keyframes fade-out-up {
0% {
opacity: 1;
transform: translateZ(0)
}
100% {
opacity: 0;
transform: translate3d(0, -4rem, 0)
}
}
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translate3d(0, 4rem, 0)
}
100% {
opacity: 1;
transform: translateZ(0)
}
}
================================================
FILE: examples/theme.css
================================================
/* ========= */
/* = Theme = */
/* ========= */
/* @link https://utopia.fyi/type/calculator?c=320,18,1.2,1240,20,1.25,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
:root {
--step--2: clamp(0.78rem, calc(0.77rem + 0.03vw), 0.80rem);
--step--1: clamp(0.94rem, calc(0.92rem + 0.11vw), 1.00rem);
--step-0: clamp(1.13rem, calc(1.08rem + 0.22vw), 1.25rem);
--step-1: clamp(1.35rem, calc(1.28rem + 0.37vw), 1.56rem);
--step-2: clamp(1.62rem, calc(1.50rem + 0.58vw), 1.95rem);
--step-3: clamp(1.94rem, calc(1.77rem + 0.87vw), 2.44rem);
--step-4: clamp(2.33rem, calc(2.08rem + 1.25vw), 3.05rem);
--step-5: clamp(2.80rem, calc(2.45rem + 1.77vw), 3.82rem);
}
body {
margin: 0 auto 2.25em 0;
font-family: Charter, 'Bitstream Charter', 'Sitka Text', Cambria, serif;
background-color: white;
font-size: var(--step-1);
color: black;
line-height: 1.5;
background: #f9f9f9;
}
ol {
margin: 1em 0;
padding-left: 1.25em;
}
ol li + li {
margin-top: 0.5em;
}
a {
color: #06f;
}
nav {
display: flex;
background-color: white;
border-bottom: 2px solid #e6e6e6;
padding: 1rem 2.5%;
position: relative;
z-index: 10;
font-family: system-ui, sans-serif;
contain: layout;
view-transition-name: nav;
}
nav a {
position: relative;
display: block;
font-weight: bold;
text-decoration: none;
}
nav a[aria-current] {
color: black;
}
nav a[aria-current]::after {
content: "";
position: absolute;
width: 100%;
height: 4px;
background-color: black;
left: 0;
right: 0;
bottom: -6px;
contain: layout;
view-transition-name: current;
}
nav a + a {
margin-left: 2.5%;
}
nav a:last-child {
margin-left: auto;
}
main {
width: 95%;
max-width: 48rem;
margin: 0 auto;
contain: layout;
}
h1 {
margin: 0.667em 0;
font-family: system-ui, sans-serif;
font-size: var(--step-5);
font-weight: 800;
color: black;
line-height: 1;
contain: layout;
view-transition-name: heading;
}
h2 {
font-size: var(--step-2);
margin: 0.667em 0;
font-family: system-ui, sans-serif;
}
p {
line-height: 1.4;
margin: 1em 0;
}
pre {
width: 100%;
overflow-x: auto;
font-size: var(--step-0);
}
code {
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
}
.timelines {
text-align: center;
}
.animation-timeline {
display: grid;
}
.animation {
font-family: system-ui, sans-serif;
font-weight: 700;
text-transform: uppercase;
font-size: var(--step--1);
padding: 0.25rem 0.5rem;
color: white;
letter-spacing: 0.05em;
}
.animation-exit {
text-align: left;
background-image: linear-gradient(to right, black, white);
}
.animation-enter {
text-align: right;
background-image: linear-gradient(to left, black, white);
}
.animation-transition {
text-align: center;
color: black;
background-image: linear-gradient(to left, black, white, white, black);
}
.or {
font-family: system-ui, sans-serif;
font-size: var(--step--1);
font-weight: 600;
text-transform: uppercase;
}
html.turn-no-view-transitions #no-view-transitions {
color: darkred;
display: block;
}
.videos {
margin: 0;
padding: 0;
list-style: none;
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--step--2);
}
video {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
contain: layout;
}
::view-transition-old(title) {
display: none;
}
::view-transition-new(title) {
animation: none;
}
::view-transition-old(video) {
display: none;
}
::view-transition-new(video) {
animation: none;
}
================================================
FILE: examples/view-transitions-only/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html" aria-current>Turn</a>
<a href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn/blob/main/examples/view-transitions-only/">Source</a>
</nav>
<main data-turbo="false">
<h1>Turn: View Transitions</h1>
<div class="timelines">
<div class="animation-timeline" style="grid-template-columns: 1fr">
<div class="animation animation-transition">View Transition</div>
</div>
</div>
<p>This demonstrates animated page navigations with View Transitions and <a href="https://github.com/domchristie/turn">Turn</a>. Tap the Turn/Lorem links in the navigation bar to view. No custom CSS is required for the default animations.</p>
<p hidden id="no-view-transitions"><em>Your browser does not support View Transitions, so you'll not see any animations.</em></p>
<p><a href="../fallback/">Next example</a> or <a href="../../">back to examples</a></p>
<hr>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: examples/view-transitions-only/one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn Demo: Animations Only</title>
<link rel="stylesheet" href="../theme.css">
<script src="https://unpkg.com/@hotwired/turbo"></script>
<script type="module">
import Turn from '../../src/turn.js'
Turn.start()
</script>
</head>
<body>
<nav>
<a href="index.html">Turn</a>
<a aria-current href="one.html">Lorem</a>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main>
<h1>Lorem</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</main>
</body>
</html>
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Turn: Animate Page Transitions in Turbo Drive Apps</title>
<link rel="stylesheet" href="./examples/theme.css">
</head>
<body>
<nav>
<a href="https://github.com/domchristie/turn">Source</a>
</nav>
<main>
<h1>Turn</h1>
<p>Turn is a library for animating page transitions in Turbo Drive apps. It applies various class names to the HTML element during the lifecycle of a navigation. Targeted elements can then use these classes to apply animations. It supports View Transitions and can be used with a combination of the two approches.</p>
<h2>Examples</h2>
<ol>
<li><a href="./examples/animations-only/">Basic</a>: immediate exit animations, followed by enter animations (without View Transitions)</li>
<li><a href="./examples/view-transitions-only/">View Transitions</a>: transitions common elements between pages (where supported, without a fallback)</li>
<li><a href="./examples/fallback/">Fallback</a>: animate with View Transitions, with a fallback to custom exit/enter animations</li>
<li><a href="./examples/hybrid-basic/">Hybrid Basic</a>: combines custom CSS exit and enter animations with View Transitions for common elements between pages</li>
<li><a href="./examples/hybrid-advanced/">Hybrid Advanced</a>: similar to the basic example but only uses a custom CSS exit animation; the enter animations are achieved with a View Transition</li>
<li><a href="./examples/animate-restore/">Animate Restore</a>: animating restoration visits from native controls is generally discouraged, but if your app has Back links, this approach provides a nice user experience</li>
</ol>
<!-- <p><a href="https://github.com/domchristie/turn">Turn</a> is a JavaScript library for animating page navigations in <a href="https://turbo.hotwired.dev/">Turbo Drive</a> applications. It works both with and without <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">View Transitions</a>, or and you can use a combination of View Transitions and custom CSS animations.</p> -->
</main>
</body>
</html>
================================================
FILE: one.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./examples/theme.css">
<meta http-equiv="refresh" content="0; url=./">
</head>
<body>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "@domchristie/turn",
"version": "3.1.1",
"type": "module",
"description": "Animate page transitions in Turbo Drive apps.",
"main": "dist/turn.min.js",
"files": [
"dist"
],
"scripts": {
"build": "rollup --config && cp turn.css dist/turn.css",
"prepare": "npm run build",
"start": "rollup --config --watch",
"test": "npx standard"
},
"repository": {
"type": "git",
"url": "git+https://github.com/domchristie/turn.git"
},
"keywords": [
"hotwire",
"turbo",
"animations"
],
"author": "Dom Christie",
"license": "MIT",
"bugs": {
"url": "https://github.com/domchristie/turn/issues"
},
"homepage": "https://github.com/domchristie/turn#readme",
"devDependencies": {
"rollup": "^2.79.1",
"rollup-plugin-terser": "^7.0.2",
"standard": "^17.0.0"
},
"peerDependencies": {
"@hotwired/turbo": "^7"
}
}
================================================
FILE: rollup.config.js
================================================
import { terser } from 'rollup-plugin-terser'
export default {
input: 'src/turn.js',
output: [
{
file: 'dist/turn.js'
},
{
file: 'dist/turn.min.js',
plugins: [terser()]
}
]
}
================================================
FILE: src/animation-turn.js
================================================
import Animations from './animations.js'
import BaseTurn from './base-turn.js'
export default class AnimationTurn extends BaseTurn {
static supported = true
exit (detail) {
const exitAnimations = new Animations('[data-turn-exit]')
let resolveExit
this.animateOut = Promise.all([
exitAnimations.ended,
new Promise((resolve) => { resolveExit = resolve })
])
this.addClasses('before-exit')
this.dispatch('before-exit', { detail })
window.requestAnimationFrame(() => {
exitAnimations.start(() => {
this.addClasses(this.direction)
this.addClasses('exit')
})
this.removeClasses('before-exit')
resolveExit()
})
}
async beforeEnter (detail) {
await this.animateOut
this.removeClasses('exit')
if (this.animateIn) await this.animateIn // only present on post-preview enters
else {
this.dispatch('before-enter', {
detail: { ...detail, action: this.action }
})
}
}
enter () {
const enterAnimations = new Animations('[data-turn-enter]')
this.animateIn = enterAnimations.ended
enterAnimations.start(() => this.addClasses('enter'))
return this.animateIn
}
async complete (detail) {
this.removeClasses('enter')
this.removeClasses(this.direction)
this.dispatch('enter', { detail: { ...detail, action: this.action } })
}
abort () {
this.removeClasses('before-exit')
this.removeClasses('exit')
this.removeClasses('enter')
this.removeClasses(this.direction)
}
get finished () {
return this.animateIn
}
}
================================================
FILE: src/animations.js
================================================
import { animationsEnd } from './helpers.js'
export default class Animations {
constructor (selector) {
this.selector = selector
this.ended = new Promise((resolve) => { this.resolve = resolve })
}
async start (applyAnimations) {
applyAnimations()
const elements = [...document.querySelectorAll(this.selector)]
if (elements.every((e) => e.getAnimations().length)) {
await animationsEnd(this.selector)
this.resolve()
} else {
this.resolve()
}
}
}
================================================
FILE: src/base-turn.js
================================================
import { camelCase, pascalCase } from './helpers.js'
const DEFAULT_OPTIONS = {
animateRestore: false
}
export default class BaseTurn {
constructor (action, direction = 'none', options = {}) {
this.action = action
this.direction = direction
this.options = { ...DEFAULT_OPTIONS, ...options }
this.beforeExitClasses = new Set()
this.exitClasses = new Set()
this.enterClasses = new Set()
this.beforeTransitionClasses = new Set()
this.transitionClasses = new Set()
this.forwardClasses = new Set()
this.backClasses = new Set()
this.noneClasses = new Set()
}
addClasses (type) {
document.documentElement.classList.add(`turn-${type}`)
Array.from(document.querySelectorAll(`[data-turn-${type}]`)).forEach((element) => {
element.dataset[`turn${pascalCase(type)}`].split(/\s+/).forEach((klass) => {
if (klass) {
element.classList.add(klass)
this[`${camelCase(type)}Classes`].add(klass)
}
})
})
}
removeClasses (type) {
document.documentElement.classList.remove(`turn-${type}`)
Array.from(document.querySelectorAll(`[data-turn-${type}]`)).forEach((element) => {
this[`${camelCase(type)}Classes`].forEach((klass) => element.classList.remove(klass))
})
}
dispatch (eventName, { target = document, detail = {}, bubbles = true, cancelable = true } = {}) {
const type = `turn:${eventName}`
const event = new window.CustomEvent(type, { detail, bubbles, cancelable })
target.dispatchEvent(event)
return event
}
}
================================================
FILE: src/controller.js
================================================
import NullTurn from './null-turn.js'
import AnimationTurn from './animation-turn.js'
import ViewTransitionTurn from './view-transition-turn.js'
const VIEW_TRANSITIONS = 'turn-view-transitions'
const NO_VIEW_TRANSITIONS = 'turn-no-view-transitions'
const ACTIONS = ['advance', 'restore', 'replace']
export default class Controller {
constructor (config) {
this.config = config
}
start () {
this.animationTurn = new NullTurn()
this.viewTransitionTurn = new NullTurn()
addSupportClass(this.config)
this.currentUrl = window.location.toString()
}
stop () {
this.animationTurn.abort()
this.viewTransitionTurn.abort()
this.animationTurn = new NullTurn()
this.viewTransitionTurn = new NullTurn()
removeSupportClasses()
delete this.initiator
delete this.currentUrl
}
click (event) {
this.initiator = event.target
}
submitStart (event) {
this.initiator = event.target
}
visit (event) {
this.reset(event)
this.animationTurn = create(AnimationTurn, event.detail.action, event.detail.direction)
this.viewTransitionTurn = create(ViewTransitionTurn, event.detail.action, event.detail.direction)
this.animationTurn.exit({
...event.detail,
referrer: this.currentUrl,
initiator: this.initiator
})
}
async beforeRender (event) {
event.preventDefault()
const detail = {
newBody: event.detail.newBody,
referrer: this.currentUrl,
initiator: this.initiator
}
await this.animationTurn.beforeEnter(detail)
this.hasPreview
? await this.viewTransitionTurn.finished
: await this.viewTransitionTurn.beforeEnter(detail)
if (this.isPreview) this.hasPreview = true
event.detail.resume()
}
render () {
this.currentUrl = window.location.toString()
delete this.initiator
const isInitialRender = this.isPreview || !this.hasPreview
if (isInitialRender) {
this._render = this.viewTransitionTurn.enter()
.then(() => this.animationTurn.enter())
}
}
async load (event) {
await this._render
removeActionClasses()
this.animationTurn.complete({
...event.detail,
referrer: this.currentUrl
})
}
popstate (event) {
const fixNonRestoreBack = this.animationTurn.action !== 'restore'
fixNonRestoreBack && this.animationTurn.abort()
}
get isPreview () {
return document.documentElement.hasAttribute('data-turbo-preview')
}
reset (event) {
removeActionClasses()
addActionClass(event.detail.action)
this.hasPreview = undefined
this._render = undefined
this.animationTurn.abort()
this.viewTransitionTurn.abort()
if (event.detail.action === 'restore' || !this.initiator) {
this.initiator = document.documentElement
}
}
}
function addSupportClass (config) {
document.documentElement.classList.add(
ViewTransitionTurn.supported ? VIEW_TRANSITIONS : NO_VIEW_TRANSITIONS
)
}
function removeSupportClasses () {
document.documentElement.classList.remove(
ViewTransitionTurn.supported
? VIEW_TRANSITIONS
: NO_VIEW_TRANSITIONS
)
}
function addActionClass (action) {
document.documentElement.classList.add(`turn-${action}`)
}
function removeActionClasses () {
const classList = document.documentElement.classList
classList.remove.apply(classList, ACTIONS.map(a => `turn-${a}`))
}
function create (Klass, action, direction) {
if (!Klass.supported || document.body.dataset.turn === 'false') {
Klass = NullTurn
}
const options = JSON.parse(document.body.dataset.turnOptions || '{}')
return new Klass(action, direction, options)
}
================================================
FILE: src/helpers.js
================================================
export function prefersReducedMotion () {
if (typeof window !== 'undefined') {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
return !mediaQuery || mediaQuery.matches
} else {
return true
}
}
export function motionSafe () {
return !prefersReducedMotion()
}
export function animationsEnd (selector) {
const elements = [...document.querySelectorAll(selector)]
return Promise.all(elements.map((element) => {
return new Promise((resolve) => {
function listener () {
element.removeEventListener('animationend', listener)
resolve()
}
element.addEventListener('animationend', listener)
})
}))
}
export function pascalCase (string) {
return string.split(/[^\w]/).map(capitalize).join('')
}
export function camelCase (string) {
return string.split(/[^\w]/).map(
(w, i) => i === 0 ? w.toLowerCase() : capitalize(w)
).join('')
}
function capitalize (string) {
return string.replace(/^\w/, (c) => c.toUpperCase())
}
================================================
FILE: src/null-turn.js
================================================
export default class NullTurn {
static supported = true
direction = 'none'
exit () {}
async beforeEnter () {}
async enter () {}
complete () {}
abort () {}
finished = Promise.resolve()
}
================================================
FILE: src/turn.js
================================================
import { motionSafe } from './helpers.js'
import Controller from './controller.js'
const Turn = {
start () {
if (!this.started && motionSafe()) {
for (const event in eventListeners) {
window.addEventListener(event, eventListeners[event])
}
this.controller = new Controller(Turn.config)
this.controller.start()
this.started = true
}
},
stop () {
if (this.started) {
for (const event in eventListeners) {
window.removeEventListener(event, eventListeners[event])
}
this.controller.stop()
this.started = false
}
},
config: {
experimental: {
viewTransitions: true
}
}
}
const eventListeners = {
'turbo:click': function (event) {
this.controller.click(event)
}.bind(Turn),
'turbo:visit': function (event) {
this.controller.visit(event)
}.bind(Turn),
'turbo:submit-start': function (event) {
this.controller.submitStart(event)
}.bind(Turn),
'turbo:before-render': async function (event) {
this.controller.beforeRender(event)
}.bind(Turn),
'turbo:render': async function () {
this.controller.render()
}.bind(Turn),
'turbo:load': async function (event) {
this.controller.load(event)
}.bind(Turn),
popstate: function () {
this.controller.popstate()
}.bind(Turn)
}
export default Turn
================================================
FILE: src/view-transition-turn.js
================================================
import BaseTurn from './base-turn.js'
export default class ViewTransitionTurn extends BaseTurn {
static supported = !!document.startViewTransition
prepare () {
this.snapshot = new Promise(resolve => { this.snapshat = resolve })
this.transition = document.startViewTransition(_ => this.render())
return this.snapshot
}
exit () {}
async beforeEnter (detail) {
this.addClasses('before-transition')
this.dispatch('before-transition', {
detail: { ...detail, action: this.action }
})
await this.prepare()
}
render () {
this.snapshat()
return new Promise(resolve => { this.rendered = resolve })
}
async enter () {
this.rendered()
this.removeClasses('before-transition')
this.addClasses('transition')
await this.finished
await Promise.resolve() // next tick
this.removeClasses('transition')
}
complete () {}
abort () {
this.removeClasses('transition')
}
rendered () {}
get finished () {
return this.transition?.finished
}
}
================================================
FILE: turn.css
================================================
[data-turn-exit],
[data-turn-enter] {
animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);
animation-fill-mode: forwards;
}
html.turn-advance.turn-exit [data-turn-exit] {
animation-name: fade-out-up;
animation-duration: .3s;
}
html.turn-advance.turn-enter [data-turn-enter] {
animation-name: fade-in-up;
animation-duration: .6s;
}
html.turn-advance.turn-before-transition [data-turn-exit],
html.turn-advance.turn-transition [data-turn-exit] {
opacity: 0;
}
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
@keyframes fade-out-up {
0% {
opacity: 1;
transform: translateZ(0)
}
100% {
opacity: 0;
transform: translate3d(0, -4rem, 0)
}
}
@keyframes fade-in-up {
0% {
opacity: 0;
transform: translate3d(0, 4rem, 0)
}
100% {
opacity: 1;
transform: translateZ(0)
}
}
gitextract_h2hckinu/ ├── .gitignore ├── LICENSE ├── README.md ├── examples/ │ ├── animate-restore/ │ │ ├── index.html │ │ ├── one.html │ │ └── turn.css │ ├── animations-only/ │ │ ├── index.html │ │ ├── one.html │ │ └── turn.css │ ├── dynamic-view-transitions/ │ │ ├── index.html │ │ ├── one.html │ │ ├── script.js │ │ └── two.html │ ├── fallback/ │ │ ├── index.html │ │ ├── one.html │ │ └── turn.css │ ├── hybrid-advanced/ │ │ ├── index.html │ │ ├── one.html │ │ └── turn.css │ ├── hybrid-basic/ │ │ ├── index.html │ │ ├── one.html │ │ └── turn.css │ ├── theme.css │ └── view-transitions-only/ │ ├── index.html │ └── one.html ├── index.html ├── one.html ├── package.json ├── rollup.config.js ├── src/ │ ├── animation-turn.js │ ├── animations.js │ ├── base-turn.js │ ├── controller.js │ ├── helpers.js │ ├── null-turn.js │ ├── turn.js │ └── view-transition-turn.js └── turn.css
SYMBOL INDEX (63 symbols across 9 files)
FILE: examples/dynamic-view-transitions/script.js
function apply (line 19) | function apply (initiator, body = document.body) {
function reset (line 32) | function reset () {
FILE: src/animation-turn.js
class AnimationTurn (line 4) | class AnimationTurn extends BaseTurn {
method exit (line 7) | exit (detail) {
method beforeEnter (line 29) | async beforeEnter (detail) {
method enter (line 40) | enter () {
method complete (line 47) | async complete (detail) {
method abort (line 53) | abort () {
method finished (line 60) | get finished () {
FILE: src/animations.js
class Animations (line 4) | class Animations {
method constructor (line 5) | constructor (selector) {
method start (line 10) | async start (applyAnimations) {
FILE: src/base-turn.js
constant DEFAULT_OPTIONS (line 3) | const DEFAULT_OPTIONS = {
class BaseTurn (line 7) | class BaseTurn {
method constructor (line 8) | constructor (action, direction = 'none', options = {}) {
method addClasses (line 22) | addClasses (type) {
method removeClasses (line 35) | removeClasses (type) {
method dispatch (line 43) | dispatch (eventName, { target = document, detail = {}, bubbles = true,...
FILE: src/controller.js
constant VIEW_TRANSITIONS (line 5) | const VIEW_TRANSITIONS = 'turn-view-transitions'
constant NO_VIEW_TRANSITIONS (line 6) | const NO_VIEW_TRANSITIONS = 'turn-no-view-transitions'
constant ACTIONS (line 7) | const ACTIONS = ['advance', 'restore', 'replace']
class Controller (line 9) | class Controller {
method constructor (line 10) | constructor (config) {
method start (line 14) | start () {
method stop (line 21) | stop () {
method click (line 31) | click (event) {
method submitStart (line 35) | submitStart (event) {
method visit (line 39) | visit (event) {
method beforeRender (line 52) | async beforeRender (event) {
method render (line 70) | render () {
method load (line 81) | async load (event) {
method popstate (line 90) | popstate (event) {
method isPreview (line 95) | get isPreview () {
method reset (line 99) | reset (event) {
function addSupportClass (line 112) | function addSupportClass (config) {
function removeSupportClasses (line 118) | function removeSupportClasses () {
function addActionClass (line 126) | function addActionClass (action) {
function removeActionClasses (line 130) | function removeActionClasses () {
function create (line 135) | function create (Klass, action, direction) {
FILE: src/helpers.js
function prefersReducedMotion (line 1) | function prefersReducedMotion () {
function motionSafe (line 10) | function motionSafe () {
function animationsEnd (line 14) | function animationsEnd (selector) {
function pascalCase (line 28) | function pascalCase (string) {
function camelCase (line 32) | function camelCase (string) {
function capitalize (line 38) | function capitalize (string) {
FILE: src/null-turn.js
class NullTurn (line 1) | class NullTurn {
method exit (line 4) | exit () {}
method beforeEnter (line 5) | async beforeEnter () {}
method enter (line 6) | async enter () {}
method complete (line 7) | complete () {}
method abort (line 8) | abort () {}
FILE: src/turn.js
method start (line 5) | start () {
method stop (line 16) | stop () {
FILE: src/view-transition-turn.js
class ViewTransitionTurn (line 3) | class ViewTransitionTurn extends BaseTurn {
method prepare (line 6) | prepare () {
method exit (line 12) | exit () {}
method beforeEnter (line 14) | async beforeEnter (detail) {
method render (line 22) | render () {
method enter (line 27) | async enter () {
method complete (line 36) | complete () {}
method abort (line 38) | abort () {
method rendered (line 42) | rendered () {}
method finished (line 44) | get finished () {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (76K chars).
[
{
"path": ".gitignore",
"chars": 28,
"preview": ".DS_Store\ndist\nnode_modules\n"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2021 Dom Christie\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 8695,
"preview": "# Turn\nAnimate page transitions in [Turbo Drive](https://turbo.hotwired.dev/) apps.\n\n## Installation\nInstall [@hotwired/"
},
{
"path": "examples/animate-restore/index.html",
"chars": 3036,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/animate-restore/one.html",
"chars": 2549,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/animate-restore/turn.css",
"chars": 910,
"preview": "[data-turn-exit],\n[data-turn-enter] {\n animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);\n animation-fill-m"
},
{
"path": "examples/animations-only/index.html",
"chars": 2042,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/animations-only/one.html",
"chars": 2159,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/animations-only/turn.css",
"chars": 984,
"preview": "/* Temporary fix for https: //github.com/domchristie/turn/issues/13 */\n.turn-view-transitions.turn-transition [data-turn"
},
{
"path": "examples/dynamic-view-transitions/index.html",
"chars": 2262,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/dynamic-view-transitions/one.html",
"chars": 1044,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/dynamic-view-transitions/script.js",
"chars": 1128,
"preview": "window.addEventListener('turn:before-transition', function ({ detail }) {\n let { referrer, action, initiator, newBody }"
},
{
"path": "examples/dynamic-view-transitions/two.html",
"chars": 1049,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/fallback/index.html",
"chars": 3308,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/fallback/one.html",
"chars": 2159,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/fallback/turn.css",
"chars": 872,
"preview": "[data-turn-exit],\n[data-turn-enter] {\n animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);\n animation-fill-m"
},
{
"path": "examples/hybrid-advanced/index.html",
"chars": 3568,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/hybrid-advanced/one.html",
"chars": 2159,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/hybrid-advanced/turn.css",
"chars": 1298,
"preview": "[data-turn-exit],\n[data-turn-enter] {\n animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);\n animation-fill-m"
},
{
"path": "examples/hybrid-basic/index.html",
"chars": 3564,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/hybrid-basic/one.html",
"chars": 2159,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/hybrid-basic/turn.css",
"chars": 901,
"preview": "[data-turn-exit],\n[data-turn-enter] {\n animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);\n animation-fill-m"
},
{
"path": "examples/theme.css",
"chars": 3564,
"preview": "/* ========= */\n/* = Theme = */\n/* ========= */\n\n/* @link https://utopia.fyi/type/calculator?c=320,18,1.2,1240,20,1.25,5"
},
{
"path": "examples/view-transitions-only/index.html",
"chars": 2814,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "examples/view-transitions-only/one.html",
"chars": 2053,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "index.html",
"chars": 2282,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "one.html",
"chars": 329,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
},
{
"path": "package.json",
"chars": 901,
"preview": "{\n \"name\": \"@domchristie/turn\",\n \"version\": \"3.1.1\",\n \"type\": \"module\",\n \"description\": \"Animate page transitions in"
},
{
"path": "rollup.config.js",
"chars": 216,
"preview": "import { terser } from 'rollup-plugin-terser'\n\nexport default {\n input: 'src/turn.js',\n output: [\n {\n file: 'd"
},
{
"path": "src/animation-turn.js",
"chars": 1592,
"preview": "import Animations from './animations.js'\nimport BaseTurn from './base-turn.js'\n\nexport default class AnimationTurn exten"
},
{
"path": "src/animations.js",
"chars": 503,
"preview": "\nimport { animationsEnd } from './helpers.js'\n\nexport default class Animations {\n constructor (selector) {\n this.sel"
},
{
"path": "src/base-turn.js",
"chars": 1557,
"preview": "import { camelCase, pascalCase } from './helpers.js'\n\nconst DEFAULT_OPTIONS = {\n animateRestore: false\n}\n\nexport defaul"
},
{
"path": "src/controller.js",
"chars": 3646,
"preview": "import NullTurn from './null-turn.js'\nimport AnimationTurn from './animation-turn.js'\nimport ViewTransitionTurn from './"
},
{
"path": "src/helpers.js",
"chars": 1016,
"preview": "export function prefersReducedMotion () {\n if (typeof window !== 'undefined') {\n const mediaQuery = window.matchMedi"
},
{
"path": "src/null-turn.js",
"chars": 202,
"preview": "export default class NullTurn {\n static supported = true\n direction = 'none'\n exit () {}\n async beforeEnter () {}\n "
},
{
"path": "src/turn.js",
"chars": 1347,
"preview": "import { motionSafe } from './helpers.js'\nimport Controller from './controller.js'\n\nconst Turn = {\n start () {\n if ("
},
{
"path": "src/view-transition-turn.js",
"chars": 1032,
"preview": "import BaseTurn from './base-turn.js'\n\nexport default class ViewTransitionTurn extends BaseTurn {\n static supported = !"
},
{
"path": "turn.css",
"chars": 901,
"preview": "[data-turn-exit],\n[data-turn-enter] {\n animation-timing-function: cubic-bezier(0.65, 0.05, 0.35, 1);\n animation-fill-m"
}
]
About this extraction
This page contains the full source code of the domchristie/turn GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (69.2 KB), approximately 19.0k tokens, and a symbol index with 63 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.