Getting Started with Astro 3 View Transitions

Getting Started with Astro View Transitions

Last year, I migrated wilchow.com from a standard React app to Astro. I’d been wanting to build something in Astro since it’s announcement. The biggest reason I wanted to try Astro was for the speed improvements.

I won’t go into all the features that Astro offers. That may be a topic for another post. For those who don’t know what Astro is all about, the big selling point for me is that it ships zero JavaScript by default. This is major since so many frameworks have become so bloated. My website is not very interactive and all the overhead of the scripts it used before were wasted time for someone browsing my site.

Astro just launched v3.0 a couple weeks ago with some great new features. My favorite feature is the View Transitions which give you elegant transition effects between pages. I’ll show you how to get started using this new API to make your site a little more lovable.

I’ll be using the Astro Blog template on StackBlitz to create a simple demo using View Transitions. You will see that a few simple tweaks to your Astro site will create a big visual impact.

Adding View Transitions to a page

Astro gives you the option to use the Transitions site wide or only between certain pages. In my demo, I will enable it for the entire site to make things easier. To do this, I will be adding the <ViewTransitions /> routing component to src/components/BaseHeader.astro.

---
// BaseHeader.astro
import { ViewTransitions } from 'astro:transitions';

// previous code
---

    <!-- previous markup -->
    <ViewTransitions />

If you don’t want transitions to be on every page, just add the above code to the <head> on the pages you want them on.

Setting Up Transitions

If you want to control what elements animate between pages, you have to name them. We’re going to start by adding transitions to the hero images of the blog posts. In the Astro docs, they have an example animating the <aside> elements.

For our demo, we want to animate the hero image in the Blog index page to the Post page. Since the Blog index page has multiple hero images (one for each post), we can’t name each transition the same thing. Let’s create unique names for each hero <img> tag with hero-[slug] since each blog post has a unique slug.

1. Blog Index page

In the Blog index page, we will add the following to the <img> tag:

transition:name={`hero-${post.slug}`}
---
// src/pages/blog/index.astro
---

<main>
  <section>
    <ul>
      {
        posts.map((post) => (
        <li>
          <a href={`/blog/${post.slug}/`}>
            <img width={720}
              height={360}
              src={post.data.heroImage}
              alt=""
              transition:name={`hero-${post.slug}`} />
            <h4 class="title">{post.data.title}</h4>
            <p class="date">
              <FormattedDate date={post.data.pubDate} />
            </p>
          </a>
        </li>
        ))
      }
    </ul>
  </section>
</main>

2. Blog Post Layout

The second thing we need to do is to name the transition for the single post view. This will be done in the src/layouts/BlogPost.astro. As we did on the Blog index page, we need to add the following to the <img> tag:

transition:name={`hero-${slug}`}

At this point, post.slug does not exist in this layout file. We need to pass it into this layout file. In order to do this, we need to add slug the destructuring assignment in the frontmatter section.

const { title, description, pubDate, updatedDate, heroImage, slug } =
  Astro.props;

This is what BlogPost.astro should look like:

---
// previous code

const { title, description, pubDate, updatedDate, heroImage, slug } = Astro.props;
---
<article>
  <div class="hero-image">
    {heroImage &&
      <img
        width={1020}
        height={510}
        src={heroImage}
        alt=""
        transition:name={`hero-${slug}`} />}
  </div>

  {/* previous code */}
</article>

3. Passing slug Value into Blog Post Layout

Now that the Layout file is set up to accept a slug value, we need to update src/pages/blog/[...slug].astro, which is the page that is using that layout.

There are better ways to set this up, but we’re going to keep things simple for this demo. First we need to declare a variable for slug in the frontmatter section:

const slug = post.slug;

Now we need to pass slug into the <BlogPost> layout component:

<BlogPost {...post.data} slug={slug}>

Here is what [...slug].astro should look like:

---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: post,
  }));
}
type Props = CollectionEntry<'blog'>;

const post = Astro.props;
const slug = post.slug;
const { Content } = await post.render();
---

<BlogPost {...post.data} slug={slug}>
  <Content />
</BlogPost>

The Result

Here is what the transition effect is looking like:

Bonus Round

Let’s see if we can make things a little more animated. We can add transitions to the titles and dates in src/pages/blog.index.astro:

<main>
  <section>
    <ul>
      {posts.map((post) => (
        <li>
          <a href={`/blog/${post.slug}/`}>
            <img
              width={720}
              height={360}
              src={post.data.heroImage}
              alt=""
              transition:name={`hero-${post.slug}`}
            />
            <h4
              class="title"
              transition:name={`title-${post.slug}`}>
              {post.data.title}
            </h4>
            <p
              class="date"
              transition:name={`date-${post.slug}`}>
              <FormattedDate date={post.data.pubDate} />
            </p>
          </a>
        </li>
      ))}
    </ul>
  </section>
</main>

Now we just need to add the same names to the elements on src/layouts/BlogPost.astro. Also, let’s move the title above the date so that the transition is smoother:

<article>
  {/* previous code */}
  <div class="title">
    <h1 transition:name={`title-${slug}`}>{title}</h1>
    <div class="date" transition:name={`date-${slug}`}>
      <FormattedDate date={pubDate} />
      {
        updatedDate && (
          <div class="last-updated-on">
            Last updated on <FormattedDate date={updatedDate} />
          </div>
        )
      }
    </div>
    <hr />
  </div>
  {/* previous code */}
</article>

This is what the final transition effect looks like now. Check out the working demo on StackBlitz.

Conclusion

Astro’s View Transitions are easy to implement and cleanly make a big, visual impact. Head over to the View Transitions docs for more transition options.

Resources