Animate page transitions in Turbo Drive apps.
Install @hotwired/turbo, then
npm install @domchristie/turn
- Include Turbo,
dist/turn.js
anddist/turn.css
however you build your JavaScript & CSS - Add
data-turn-enter
anddata-turn-exit
to the elements you wish to animate (optional if you're only using View Transitions) import Turn from '@domchristie/turn'
and callTurn.start()
in your application JavaScript- Navigate between pages β¦ β¨
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:
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.
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
.
The following class names are added to the HTML element at various time during a navigation lifecycle.
Added on start if the device supports View Transitions, and View Transitions are enabled.
Added on start if the device does not support View Transitions, or View Transitions are disabled.
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.
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.
Added when a visit starts and the action is replace
. Removed when the navigation has completed and all animations have ended.
Added when a visit starts. Useful for optimising animations with will-change
. (See turn.css
for an example.) Removed when the exit animations start.
Added when a visit starts. Use this to scope exit animations. Removed when the exit animations complete.
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.
Adding during a View Transition. Useful when combining View Transitions with custom exit/enter animations.
Added after any requests have completed and previous animations/transitions have completed. Removed once the animations have completed.
event.details
may contain:
action
: the action of the visit (advance
orrestore
)initiator
: the element the visit was triggered from (ana
,form
, orhtml
element if a Back/Forward navigation)referrer
: the URL the page is transitioning fromurl
: the URL the page is transitioning tonewBody
: the incoming<body>
that will be transitioned to
Dispatched before exit animations are started. event.detail
includes:
action
initiator
referrer
url
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
Dispatched before enter animations are started (after Vire Transitions if present). event.detail
includes:
action
initiator
newBody
referrer
Dispatched after the all transitions and animations have completed. event.detail
includes:
action
referrer
url
timing
: the visit's timing metrics
Define animations in tailwind.config.js
, and add a plugin that scopes the styles, e.g.:
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:
<main data-turn-exit="turn-exit:animate-exit" data-turn-enter="turn-enter:animate-enter">
<!-- β¦ -->
</main>
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.)
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.
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:
<main data-turn-exit data-turn-enter>
<h1 data-turn-exit data-turn-enter>Hello, world!</h1>
</main>
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:
html.turn-before-exit [data-turn-exit],
html.turn-exit [data-turn-exit] {
will-change: transform, opacity;
}
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:
.spinner {
position: fixed;
top: 15%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 100ms;
}
html.turn-exit .spinner {
opacity: 1;
transition-delay: 700ms
}
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
.
Turn adds exit and enter classes at the appropriate times like so:
- on
turbo:visit
add the exit classes - pause
turbo:before-render
(wait for exit animations to complete before resuming) - on
turbo:render
, remove exit classes and add the enter classes - on
turbo:load
, wait for the enter animations to complete before removing the enter classes
Default fade in/out animations adapted from Jon Yablonski's Humane By Design.
Copyright Β© 2021+ Dom Christie and released under the MIT license.