Smooth Page Transitions with Astro & the Shared Element Transition API
Can you make a multi-page app (MPA) feel like a single-page app (SPA)? Maxi Ferreira says we can and will show us how to do it.
Links & Resources
- https://github.com/Charca/astro-records
- https://astro-music.netlify.app/
- https://www.pluralsight.com/
- https://www.learnwithjason.dev/
- https://twitter.com/charca
- https://www.youtube.com/watch?v=JCJUPJ_zDQ4
- https://www.learnwithjason.dev/topic/astro
- https://github.com/withastro/rfcs/discussions/307
- https://developer.chrome.com/docs/web-platform/navigation-api/
- https://www.maxiferreira.com/blog/astro-page-transitions/
- https://www.learnwithjason.dev/topic/state-machines
- https://www.learnwithjason.dev/schedule
Full Transcript
Click to toggle the visibility of the transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn with Jason. Today, on the show, we've got Maxi I was about to try to roll my eyes, MaxiFerreira. I have learned the slightest bit of Spanish, but I can't roll my Rs.
MAXI: Yeah, it's hard.
JASON: I always sound like a doofus. I'm like, OK, we're going to a place with a double R in it. We need to tell our friends to meet us. I'm going to try. And it was the place was called Morrofi. I did it on the first try. [ Laughter ]
MAXI: Nice job.
JASON: Never happened before. You know what, I am so happy that just happened. I could this could be, what a great day. Today's a great day. [ Laughter ]
MAXI: Want to try my last name now?
JASON: see that's what usually happens. Jesus. See, OK.
MAXI: Ferreira, you can just say, it's totally acceptable.
JASON: See, it's so hard. Anyways, I'm going to take my one out of three win today for rolling my Rs and move on. So outside of my absolute inability to pronounce some Spanish characters, I'm super excited to have you on the show because you're working on some really, really impressive stuff that's with some kind of future facing technology and I'm really, really excited to learn about it, but before we do that, let's talk a little bit about you, so for folks who aren't familiar with you and your work, do you want to give us a bit of a background?
MAXI: Yeah, sure, my name is Maxi, I am mostly frontend developer. I do a lot with React these days, but yeah, I come from the PHP generation, I used to build websites on a single index .PHP file with your SQL queries at the top and mark up at the bot M to. Perhaps that's why I like Astro so much. Just a fantastic framework all around. And yeah, on top of that, I've been also playing with this couple of new experimental APIs that are the Chrome team has been working on, we there are plans to bring them to the web platform for all browsers to support. Right now, they're still experimental, but I think they're pretty cool and they enable a lot of really cool features for the web. Yeah, I'm excited to be able to share with you all today.
JASON: Yeah, I'm really looking forward to it. I think it's going to be a ton of fun. Let's talk a little bit about Astro to start. So something I found really exciting about just the web in general lately is it feels like we're in a little bit of a renaissance where when I was first getting into web dev, in 2003 or 2002, whenever I started doing it, there was DHTML, and it was huge pain to do anything with JavaScript. Tons of browser inconsistencies. And you just kind of had to live with that because there was no alternative. Until we saw this wave of new frameworks like Moo tools and J Query, and J Query simplified all of the cross browser things and we stuck with that a little bit. J Query dev. And as we saw that, it simplified something and we started complicated things on top of J Query and got unwieldy, the needs of the web evolved and the browsers, and J Query's hard to use now. What would be better? What would be simpler? And we had another renaissance, we saw Angular, View, React, and this sort of emergence of component based JavaScript. And this how do we make UIs that are really interactive? And that gave rise to the single page app and, you know, the but then, as we grew, like we've standardized mostly, you know, in terms of popular adoption, we see a lot of React out in the production world these days. And so, we're like at component based, a lot of SPAs and we were like, oh, we have other needs and we started doing server rendering and React server components and now we've got these issues around use effect and people don't understand how that stuff works. We're building more scaffolding on top of it. And it's getting complicated again. And we're saying, the platform has evolved. Our needs have evolved. We need something different. What's next? What's better than React? Simpler? And we're seeing reemergence of these really innovations on top of that, we've got stuff coming in the browser, which we'll talk about in a minute. So what have you seen that really drove you toward Astro?
MAXI: Yeah, no, I think that was a fantastic way to put the way we got here. The thing that drew me to Astro was this sense of too much complexity on the client. We're shipping too much JavaScript to the clients. And I like the way the HTML first frameworks, Astro, where they say, OK, instead of trying to encapsulate all of that complexity, like next.js is doing, we're going to be a metaframework so you don't have to worry about that. That comes at the cost of more JavaScript. I like the way this HTML frameworks are doing. Instead of encapsulating the complexity, they're eliminating. Starting with HTML from scratch. And then you can opt into hydration or interactivity if you need some JavaScript. That's probably my favorite approach to building websites or applications today. I still think that SPAs, traditional SPAs are the better choice for a lot of apps if you're building Gmail or Spotify and the web, you need an SPA. Going to get into a lot of trouble if you try to use an NPA framework. But for a lot of websites, this HTML frameworks like Astro are a great choice.
JASON: Yeah, I tend to agree with you that it feels like what ended up happening was because what we needed was a way to build apps on the web and because what we saw was this kind of component based architecture and SPA thinking emerges the dominant way for companies to build apps, everybody who was working on these complex apps, you know, working at Facebook, working at wherever, like, IBM Cloud and all these big, big dashboards and complex apps that were AirBnB all adopting React. We saw a lot of devs saying, oh, that's the way I build for the web. They were reaching for React and SPAs and they were building blogs. They were building marketing sites, things that, you know, when I go to a blog, what's the standard behavior for somebody going to a blog? You Google, you find a blog post that answers your question. You open it, you read the blog post and you close the page, right? I didn't use any SPA features, right? And these benefits people talk about with SPAs are there. When you load an SPA, your first page load is a little slower because you're booting all of the JavaScript. But subsequent page loads are near instant because you've already loaded everything. You click a button and the JavaScript updates the DOM. And that looks really nice. You get cool transitions, all cool stuff you want. But the part about that that's not fun is that for a lot of websites, the vast majority of websites I interact with on a day to day basis, I never navigate. I'm opening the page and then, I'm closing it, which means for almost every website I visit, if it's built as an SPA, I'm paying the cost for future navigate and future UX enhancements and never actually seeing that payoff because I'm not using the navigation or not clicking anything. I'm just reading. And so, what I'm really excited about with Astro and 11ty and some of these other things is we're seeing a shift towards make the right thing the easy thing. If I'm building a page that's just content, then just ship content. Don't make me boot JavaScript. And then, if I need interactivity, I can say, hey, this piece needs to be interactive and now we can boot some JavaScript to make it interactive. And that to me is really exciting. But it comes with a tradeoff. It has traditionally, and this is why I'm excited to have you on. Because one of the major pushback items you get when you talk about saying, well, in MPAs, they feel bad because you have to reload the page when you click a link to navigate. And that's no longer true, right? Now, we've got the shared element transition API. Do you want to talk about that? And what that unlocks for us?
Yeah, absolutely. This API, for folks who are not familiar, it's an experimental API. It's only available on Chrome at the moment. You have to what it allows you is to essentially transition with an animation, like smoothly transition from one page to the next. Even if those pages are, you know completely different pages that are rendered on the server. There is a catch there that the API at this moment only support SPA behavior. It doesn't work yet with NPAs that we're talking about.
JASON: OK.
MAXI: That's common in future version, there are issues on GitHub, they're talking about the API.
JASON: Gotcha.
MAXI: But we can trick our NPA. And we're going to see one in the demo. But basically, we can intercept the request. We're loading a server rendered page, this is my blog post. And I want to navigate the home page, but instead of letting the browser go and fetch the home page from the server, I am intercepting the request from the client, fetching the request, fetching the content with JavaScript and then, bringing that SPA style into the page. And with that, we get we enable this new transitions API on traditional website like Astro.
JASON: Mm hmm, yeah. I think that is the part I find really exciting because it sort of illustrates, I think maybe why as a community we're reaching a point where we can start looking beyond the current approach. The browsers have advanced where they were. We've seen a really big consolidation toward using Chromium and the ones that aren't Chromium based are doing a great job of making sure we're all working together to get pretty consistent support for all of these things. A lot of the things that React was built to control, a huge portion of the React codebase is synthetic events. And those are less necessary now than they were in 2015. And so, when you start looking at the landscape and see things like these new CSS APIs, these new browser built ins that are going to give us the ability to do you can now do drag and drop pretty straightforwardly. It's not as complicated as it was. You can do file uploads, you can do these shared element transitions. You can do the animation APIs are pretty dang good now. All of this stuff that was hard is becoming more natively supported by browsers without a bunch of cross browser work arounds, and stuff. That's not entirely true because we're still making progress. But it feels more possible.
MAXI: Yeah, and I think the APIs is a big win for SPAs, as well. If you have a traditional React app where you have React router to transition between pages. Now, you can transition between those pages using a lot less JavaScript. Before, you had to, you know, import animated JS or some other library or do a bunch of manipulation. This API makes those kind of transitions really simple and with only a few lines of JavaScript. I think big win for NPAs and SPAs, as well.
JASON: Yeah, and just the web platform in general. I think the goal should never be like we want an internet that exists without JavaScript whatsoever. JavaScript is good. It's a wonderful way to build in interactivity and to provide additional experiences and to make the web feel app like. And that is a goal. And I think I'm really excited to see that we can limit the amount of library JavaScript that we're writing so that the JavaScript that we do ship is for functionality not for boilerplate or foundation.
MAXI: Mm hmm.
JASON: That's exciting to see, instead of the 40, 50 kilobytes of boilerplate, you bring in libraries, you're looking at 3 to 4 megabytes to get your site bootstrapped and your component code is like 4 kilobytes, oh, that hurts. But so I think it's really, really fun to see that you can now things that if you had tried to write vanilla JavaScript to do a shared transition API, and I know because I tried this once. I remember I was trying to do this thing where I wanted page transitions. And so, I had my page on the screen, and then, I would load the next page, I intercepted the click event, load the next page in the background, and then, I'd have to build detached DOM elements and then, I would kind of get those positioned off screen. And then, you could float things around. It was an absolute nightmare. It totally broke on any browser I wasn't very actively paying attention to. It was so flakey and weird. And I gave up on it eventually. This is not worth doing.
MAXI: Yes.
JASON: And you move to React, because you could import the library, move things around and it just worked. With the shared element transition API, it doesn't feel that way. It doesn't feel rickety. Doesn't feel like, oh, boy, I've built myself a fragile house of cards that's going to fall over the first time I meet any kind of resistance. So this is something I've seen you sort of got you got a lot of attention for this demo that you built of Astro and the shared element API. You've been all over the place talking about it. Where did your initial interest in this API come from? What were you working on that got you thinking about this?
MAXI: I remember watching the demo, I think it came out in May from the Chrome team, demoing the API. And I was blown away. And I thought it was available for NPAs and SPA, as well, but they mentioned at some point it's only for SPAs at the moment. And I thought, oh, really cool to use this on an NPA, Astro, making a traditional website. And you can do this with any website that you build 20 years ago that is just HTML pages. And I decided to experiment. And how we can build this sort of SPA like behavior using a client side routing on an Astro website to the API. And I found out about the navigation API, which is the API I've used in my previous demos where the navigation API is experimental API. It's not available outside of Chrome. But it's really cool because it's like the evolution of the history API. So if you're building a sort of client side router today, if you're building it from scratch, you have to so the way the way you would do it is you listen to clicks and form submissions and different events that could trigger a page transition. And manually, you know, push date, history, push date, update the URL. Things like that. The history the navigation API lets you intercept a global navigation event. And it handles a lot of the things you have to do manually with the history of API, like controlling the scroll position or the focus state. Or even accessibility features. So it tells the browser, it's navigating so the browser will announce screen readers that the browser is busy doing work. All of those things come out of the box with the navigation API. So I think it's a very nice API. But the drawback is that it only works in Chrome. And you still have to do a lot of things manually when it comes to when it comes to handling scripts. Let's say you're on page one, you navigate to page 2, and you do that by just updating the DOM. You load the HTML of page 2, you paste that in a DOM element on page 1. So you have your navigation. The problem is that if you have scripts there either in the head or the body somewhere, you want those scripts to execute on the next page. You can do that with the navigation API, it's a bit clunky. Today, I'm going to use a different method. I'm going to use a different method using a tool, I don't know if you're familiar with turbo links. It's an old lever. I think it came out in 2013 or so.
JASON: Oh, wow.
MAXI: Yeah, and it was around the time where backbone and Angular and all of those frameworks were popular. And Rails and other NPA frameworks kind of to compete with those frameworks allow you this seamless transition between pages. They created turbo links, which basically lets you do that. It access client server router, intercepts your requests. Loads the next page, handles all of the scripts and things like that. It's very cool. We'll see how it works. Things might break, but hopefully not.
JASON: Yeah. Well, OK, so I'm looking at the chat to see if we have any questions. And a quick shout out thank you to Ben and Cynthia for the subs. Really appreciate that. Always very much appreciated. And Ben, you are now over two years on the show. Cynthia is a month away from a year. Really, just really appreciate y'all for supporting the show. It means a lot. OK. I'm not seeing any questions. So if anybody has questions, please feel free to drop those. I think at this point, all of the other questions I have are going to be easier if we're looking at code. Let me switch us into pair programming here. And I'm going to start by doing a shout out for the captioning. So we have Diane with us today from White Coat Captioning taking everything down. Thank you very much, Diane. And that is made possible through the support of our sponsors, Netlify, NX and New Relic all kicking in to make the show more accessible to more people. And I have good news, we just picked up a new sponsor for the show. So let me actually, since I don't have the logo up here. Let me drop a link to Pluralsight. Yep, oh, no not the home page. All right. Let me drop a link for you all. We have there we go. Thank you Pluralsight for jumping in. Super excited to have you on board. If you want to see the captions, head over to LearnwithJason.dev. Yeah, shaking it up. We've got our first non N name sponsor. Great news. And we're talking to Maxi today, make sure you head over and give a follow on Twitter. This is, yeah, this is going to be great. So we're talking about the shared element transition API, and there was a wonderful demo that you mentioned earlier let me see if I can find it from, it was from Jake Archibald.
MAXI: Yes.
JASON: And
MAXI: Share the link to the blog post on the Chrome website.
JASON: Is it this one? No, that's not it.
MAXI: If you check the Twtich chat. The other one. Yeah, there it is.
JASON: This is the one that we want. This video here that Jake did is fantastic. It's a great overview of what's going on, highly recommend giving it a watch. But today, we're going to just rely on Maxi. So what should I do first if I'm ready here? I know the first thing is, I have to have Google Chrome open. So I'm going to I installed it specially for today so we had it available.
MAXI: Yes. We're going to be working with this new API, which is super experimental at the moment. You have to enable the feature flag by hand. So you have to go to I can send you the link or, yeah, I can send you the link on the chat.
JASON: Chrome, flags, document, transition.
MAXI: Yes. You can change that to enabled.
JASON: I've enabled it, relaunching Chrome.
MAXI: That's it. All right. And yeah, I sent you a link to a repo where
JASON: So let me
MAXI: Yes. This is what we
JASON: Here's the repo and I'll drop this in the chat for anybody who wants to check it out on your own. And we're going to actually, let me drop a link to this Chrome flag, as well, if anyone wants to enable it. To get your own flag up, you can change that. I'm going to fork this, is that right?
MAXI: Yes.
JASON: I'll use the GitHub CLI which I adore. GitHub repo fork here and I want that to be at Learn with Jason Astro. Let's see if it goes. All right. Fine. Would I like to clone the fork? Yes, I would. The way to change that org name and I don't know what it is, I'll have to look up the docs at one of these points. But so, what are we looking at here? We have a what looks to be pretty straightforward Astro site.
MAXI: Yeah, it has two pages I built. It has some nice SPA like features that we're going to see the point of those in a minute. But, yeah, it's
JASON: Oh, whoops. I opened my whole folder instead of the let's go into Astro records. Crap, did I just clone it in here? Where did it go? Let's do a GitHub repo clone Astro records. That should go into Astro records. I'll figure out what happened before later. This is a nice, small repo to get set up.
MAXI: Should be small. I think they have some
JASON: Did they clone somewhere else? Cloning into learning Jason I gave it a folder name. All right. I'll clean that up later. So we'll get into Astro records and I'm going to open this up. Here's our actual project. We've got two pages. So we've got the index and a dynamic page.
MAXI: Mm hmm.
JASON: And I'm not going to get in we probably don't want to get too deep into the details here, but if you are interested in more, we've got a great actually, let me show the whole Astro section here. We've got a ton of good stuff. Two episodes with Fred and once today goes up, we'll also have this episode with Maxi in here where you can learn all of the things you want to learn about Astro. Close that up, again. And we'll head back here. OK.
MAXI: As you can see, very little JavaScript, and then the rest is just HTML. Anything below line 12 is HTML. A little bit of, this JSX like syntax, but it's mostly HTML. And you can run the dev server to see what this looks like and might make more sense.
JASON: I'll do a quick NPM install, then I'm going to open up the package JSON, so it's got all of the standard scripts so I can do an NPM run dev. And we'll go over to Chrome. Here we go.
MAXI: Yeah, this is server side rendered app. We're fetching displaced records from an API. And if you click on any of those, you will get to the details view, which is another server side render.
JASON: The thing that's really cool about this, if you view source on one of these, it's just the list. This is the HTML this is very nostalgic for me, when I first started building for the web, you would view source. And as JavaScript tooling got more sophisticated, we got grunt and gulp and web pack and babble. It stopped being possible. Because when you view the source on most SPAs, you just see a div ID app and a bunch of JavaScript, you can't see how it's built unless you get the source code. GitHub solves that problem, you can see what's going on. But this is really nice to be able to go in and look and say, oh, here's how that record div is structured without having to go to the source. All right. I'm going to click into one of these. We've got a full page load. Clicking back out, full page load. Right? This acts exactly as I would expect a multi page app to behave.
MAXI: Exactly. This app in particular has some SPA like features. We are going to see how to improve the experience of the website using the tool API package. If you enter any of those links and you play, if you play or set this on. You can it's actually the same song, every record has the same song. It's not the actual song.
JASON: I was like, I'm not going to play very much of this because I don't want to get DMCA take down.
MAXI: I made sure the song we are playing is copyright free kind of song. It should be OK. But you can see we're playing now this song, and the problem is I want to go back and check out another CD or something, I'm going to lose the playing of the recording, this is an NPA. If you go back, it stops playing.
JASON: Mm hmm.
MAXI: If you were in an SPA and I know that I mentioned in the beginning if I were building Spotify, I wouldn't use an Astro, for example, so this is a bad example for building a website with Astro, but I think there are still some use cases when you might want to have some element of your site kind of persist on the page where you keep navigating the page, right? So for example, on the Learn with Jason website, you can go in and watch an episode. Maybe you want to add a feature that are you using Astro for this website? What is this build on?
JASON: What did I just do? I am using Remix, I believe.
MAXI: Oh. OK. Let's say we were building this with Astro or 11ty or some other NPA framework. And you want to play one of those videos. And you want to keep it playing while you navigate around. Let's say, you want to I want to listen to this video while I go to the blog and check out some blog posts.
JASON: So right now, it doesn't work. It's gone as soon as I click.
MAXI: It's gone. Exactly. And maybe with Remix, there's a way to do this. I think Remix has the routing, as well.
JASON: Give me one second. There's like a buzz saw going on outside my window. [ Laughter ] I've closed the window and we're going to hope for the best. Focus. There we go. If we were using Astro 11ty, et cetera and we wanted this to play, we would need to do something with it.
MAXI: We couldn't do it out of the box. We would have to do some methods. Like the one we're going to use today. But yeah, it would suck if you have to refactor your website using next.js, for example, just because you want this feature. You want to keep using Astro. That's a use case we're going to be covering with the demo today, even if it looks like Spotify and Spotify's probably not the best example of an NPA.
JASON: Honestly, what I love is it's even possible to make a demo even if this isn't the most efficient way to do it. Because it just wasn't possible even a few years ago. You would start P ask immediately hit so many weird edge cases. Let's not bother. We want to be able to specifically keep this running while we navigate around the site.
MAXI: Yes, exactly. So as a first step, once we do that, we are able to do the transitions and the animations, but the first step is to create this sort of SPA like behavior.
JASON: Yes.
MAXI: I used to do this with the navigation API, it was a bit clunky and experimental API. So for this demo, I'm going to use this package called tool which should be turbo. It's very easy to use. If you go to, there should be a script folder with router. Yeah. Should be empty file. So here you can import turbo. You have to import start yeah, that package hotwire.
JASON: OK.
MAXI: And turbo.start. Yes. It's a method. And that's it. If you refresh if you save this and refresh the website, you should now have a sort of SPA like behavior.
JASON: Whoa.
MAXI: It's hard to see, but it's there. And you can test if you play the song, you play the song.
JASON: Yeah, and you can kind of see here, too, we're not we are full navigating but we're not am I I'm not filtering?
MAXI: It's not a full navigation. If you compare this with a page refresh. If you compare it with the page refresh, loads all of the CSS, JavaScript, all of this. This is fetching the HTML of the next page. It's a fetch, client side fetch call for the next page. And it's replacing that, updating the DOM with the next page.
JASON: Got it. Got it.
MAXI: Now, if you play a song and then go back, you know, click, go back, then that thing should persist there.
JASON: Cool. OK. So that was that felt like magic. What happened that made that work? What's happening under the hood. Did you set anything up?
MAXI: I did not. That is actually all the code you need to do this. And that's because, you know, this turbo elaborates doing the heavy lifting. Under the hood what's happening, it's fetching the next page.
JASON: OK.
MAXI: That player I'll go into this. But that player is React component exists on the layout. So the element is present on both pages.
JASON: So if I can attempt to reverse engineer this on my limited understanding of what's going on. The only thing that turbo did, it is saying when I click a relative link to another page on this site, instead of doing a full page refresh, get the next page's HTML and it just is it doing a diff and replacing the slot here and that's why we saw once I click a song, the player becomes visible? And because we didn't do a full page refresh, it remains visible but it turbo's not replacing the full HTML or it would have knocked this player out. Is it smart enough to know only what changed?
MAXI: It is actually replacing the entire body. But since the player exists on both pages, it keeps the player is if you go back to the demo, you play a song for a few seconds, you see that when you navigate back, the player kind of play for a couple of seconds so you see the progress bar is loading and then you navigate back and it resets, right? That's because that's because we are also replacing this element.
JASON: That was weird. It was stacking the songs or something.
MAXI: That's an issue we're going to fix in a second. That's because this same behavior that we're seeing. Turbo is replacing the entire document. I think it's attached to a global listener or something like that. So it didn't cut the audio.
JASON: Got it.
MAXI: The component rerendered and it started from the beginning, from the initial state. And it started to play the song, again, right?
JASON: So it is replacing the element. It's just retaining because we didn't reload the page, it's retaining that preact state that says I'm open.
MAXI: Exactly.
JASON: OK.
MAXI: Yeah, that's a default behavior of turbo, replace the entire body of the page and it'll kind of merge your head. It does smart things on the head to say, OK, I already run this script, so the script, I don't need to run it, again, you say, OK, this script exists on both pages but I actually want to rerun the script go to the next page. You have a lot of control over what the behavior you want to do is. But if you want to fix that issue with the player, what we can do is we can go to the layout element and we can wrap the player in a div. And we can set the attribute called data turbo permanent. And we have to give this an ID, as well, ID audio player or something.
JASON: OK.
MAXI: Now, turbo will see this, I already have this data, this div with the idea of player on this page, it exists on the next page. I'm not going to replace it. And that will fix the issue we have with the kind of player resetting. If you go back now, you see that the state is persisted.
JASON: Cool. That's really cool. And as I'm navigating around now, it's not changing. I'm going to go into this one.
MAXI: Yeah.
JASON: That's slick. Very little code to do something I thought was going to take us most of the episode. I don't know if you see me checking the time. I was like, I don't know how we're going to get this done. [ Laughter ]
MAXI: Yeah, and that's why I didn't use the navigation API for this, because with the navigation API, we have to build a lot of these things from scratch. This is a bit more magical, but it's also more bullet proof, faster, of course, this is a library that's been around for almost ten years now. It's pretty consistent, I will say. As opposed to the navigation API, I will actually ship this to approximate, for example. And as you also mentioned, I don't know if we have folks from Astro on the chat, but there is an RFC on the Astro GitHub repo where they're talking about something called I think it's called permanent island. Let's see, persistent island, I think it is. Persistent islands, I think it's called. Let me find you a link.
JASON: Is there let's see. Let's go here. We can drop everybody's got this link. Thank you. [ Laughter ] OK. Perfect. Persistent islands is going to give us
MAXI: Basically the behavior we just saw but out of the box. Out of the box with Astro. This element if it exists on the next page, keep it, don't replace it. I'll have to upload that later. This is great. I love this. Hopefully in the future we don't have to. We have this sort of SPA behavior now for NPA. What we're going to link when turbo visits a new page. And we're going to call this new API. Add event listener.
JASON: Am I doing it in router here?
MAXI: Yeah this is globally on every page. We can do everything here. The event is called turbo colon load.
JASON: OK.
MAXI: Whenever we load the page, we call this. And in here, we have to listen to another event. And this is bit clunky, we have to do it this way otherwise we get multiple event listeners per page. Before dash render.
JASON: OK.
MAXI: And we're going to define this as a separate function, if you can, so that we can detach it, then.
JASON: OK.
MAXI: Full render. And yeah. And at the end of this function, we can do it here, we're going to stop listening for that before render event. So we can call document, remove event listener. Before turbo render, and we're going to pass this before render function.
JASON: You want it inside the function like this?
MAXI: Yes, yeah. This is a bit boilerplate, but we have to do this so we don't attach multiple events per page load.
JASON: Right.
MAXI: Now, we can start inside this before render function, we start playing with the transition API, and it's very easy to get started. So, the first we're going to do sorry, this function takes an event as an argument, which is a turbo event. Event.prevent default here. This will prevent the navigation. This will, you know, fetch the next page, but it won't render, which is OK, we're going to handle the rendering by ourselves.
JASON: OK. I'm going to add a couple notes here. Prevent adding event listeners. And this is going to load the next pages HTML that doesn't render it.
MAXI: Correct. Yes. By the time the before render function is called, we have the HTML on the next page, but we're saying, don't render it just yet.
JASON: OK.
MAXI: Now the transition API. We should check if we have access to the API before so that we don't break on if we say document create document transition doesn't exist for some reason or if not document. Which is going to return actually, we can what we can do here is call if, we can call sorry I've lost the we can call event sorry go ahead.
JASON: Yeah, sorry, is it isn't it like if you do if, something like that?
MAXI: Yes. Yeah. I think it's either way should work, I believe. But yeah. If we kind of do the opposite of this. In this case, we're going to call event.detail.resume.
JASON: Detail
MAXI: Resume and that's going to resume the rendering that we just prevented online 10. And we're going to return here, return early because the browser doesn't support the transition API.
JASON: Support fallback, OK.
MAXI: Yes. Now here, we can actually create a transition and we're going to call create document.create document transition. And that will give us back a transition object, which we can grab. So we can say const transition equals to that. Transition has one method. That's start, transition.start and pass a call back. We're going to say render as part of the transition.
JASON: OK.
MAXI: And if we didn't I didn't mess up, I think we should see the transition happening if we refresh the page now.
JASON: So refreshing.
MAXI: Yeah, I think it's there.
JASON: It's like a fade.
MAXI: This is the default behavior of the API. We can make this more obvious by
JASON: So, when you say default, there's there is no, yeah, this is empty. So that is not like you did code ahead of time, that's just when we say create an element transition by default, it does a cross fade.
MAXI: Exactly, that's a default. Cross fade.
JASON: All of this behavior here of this cross fade happening is all because of this like these four lines of code?
MAXI: Yes.
JASON: Got it. Very, very cool.
MAXI: And the transition, we can talk about how it works in a minute. But the cool thing, we can control it with CSS. So, if we go to the transition CSS file that I created, you just show this empty.
JASON: Yeah, sorry, I'm responding to a question in chat. Now I'm going back to this transitions.css. OK.
MAXI: Yes, and here we can we can style the transition, because it's a CSS transition between two elements.
JASON: Right. The elements we have to target for the CSS rule are pseudo elements, to target and you have to do colon colon, page dash transition dash outgoing dash image. And in parentheses after image, we have to pass in parentheses, the tag of the element. So in this case, we only have element that is transitional that is called root. So we have to pass root here.
JASON: Yeah?
MAXI: And it's not a class name, it's a tag, which is a new thing they came up with. We'll style the incoming images, as well, you can copy the first line and we can style the incoming image, as well. Yeah. But change outgoing with incoming. There you go. And here, we can say animation duration, for example, say 5 seconds. So that's more obvious what's going on. So it's the same transition, but we control with CSS.
JASON: Amazing for de bugging if you ship this to production, I would send you hate mail. [ Laughter ]
MAXI: Yeah, and now we can commit it out right away just in case.
JASON: This is great. What I do like about this, if we slow it down a little bit. By slowing it down, we get really good feedback on what's happening. Because when it is that default, is what, like 200 milliseconds? It's super fast. Let me refresh to get the new. Yeah, it's so fast you almost can't see what's happening. So that slowdown for development is incredible.
MAXI: Yeah. And this so you may be wondering why pseudo elements out there is weird looking rules we have. And the way this works is if you go back to the script to the router.js file, the way this works is that when you call transition.start, at that point, before executing the call back, the browser will take a screen shot of the page, at this very moment before the transition happens. And then, it'll execute the call back and the call back we can update the DOM and do all of those things, those will happen behind the screen shot we take. We can do a bunch of things here, but the user will still see the old page because the screen shot on top.
JASON: Mm hmm.
MAXI: By the time the call back ends, it'll take another screen shot. And then, it will transition between those two. In this case, we're seeing that one fades in and the other one fades out and we get the transition.
JASON: Right. Right. OK. That makes sense, I'm with you.
MAXI: Yeah. We can play now with another transition. We can define in the CSS file.
JASON: OK.
MAXI: We can define a new animation with the key frames. Add key frames. And I call this slide right, slide right. And here, we're going to say from and transform, translate X100%. And then we're going to get the slide left, which is going to do the opposite. It's going to be two translate X minus 100%. Now, we will need to need to target the incoming and outgoing message separately. We want to give the outgoing image, we want to give the slide left animation. So we can say animation yes. And then, slide right to the other one.
JASON: OK.
MAXI: Let's see how this looks.
JASON: I think I broke it. What did I break?
MAXI: I think we need more stuff in the animation rule. So let's say animation 3, sorry on the line 7, let's say one
JASON: Yeah, yeah, OK. We need
MAXI: Yeah, we need the first duration, yeah, 2 seconds, then the ease is out. Let's try this, if not, we can put the ease in before. Oh, yeah, it works perfect.
JASON: Eh!
MAXI: It always slides to the right. It's always coming from the right. Even if you go back. And we have to do some other things you want to reverse the transition. But in this case, it doesn't really matter. But you notice that the entire page transition. If you click on even the header and the header's something we might want to, you know, you don't want to don't want to transition the header, as well, if you can. What we can do is and once they have that, they will transition and animate. What we can do here is define a class here in this CSS file.
JASON: OK.
MAXI: Going to call it yeah, call it page header or something like that. And here we need to apply an yeah, attribute called page transition tag.
JASON: In here?
MAXI: Yes. It's like a style. It's a new thing that you can define. And here, we can give it any name. I don't think it has to be a string. I think it can be
JASON: Oh, it can be
MAXI: Just a value. Header. And we need the rule contains. Contain column paint.
JASON: What was the value?
MAXI: Paint. This contain paint is basically saying that the contents of this element doesn't, they don't leak outside of its boundaries. So this is just to have the transition.
JASON: And I need to give this
MAXI: Yes.
JASON: An element? Or do I need to do it in the header component?
MAXI: I think you need to go into the header component.
JASON: Let's go into header and here, I want the class to be page header, right?
MAXI: Exactly, yes.
JASON: I'm going to save that one, come out here to my transitions, save this one. Going to reload the page. Look at it go. All right. Very cool.
MAXI: If you play a song now, you have the same problem with the footer that it also will kind of transition with the rest of the page. We can apply the same kind of behavior if you want to.
JASON: It was player, right? We can, honestly, what we is it
MAXI: We have a wrapping div around the player if you want to use that on layout.
JASON: That's right. We do. On layout and this one, we could just call class like bottom player, and if we come in here to the transitions, and we should get the same output. Let me refresh the page make sure we have that.
MAXI: You will get an error here. Talk about why in a minute. Yeah. Did it break or something?
JASON: Looks like it broke and I don't know why I broke it.
MAXI: That is probably because you have two elements. You're assigning the same transition tag to two elements on the page.
JASON: I get it. I get it.
MAXI: That's kind of like a constraint of the API. Only one element of the page can have a given transition tag. And that's because you need to the browser needs to know which, you know, which element on the next page.
JASON: Totally makes sense. Yeah. I still seem to have broken it and I don't know how. What changed? Change anything in here?
MAXI: It could be because the contain paint rule. Try moving that and seeing if that fixes, at least, the UI. Hit play. I don't think the transition will work.
JASON: Yeah, we didn't tell it to sit still.
MAXI: Exactly. This might be maybe if we applied the class instead to the wrapping div to the component.
JASON: OK.
MAXI: Let's move that to the player component. Not sure if this will work. We'll see. This seems to be some sort of conflict between the styling of this element and
JASON: I think I had an inconsistent DOM thing. Now it plays.
MAXI: I think you need to give the contain paint.
JASON: Contain paint again. OK, let's try, again. Works. And works. Hey! All right. This feels great. We're already doing something pretty cool here. And I have a feeling we can make it cooler. We can, yes, and it's a bit more JavaScript. A bit more cooler. Because basically, animation that I did on a previous demo is that instead of this doing this slide animation, when you click on an album, the cover will expand on the sit on where the album is on the next page.
JASON: Right.
MAXI: We can do that, but the problem, an issue that we have is that we had to first find the element on this page that has the cover of the album we want to transition to. And problematically apply this class that has a transition tag.
JASON: OK.
MAXI: We can't give the tag to every element on the page because we saw only one element on the page can have the tag.
JASON: Got it. So to do that
MAXI: What we can do is and we also need to, it would be helpful, also, to separate the transitions between if you're going from the home page to the album page and the transition going from the album page to the home page because you will need to do different behavior. So what we can do is instead of creating the transition down here, what we can do is, we can check. Let's see what I have here. All right. We can check the location path name. We can check if location path name is equals to just a slash. We're in the home page. We can call a function we're going to call a function we're going to call it handle home navigation. And we're going to pass the event.
JASON: OK. So it's
MAXI: Yeah, create a function here.
JASON: And the other one would be album nav or something?
MAXI: Album nav exactly.
JASON: And we'll do an else and we'll album navigation. And my assumption is we're going to move this into these functions?
MAXI: Yes, exactly. Let's start with the album nav first, that's a bit easier.
JASON: OK.
MAXI: Here, in addition to this, we need to know hold on, OK. We need to find first the image that corresponds to the album we're navigating to. We can fetch, query the DOM for the link that has the transition path name. If we go to album/123, we need to coordinate which has the album 123.
JASON: We want document.query selector. And then, we can do equals, and is it just event.target? Href?
MAXI: We can do location.path name, I think.
JASON: Location.path name is going to be updated.
MAXI: At this point we're ready to render the next page. We haven't done so yet. That's going to be the link. Exactly. Let's see if you can log that. I don't know if this make sure it's giving out the album.
JASON: Put that to the bottom, go to the console, refresh and nope, doesn't like that. Doesn't like query selector. Oh, do I need to quote it? That gives us the link.
MAXI: It's empty now because we have moved on to the next page, the element is hanging in there. But if we query now, if within that album link element we query what's it called? We can query the image, it should be only one image there.
JASON: OK.
MAXI: You can say yeah, query selector, image.
JASON: OK.
MAXI: Now, we can give that image a class of class list.add and create this class that we will give it a transition tag. Say album image. Yep.
JASON: And now, in here, I'm going to take this and say album image, and then, we're going to call this one image. And I need to also update that in the component, right?
MAXI: We're giving the class via JavaScript. We don't need to do it here, right?
JASON: We're only adding it on this one. Doesn't it need to match here, too? Like this one needs the transition tag?
MAXI: Yeah, with JavaScript, we're giving it to the album cover here on the index page. And we also need to update the image on the next page so if you go to what's the name?
JASON: Album sorry, I misunderstood how this worked.
MAXI: Yeah, it's one of those thumbnails, it's not the same.
JASON: Got it. So I need to use this album image class on the image here. Right?
MAXI: Right.
JASON: Theoretically, this will work, right?
MAXI: This will work. We need to yes, it will work. That's right. Try it. Yeah.
JASON: Look at it go! OK, but it doesn't work on the way back for reasons I don't understand.
MAXI: We need to implement the other function.
JASON: You're right. We didn't do any of that. So let me go back to our router and for the home navigation to at least unbreak it. We can throw this in here, right?
MAXI: Mm hmm. Yep.
JASON: Go here, pop. And then on the way back, it goes back to the cross fade.
MAXI: Exactly. Yep. Yeah. All right. In the next one, if we want to make it pop back go back to the place where it was is let's see, it's a bit let me see, I have some messy code here. Essentially, we need to do the opposite. So instead of applying the class name to the album before we render, we need to first render, so we need to first call that event detail resume function.
JASON: OK.
MAXI: And after we do that, we will have to find the album image and apply the class.
JASON: So same code here? JA.
MAXI: Yeah, let's do the same code.
JASON: We'll abstract that one out and we'll do one called add image class and get the, we don't need anything, we can drop this all in because it's all self contained.
MAXI: Actually, let's path name here. We're going to have to change that.
JASON: Path name.
MAXI: Yes.
JASON: OK. Make this a little bigger to see what's going on. We've got our add image class, and so, in here I'm going to change this out to be add image class, location path name and that replaces all of this. OK. And here, we want to add image class.
MAXI: And we want to pass the location where we come from. But at this point, we've already updated the URL. If you go to location path, we'll get the slash. We can get it from turbo, but we have to do it in two ways because depends if it's the first navigation or I'm sorry, depends on whether you are navigating back with back icon on the browser or if you're navigating via a link. So to get the path, we can call turbo.navigator. Last visit. And this can be undefined. We have to do the optional chaining with the and location path name. If it doesn't exist, and get it from turbo, navigator current visit referrer path name.
JASON: Refer.
MAXI: Yes, path name. It's a bit clunky. But this way we made sure we get the path name always. Theoretically speaking, this will now do the thing.
JASON: Doesn't like null reading selector, at image class.
MAXI: One thing, this is one of these catches with turbo. Where we call event.detail.resume, this is, this is not updated right away. This is an asynchronous call that updates on the next, on the next animation frame. So we need to let's try this. I don't know if this will work, but let's try this.
JASON: We're going to try going here and we're going to try to go back.
MAXI: Yeah.
JASON: It started to work and it bailed. Did you see that?
MAXI: I think we have the other slide in animation, if you comment that on the CSS, you can comment that out. Yeah. Those things. If you go back.
JASON: Yep. OK. I think this is because we've got the animation duration is out of sync.
MAXI: Yes.
JASON: Boom, boom. Boom. Look at it go, y'all. That's very cool. That's amazing.
MAXI: Yeah, there are some improvements we can make. We're probably going to run out of time. But yeah. There's some other things we can do. We can talk about them. So, for example, in this case the UI's very or the API we're using and also because we're running a local host is super fast. All right, it's super fast, so when you click on a link, the next one loads very quickly. If we had a slow API, what would happen is that you click on a link and then, this screen will freeze until we get the next we'll get the data and then, it will animate.
JASON: Mm hmm.
MAXI: One way to kind of fix this is to instead of waiting for the next page to load, we can transition right away using a sort of template with the information we already have SKRA JA oh, OK.
MAXI: It's a bit more work to implement it. We can show, show you a link to another demo I have.
JASON: Yeah, let's look the a demos. We've got about ten minutes here. Maybe we can show some of the stuff you've built advance features.
MAXI: Yeah, this one, I can send it in chat, as well.
JASON: OK, heading over here.
MAXI: This handles that behavior where if you click on the one on the left, for example, you know, that will load pretty quickly. But the one on the right has a slow down on the server.
JASON: Got it.
MAXI: It will navigate instantly. But then, when the data comes back, we'll update the UI.
JASON: That's super cool.
MAXI: Yeah, that's one way to handle it. And I guess if you're building a mobile app for iOS or Android, you will use something like this, as well. You want to wait until you have the next page to transition. You will just transition right away and then, when data comes in, we have an issue in the header. Yes.
JASON: This is great, though. This is like, it's extremely cool how app y this feels. Especially, because I'm making some assumptions here that the code under the hood has more conditional stuff, like you're looking for I'm assuming if you're going hitting the back button instead of navigating, you would want it to go right to left or left to right.
MAXI: Mm hmm.
JASON: And we might want to do something about if you're on the home page and you click the home page, you don't necessarily want to renavigate. Which, oh, wait did that already happen? Look at that, that just handled itself. Don't have to write any code at all. This is so cool because it means that, how many lines of code did we write here? We wrote 60 lines of JavaScript and 35 lines of CSS, not counting the stuff we commented out. This is incredible how much we were able to do with very under 100 lines of code.
MAXI: Yeah, it's pretty cool. Like I mentioned, we get the behavior of the client side routing, the turbo package, but that's coming, that's probably going to come out of the box to Astro with this persistent island feature in the future.
JASON: And so, this is going to work like, will all of them have the slow transition by default? Or is it only the ones that you manually set up for slow?
MAXI: You can try. They should all work.
JASON: Look at it go.
MAXI: We have a problem on the way back because we're not doing anything special to transition back. But the transition should work even if you're just simulating the network request.
JASON: Mm hmm.
MAXI: And you can see, if you check the browser tab when you're loading one of the pages. If you're navigating let's say you're navigating. See the tabben O the browser tab is loading?
JASON: Mm hmm.
MAXI: That is the behavior of the native browser. If we were loading two separate pages, that's the behavior you'll get. And we get this kind of behavior client side through the navigation API, which we didn't talk about today, but I'm going to share a link to the docs because it's pretty cool.
JASON: That is extremely cool. Yeah. So this one is manually slowed down. But this one I've now throttled like I'm on my phone in a subway tunnel. And you still get that behavior. That is really wonderful. Let me turn that off so I don't forget that's on. And it just, yeah, just feels good. I love that it's working with the browser, you get that loading state. Let's see, Wendy has a question. Would these navigations be faster than in a traditional SPA? Since API calls are happening on the server which has faster internet than the client.
MAXI: I think it should work the same way. So traditional SPA, you probably already have you're rendering client side, as well, instead of fetching the HTML and all of the mark up from the server, you're fetching data, JSON file or response and then, rendering on the client. So, it might be faster because you have to fetch less information. But also, the browser has to do more work and you have to ship more JavaScript to render those things on the client.
JASON: Right. And so, yeah, I think that sounds like something where you sort of have to just try it. But it feels as though it shouldn't take it feels like it should kind of just more or less tie unless you're dealing with pretty large data sets. Just because, you know, the HTML on the page is rarely the size bottleneck.
MAXI: Exactly.
JASON: It's usually images and media files and you have a large JavaScript bundle.
MAXI: Mm hmm.
JASON: Yeah, that feels like it could be we're early enough in the space that I don't know we have any benchmarks or any reasonable data that we could pull from. But my hunch is that it'll be pretty like apples to apples in terms of performance.
MAXI: Mm hmm. I think so. Might be a difference, for example, a slow device that it's slow to render on that device on the client, then rendering on the server would be faster. Like, if you can fetch an HTML, it would be faster, that will work faster. You know, on a fast device like an M1 MAC, the difference is, you won't be able to notice the difference.
JASON: Got it. You dropped a link, but this is what we would be able to do to get the browser API, what you're talking with the this is the API we would use to do it?
MAXI: Mm hmm.
JASON: I'm going to share some of the links you shared, again, because I have an automation that pulls the links that I share into a doc for going into the show notes. Speaking of show notes, where should somebody go if they want to learn more? If are there any, you know, are you teaching this anywhere else? Are there docs you recommend? Any other examples? What should people go see that we haven't already shared?
MAXI: I can share a link to a blog post I wrote. Going to share it in chat, as well. Last month where I talk about some of this I talk about the navigation API, take about the page transition API, it's walk you through everything we did today, this example and can also explains, you know, under the hood how things work. This is another demo that I built where one other thing about this one, another of those I need to handle. Let's say you go to one of these movies and scroll down a little bit, you go to one of the actors, right? And then, you can either if you come back, click the browser back button, you know, the author that kind of zooms out into the slot. But if you go to the same actor, and you click one of the related movies, now it's the movie poster that expands. And in both of those cases, the transition that is happening is you're going from the person profile page to the movie profile page. You need to know what sort of animation you're looking for.
JASON: Right, right, right.
MAXI: It depends on what you click on. This is probably for more advanced topic, but something, something like a state machine to handle all of those different types of transitions would be useful in those cases.
JASON: And if you are interested in state machines, we have so many good ways a whole section on state machines. You should go dig into these on the show. We've had David on a handful of times. I've built some on my own. We had Segun came on and so many good things happened. Definitely go and check those out. All right. So let me do another quick round of just telling people what to do. Get ready, chat, I'm about to tell you your business. Make sure you go and give Maxi a follow on Twitter. I need to find lost my chat window here. Here we go. And also, this episode, like every episode is being live captioned. We've got Diane from White Coat Captioning doing all of that for us today, thank you very much for being here, Diane. And that is made possible by our sponsors, Netlify, Nx, New Relic and brand new Pluralsight thank you very much for making the show more accessible for more people. While you're checking out things on the site, make sure you go and take a look at the schedule. We've got all sorts of good stuff coming up. Next week, we'll look into a reactive backend for a web app. And this is pretty cool stuff. The convex API is real cool, real interesting, it's stuff I haven't seen before. So come back and check this one out, I think it plays with my understanding of web dev and I'm excited to see how y'all feel about it. Maxi, anything you want people to look at? Any links or things we didn't share that you want to mention before we wrap up?
MAXI: I think we shared the we already shared the docs of the official documentation of this. So, yeah, I highly recommend you play with this, it's a very cool API to play with. And if you have any feedback, please reach out to the Chrome team and just give it to them. They're looking for that. They're looking for us to play with the API and find all the clunkiness and things like that. Yeah. Have fun.
JASON: Awesome, awesome, awesome. Really good stuff going on here. I'm going to find us somebody to raid and, oh, come on. Something is going on with their search. I can't seem to actually search Twtich. Anybody open right now, chat? Send me who you want to watch. And we'll go raid them. Coding with Luke live right now? Perfect. Let's go raid Luke and thank you, Maxi so much for hanging out today. This was an absolute blast.
MAXI: Thank you for having me.
Learn With Jason is made possible by our sponsors: