How to effortlessly animate html elements between Turbo page changes
01 Aug 2022
Combine Turbo and el-transition
to easily add nice animation transitions without imperative JavaScript manipulations.
Step 1
Add el-transition
Step 2
Add a JS file to use el-transition
to apply transitions to all incoming and outgoing elemenets with data-transition-enter|leave
data tags.
// app/javascript/src/turbo-animate.js
import { enter , leave } from ' el-transition '
// Animate outgoing and incoming elements between pages
//
// <!-- declare enter and leave anmiations using data attributes -->
// <div id="dropdown-menu" class="menu hidden"
// data-transition-enter="transition ease-out duration-100"
// data-transition-enter-start="transform opacity-0 scale-95"
// data-transition-enter-end="transform opacity-100 scale-100"
// data-transition-leave="transition ease-in duration-75"
// data-transition-leave-start="transform opacity-100 scale-100"
// data-transition-leave-end="transform opacity-0 scale-95"
// >
// <!-- html -->
// </div>
let leavePromises = []
document . addEventListener ( ' turbo:before-fetch-request ' , () => {
disappearAll ()
})
window . addEventListener ( ' popstate ' , () => {
disappearAll ()
})
document . addEventListener ( ' turbo:before-render ' , async ( event ) => {
if ( leavePromises . length == 0 ) return
event . preventDefault ()
await Promise . all ( leavePromises )
// Cleanup and cache page again
document . querySelectorAll ( ' [data-transition-leave] ' ). forEach (( element ) => {
element . classList . remove ( ' hidden ' )
})
Turbo . session . navigator . view . cacheSnapshot ()
event . detail . resume ()
})
document . addEventListener ( ' turbo:render ' , () => {
appearAll ()
})
const disappearAll = () => {
leavePromises = Array . from (
document . querySelectorAll ( ' [data-transition-leave] ' )
). map (( element ) => leave ( element ))
}
const appearAll = () => {
document . querySelectorAll ( ' [data-transition-enter] ' ). forEach (( element ) => {
enter ( element )
})
}
Step 3
Add data-transition-*
markup
<!-- page 1 -->
<%= form_with url: posts_path do | f | %>
<div class= "flex flex-col items-center justify-center"
data-transition-leave= "transition ease-in duration-1000"
data-transition-leave-start= "transform opacity-100 scale-100"
data-transition-leave-end= "transform opacity-0 scale-50"
data-recording-target= "recordedState"
>
<button type= "submit" class= "rounded-full w-40 h-40 flex items-center justify-center font-bold text-xl bg-purple-500 text-white shadow" >
Submit
</button>
</div>
<% end %>
<!-- page 2 -->
<div data-transition-enter= "transition ease-out duration-1000"
data-transition-enter-start= "transform opacity-0 scale-50"
data-transition-enter-end= "transform opacity-100 scale-100"
>
<%= image_tag "illustrations/happy-smiley-with-flowers.png" %>
</div>
Use cases
I find the simplicity of this appoach to have great merits. If combined with Turbo Frames - any small section of the app, or an icon button such as “Favourite” or “Bookmark”, can be nicely animated to pop out and back in as it changes state.
It won’t be difficult to employ the same appraoch to handle animations instead of transitions for more complex effects. See tailwindcss-animate for declarative animation classes.
Hope this helps!
Enjoy! 😊