This is a super quick tutorial to show you the new ViewTransition API that ships with Turbo 8, released recently.
View Transitions are a neat way to add more fluidity between views in your app. With Rails and Turbo 8, adding this effect is pretty straightforward. The most challenging obstacle is understanding the CSS side of the equation.
Let’s dive in and start experimenting.
Create a new rails app
I’ll use a basic Rails app with esbuild for JavaScript. We won’t use JavaScript in this tutorial, so feel free to use the defaults.
rails new turbo_view_transitions -j esbuild
To save time, I’ll add the tailwind-rails gem to get scaffold UI out of the box.
bundle add tailwindcss-rails
rails tailwindcss:install
With this out of the way we can generate a new Post
scaffold with some demo columns.
rails g scaffold Post title:string body:text
Update your root route to posts#index
# config/routes.rb
Rails.application.routes.draw do
resources :posts
get "up" => "rails/health#show", as: :rails_health_check
root "posts#index"
end
Modifying the Rails app main layout
To use view transitions with Turbo 8, add a new meta tag to your layout’s <head></head>
tags. <meta name="view-transition" content="same-origin" />
<!-- app/views/layouts/application.html.erb-->
<!DOCTYPE html>
<html>
<head>
<title>TurboViewTransitions</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="view-transition" content="same-origin" />
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
</head>
<body>
<main class="container mx-auto mt-10 px-5 flex">
<%= yield %>
</main>
</body>
</html>
Here’s my modified layout. The main change is the new meta tag <meta name="view-transition" content="same-origin" />
. I also changed the main
tag to use a smaller margin at the top of the page.
Modifying the posts UI
In the app/views/posts/index.html.erb
I added some new markup to help make this demo more visually appealing.
<div class="w-full">
<% if notice.present? %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<div class="grid grid-cols-12 gap-16">
<div class="col-span-8">
<div class="flex justify-between items-center">
<h1 class="font-bold text-4xl">Posts</h1>
<%= link_to "New post", new_post_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
</div>
<div id="posts">
<%= render @posts %>
</div>
</div>
<aside class="col-span-4 bg-blue-600 rounded-lg text-white p-10">
<h3 class="font-semibold text-3xl sidebar-title">Sidebar title</h3>
<p>This is my sidebar</p>
</aside>
</div>
</div>
Consider this a basic blog with a sidebar. We’ll leverage view transitions to make it snazzy.
Add custom view transition CSS
Now for the exciting part. Let's add some view transition effects starting from simple to more complex.
Simple global transitions (easy)
You may be interested in a slight animation on every view transition. To do that, you could add a couple of lines of CSS to app/assets/stylesheets/application.tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Global slight fade */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.5s;
}
Target the root of the page directly and add a global transition.
How about different types of animations?
Want to get a little more sophisticated? Try these styles that offer some slide and fade effects all in one.
@keyframes fade-in {
from {
opacity: 0;
}
}
@keyframes fade-out {
to {
opacity: 0;
}
}
@keyframes slide-from-right {
from {
transform: translateX(60px);
}
}
@keyframes slide-up {
from {
transform: translateY(130px);
}
}
@keyframes slide-to-left {
to {
transform: translateX(-30px);
}
}
/* Global but more fancy */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
Singling out elements and combining transitions
Say you want to target independent elements directly but also have global transition effects. That’s possible. Here’s my code thus far.
@keyframes fade-in {
from {
opacity: 0;
}
}
@keyframes fade-out {
to {
opacity: 0;
}
}
@keyframes slide-from-right {
from {
transform: translateX(60px);
}
}
@keyframes slide-up {
from {
transform: translateY(130px);
}
}
@keyframes slide-to-left {
to {
transform: translateX(-30px);
}
}
/* Global but more fancy */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Sidebar */
aside {
view-transition-name: sidebar;
}
html[data-turbo-visit-direction="forward"]::view-transition-new(sidebar) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-in,
210ms cubic-bezier(0.4, 0, 0.2, 1) both slide-up;
}
With turbo, we’re able to target directions. And in doing so, you can even tie into either the ::view-transition-new
or ::view-transition-old
states of the transition. With some gnarly CSS, you can target elements independently of one another and perform some wacky animations.
Notice how, much like CSS Grid, you can name “areas” to define layouts. In this specific case, you can use view-transition-name
to name an element in the dom for use in other targeted CSS.
So I used
/* Sidebar */
aside {
view-transition-name: sidebar;
}
This targets the aside
element in app/views/posts/index.html.erb
and gives us a way to define a new transitional style based on the turbo directions. Here's the example from before with that in action. Notice how our named view transition is "sidebar.”
html[data-turbo-visit-direction="forward"]::view-transition-new(sidebar) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-in,
210ms cubic-bezier(0.4, 0, 0.2, 1) both slide-up;
}
Hooking into Turbo
Turbo adds a data-turbo-visit-direction
attribute to the <html>
element to indicate the direction of the transition. The attribute can have one of the following values:
-
forward
in advance visits. -
back
in restoration visits. -
none
in replace visits.
Depending on the request, if you view the source when clicking a link or button, the HTML element will quickly update with the new data attributes. As a result, your CSS can capture the forward,
backward,
and none
directions to manipulate the elements on the page.
Browser Compatibility
Only Chrome and Microsoft Edge are currently supported, but I anticipate Safari and Firefox to follow soon. Keep this in mind as you add this to your apps.
What's the real-world use case for view transitions?
Having toyed with View Transitions, these will be extremely popular for folks leveraging Rails to build mobile apps. It allows you to do less with more native versus non-native apps. We see Turbo in use on the Basecamp team's apps already today, and it's about to get more familiar with other apps.
This is all not to say web apps or progressive web apps shouldn't leverage this feature. It will be overused to some degree. I remember when the trend of animating everything on a marketing website was popular. You couldn't scroll without something moving in or out of view. I worry we're in for that again...
We’ve only scratched the surface
This tutorial is super basic, but I hope it sheds some light on what’s possible with a little dash of CSS and tools you’re already using with Ruby on Rails. Among many new features, Turbo 8 brings a dash of life with View Transitions. Rails apps are about to get more pleasing, and I’m excited and here for it!
Top comments (0)