Turbo Streams, either fired via a controller action or over websockets are an incredibly easy but powerful way to make your apps more responsive and interactive.
At Spinal, the micro-SaaS I've founded I'm all-in on this “boring Rails stack”. It helps me ship features quickly without a lot of overhead.
There is one technique that I haven't seen talked about before in the Rails/Hotwire sphere, but I use it often to spark joy when using my SaaS app: add an animation when inserting elements into the DOM via Turbo Streams.
It's simple, really. Have an optional animated: false
attribute on the ViewComponent or the partial that you insert. Let's see some of these in action and then go over each step needed to replicate this.
Fade in a new icon
Customers can change the icon for their content type. Upon save, the icon is shown on the page with a subtle fade- and zoom-in animation.
Animate content rows
When changing the order of the content, each row is fading in one row at a time.
Animate content cells individually
When customising which columns are visible in the content overview, they are shown top to bottom and from left to right with a fade-in animation. Notice how they are not fading in all at once, but one at the time. Sparking all the more joy.
It's important to note that these animations do not get triggered on regular page visits—which would get annoying pretty quickly, but only when triggered via Turbo Streams. So how do you this?
Feel free to sign up for Spinal to see these little animations in 4K instead of a crappy gif.
How to spark joy yourself
Let's walk through all the critical parts on how to do this. I'm using ViewComponent, but the same principles apply if you use Rails' partials.
ViewComponent class
class ContentRowComponent < ApplicationComponent
def initialize(content_row:, content_row_counter:, animate: false)
@content_row = content_row
@animate = animate
@content_row_counter = content_row_counter
end
# … other methods omitted for brevity
def row_css
class_names(
"content-row",
{"animate-fadeIn": @animate}
)
end
end
First initialise the @content_row_counter
and the @animate
instance variable (set it to false
by default). The content_row_counter
is a variable coming from ViewComponent.
Then a row_css
method where all the css for the <li />
-element is defined. Here the most interesting is the class_names()
helper from ActionView. It only appends the animate-fadeIn
class if @animate
is true
.
ViewComponent erb
<%= tag.li @content_row, class: row_css, style: "animation-delay: #{@content_row_counter * 70}ms;" %>
row_css
is referenced here. Also a delay for the animation is set which simply is @content_row_counter
multiplied by 70(ms). This then makes each row fade in one after another.
Turbo Stream response
<%= turbo_stream.update "content-rows" do %>
<%= render ContentRowComponent.with_collection(@content, animate: true) %>
<% end %>
And finally, where the magic happens. Upon saving, this turbo_stream method is called. It updates the element with the id of content-rows
. And only here is animate
set to true
.
You are, of course, not limited to a boolean animate
variable. You could as well pass the animation name as a string and like that add more advanced animation option based on your needs. For example: animate: fadeInSlow
.
And that's the bare essentials of sparking joy to your Turbo Streams. If you do apply this technique, do share the results with. Love to see it.
Top comments (1)
Great stuff! I love adding a little joy to my apps as well and Turbo is perfect for that.
Good luck!