RSS FeedTwitterMastodonBlueskyShare IconHeart IconGithub IconArrow IconClock IconGUI Challenges IconHome IconNote IconBlog IconCSS IconJS IconHTML IconShows IconGit IconSpeaking IconTools IconShuffle IconNext IconPrevious IconCalendar IconCalendar Edit Icon
setInterval(() => {
  document.startViewTransition(() => {
  h1.textContent = word[index++]
})}, 500)
My google avatar.5 min read

Text Replace Transitions

cssjs

First up, the good stuff, the final demo, as if you skipped to the end.

If that's basically an error message about support, here's a recording of it running in Canary (with this flag enabled 😉.

tldr;
view-transition's let me, with CSS, describe how to dismiss the old text state and reveal the new text state.

The inspo #

This demo idea came from the game Session Skate. In the opening credits, "SESSION" has each letter rapidly cross fade. It looked pretty coo and I instantly realized I could do this with view-transition's, setInterval(), and .textContent. So I sent myself the email todo, cuz well, it was time to land a sick trick, not work.

That turned into a small prototype. Peep these barebones!

setInterval(() => {
  document.startViewTransition(() => {
    h1.textContent = word[index++]
    if (index >= word.length) index = 0
  })
}, 500)
  1. Every half a second
  2. Take a quick "before" snapshot
  3. Set some new text content

When the work finishes in that view transition callback, the browser interpolates between the changes for you, for free. In our case, there was a "V", now there's an "i". One letter disappeared, one letter appeared. We get a crossfade!

There. We matched the video game.

Customizing the transition; aka spinkling props #

Crossfades are coo (fo real y'all), but what if I want to customize how the text swaps? Perhaps with some Open Props?

No prob 🤘💀

First thing, @import the easings and animations:

@import "https://unpkg.com/open-props/easings.min.css";
@import "https://unpkg.com/open-props/animations.min.css";

Next, as an optimization, tell the whole page to chill and not transition when a document transition snapshot is requested.

html {
  view-transition-name: none;
}

Now that we said what we dont want to transition, let's specify what should, the h1!

h1 {
  /* be stable. fix that width. */
  inline-size: 1em;
  
  /* required for view-transitions  
     now it's not the whole page */
  contain: layout;
  
  /* yo browser; transition this! */
  view-transition-name: replace-effect;
}

Remember that view-transition-name, we'll be able to reference its before and after states in the next section.

Now for the best part #

Use some of these props to orchestrate the exit and entry of a character / the DOM snapshots. Let's start with how the old character should exit; let's see, I want it to scale out, like away from me, and fade out at the same time.

@media (prefers-reduced-motion: no-preference) {
  html::view-transition-old(replace-effect) {
    animation: 
      var(--animation-fade-out),
      var(--animation-scale-down);
  }
}

Delightful magic.
Now for the element entering; fade it in while sliding it up!

@media (prefers-reduced-motion: no-preference) {
  html::view-transition-new(replace-effect) {
    z-index: 1;
    animation: 
      var(--animation-fade-out) reverse,
      var(--animation-slide-in-up);
  }
}
the fade-out animation is reversed so opacity doesn't need set to 0.

Mix and match animation props in that Codepen and have some fun 🙂

outro #

Fun Stuff!.

Make a DOM change, describe how to transition the changes. Love it.

Hope this gets picked up by other browsers 🤞 Peep the spec.

Mentions #

Join the conversation on

@argyleink nice write-up, thanks

David McCormickDavid McCormick

@argyleink Good to see you here on Mastodon!

Thilo MaierThilo Maier

Crawl the CSS Webring