Home > Article > Web Front-end > Smooth Transitions with Turbo Streams
This article was originally published on Rails Designer
With Turbo Streams you can update specific parts of your app. Inject a chat message, update a profile picture or insert a Report is being created alert.
The preciseness Turbo Streams offers is great. But often the abruptness of it, its not too appealing to me. The new component is just there (or isn't, if you remove it).
I'd like to add a bit more joy to my apps and this technique is something that does just that. I previously explored multiple techniques to add some kind of transition or animation when an element was inserted or removed. I fine-tuned it over the years while using it in production. And I can say I'm happy with how the technique works I am outlining today.
First, this will be the end result:
And this is how it's used:
<%= turbo_stream_action_tag_with_block "prepend", target: "resources", data: {transition_enter: "transition ease-in duration-300", transition_enter_start: "opacity-0", transition_enter_end: "opacity-100"} do %> <%= render ResourceComponent.new(resource: @resource) %> <% end %>
If you are using Rails Designer it is included for you and ready to go. Winning! ?
There are quite a few moving elements here to get it going, but as seen from the code example above, the usage is really clean and customizable.
Let's get started.
The first step is to create the turbo_stream_action_tag_with_block helper. This is needed, because the available turbo_stream_action_tag helper doesn't allow a block to be passed (eg. render CollectionComponent) and the the default Turbo stream Rails helpers don't pass along data-attributes. It's simple enough though:
# app/helpers/turbo_stream_helper.rb module TurboStreamHelper def turbo_stream_action_tag_with_block(action, target: nil, targets: nil, **options, &block) template_content = block_given? ? capture(&block) : nil turbo_stream_action_tag(action, target: target, targets: targets, template: template_content, **options) end end
Next up is to add a listener for turbo:before-stream-render and add some custom behavior.
// app/javascript/utilities/turbo_stream_render.js document.addEventListener("turbo:before-stream-render", (event) => { const { target } = event if (!(target.firstElementChild instanceof HTMLTemplateElement)) return const { dataset, templateElement } = target const { transitionEnter, transitionLeave } = dataset if (transitionEnter !== undefined) { transitionEnter(event, templateElement, dataset) } if (transitionLeave !== undefined) { handleTransitionLeave(event, target, dataset) } }) const handleTransitionEnter = (event, templateElement, dataset) => { event.preventDefault() const firstChild = templateElement.content.firstElementChild Object.assign(firstChild.dataset, dataset) firstChild.setAttribute("hidden", "") firstChild.setAttribute("data-controller", "appear") event.target.performAction() } const handleTransitionLeave = (event, target, dataset) => { const leaveElement = document.getElementById(target.target) if (!leaveElement) return event.preventDefault() Object.assign(leaveElement.dataset, dataset) leaveElement.setAttribute("data-controller", "disappear") }
Wow! This looks scary! It's not too bad really! It intercepts the turbo:before-stream-render event, checking the target element's dataset for specific transition attributes (eg. data-transition-start). For entering elements, it sets them to hidden and adds an appear data-controller. For leaving elements, it adds a disappear data-controller.
? Want to be more comfortable writing and understanding JavaScript as a Ruby on Rails developer? Check out the book JavaScript for Rails Developers
Make sure to import it into your application.js.
// app/javascript/application.js import "@hotwired/turbo-rails" import "./controllers" import "./utilities/turbo_stream_render.js" // …
Let's create those two controllers now. They are really simple and rely on the cool el-transition library. Make sure to add it to your app (either via NPM or importmaps).
// app/javascript/controllers/appear_controller.js import ApplicationController from "./application_controller" import { enter } from "el-transtion" export default class extends ApplicationController { connect() { enter(this.element) } }
// app/javascript/controllers/disappear_controller.js import ApplicationController from "./application_controller" import { leave } from "el-transtion" export default class extends ApplicationController { connect() { leave(this.element).then(() => { this.element.remove() }) } }
And with that all out of the way, you can now add smooth transitions to any added or removed element using turbo streams.
Simply append some data-attributes (data: {transition_enter: ""}) to the turbo stream and enjoy a smooth ride.
You can use the same data-attributes supported by el-transition:
For adding elements:
<%= turbo_stream_action_tag_with_block "prepend", target: "resources", data: {transition_enter: "transition ease-in duration-300", transition_enter_start: "opacity-0", transition_enter_end: "opacity-100"} do %> <%= render ResourceComponent.new(resource: @resource) %> <% end %>
And for removing elements:
<%= turbo_stream_action_tag_with_block "remove", target: dom_id(@resource), data: {transition_leave: "transition ease-in duration-300", transition_leave_start: "opacity-100", transition_leave_end: "opacity-0"} %>
The above is the detailed content of Smooth Transitions with Turbo Streams. For more information, please follow other related articles on the PHP Chinese website!