Seamless Page Transitions with Astro and Web Platform APIs

with Maxi Ferreira

Google Chrome's new experimental Shared Element Transition API enables single-page applications to transition from page to page smoothly and seamlessly as the user navigates, much like a native application might. Our guest Maxi Ferreira has put together a lovely proof of concept of page transitions working in a multi-page application. Join us as he shows us how it can be done with web platform APIs and a little help from Astro!

More From Maxi

Mentioned Links

Transcript

[00:00:00] Ben: Howdy, howdy, y'all. Happy Tuesday. I had to check. I had to check whether it was Tuesday, just 'cause I've been doing Tuesday and Friday streams and there was a part of my brain that just, like, blanked right as I was about to say the weekday. But happy Tuesday! It's great to be here and it's great to be joined by Maxi. Maxi, hello. Welcome to the stream!

[00:00:21] Maxi: Thank you, thank you. Thank you for having me.

[00:00:23] Ben: I'm thrilled to have you, 'cause you've put out recently absolutely one of the coolest demos I've ever seen. Like, I saw it and knew I had to, like, bring you on to show off how you did it. But before we get into that, would you like to introduce yourself for anyone who hasn't seen you around yet?

[00:00:41] Maxi: Yeah, absolutely! Yeah, my name is Maxi. I am a mostly frontend developer. I work at Help Scout, which is… we're building a customer support platform that is super easy to use and works just, just amazingly well.

And yeah, I've been recently involved, well, playing around with Astro. I am of the generation that kind of learned to code with PHP and… just, basically just raw HTML and CSS and PHP. And yeah, I guess that's why I found Astro so appealing to me that I decided to play with it. And most recently, been playing with these new transition APIs, which are really cool, so I'm excited to chat about those.

[00:01:29] Ben: Yeah, absolutely! I'm wondering if… 'cause we've done a couple streams lately over on Some Antics about Astro. Recently it was with Mayank! Let me put a link to that in the chat. But this, you mentioned the page transitions API, and this is, like… this is brand new stuff, and I was wondering if you could kind of talk about, like, what that is and what it's for and why we have it.

[00:01:51] Maxi: Yeah! So this came out… I think in May, they released an updated demo during Google I/O of this API that is called the shared element transition API. I'm gonna share a link to the blogpost. And it basically gives you a platform API that's native to the browser to do this sort of transition in between two states of the app, right? So you might be on page one and you wanna transition to page two with not just, like, having page one disappear and the next one appear, but you might want to do some sort of animation like a fade in, fade out animation, something like that. And traditionally, you were able to do this with SPAs, with a lot of JavaScript, and CSS maybe in some cases, these kind of animations. But now you're able to do them without any sort of framework, any sort of library. You can just call these platform APIs and have the transitions made for you, so, yeah.

[00:02:51] Ben: Yeah, so let's talk about that, right? 'Cause, like, you've mentioned we have been able to do this with, you know, a bunch of JavaScript, but…

[00:02:58] Maxi: Mm-hmm.

[00:02:58] Ben: …what makes it different now that this is, you know, a web platform API? Like, why does that matter?

[00:03:05] Maxi: So, for SPAs it means that we can do this with a lot of JavaScript, like I mentioned before. We could do this, for example, if you're doing it with React, you can do… you can use react-transition-group, I think it's called, or, yeah, something like that. The library that lets you do these transitions with CSS. But now we can do this with less JavaScript and with the browser handling all the heavy lifting for you. You don't have to kind of block the main thread with all this work that JavaScript is doing because the browser is gonna do all that work for you. And for MPAs…

So, really quickly, the difference between MPA and SPA. So, MPA is a traditional website that performs the routing on the server. So when you navigate from page one to page two, you go to the server. You destroy the page completely, the previous page, and you render the new one. Doing this sort of transition animations on MPAs is currently impossible. You can't do that. But this API will allow that. Not at the moment, though. The API only supports SPAs at the moment. But an upcoming version — and there's already some… there are some discussions on GitHub of how the API will look like — they will support these transitions for MPAs. So that would be very exciting.

[00:04:28] Ben: Yeah, Justin was… Justin in the chat hadn't heard what we were referring to, MPAs, which is a multi-page app, which is just, yeah, you've got a bunch of documents on a server or you've hit a server and it generates a document for you each time.

[00:04:44] Maxi: Yes.

[00:04:45] Ben: Yeah. And so… the APIs as they exist, you said that they're really built for single-page applications at the moment.

[00:04:57] Maxi: Mm-hmm.

[00:04:58] Ben: But you've been doing some work to get them to work for multi-page applications!

[00:05:03] Maxi: Right! So, I'm… we're gonna be doing a demo now with Astro, similar to a demo that I did a few weeks ago. And Astro is… it's just a fantastic framework, for starters. But it's an MPA framework. It doesn't really, out of the box, support SPAs.

So, what we're gonna be doing first, to be able to use this animation or this transition API with Astro, we're gonna be kind of transforming the Astro MPA into a sort of false SPA, as I call it, because we're gonna give it a clientside router. And after doing that, we'll be able to do the animations.

[00:05:43] Ben: Gotcha. Yeah! I think what I'm gonna do real quick is I wanna… because I wanna start diving into actually, like, writing the code, but first I'm gonna share my screen. And just to call out, y'all, go follow Maxi on Twitter. Find cool demos like this.

But I wanna show you kind of, like , the demo that you put out there, and that way we can kind of talk it through — like, see, like, visually, some of the things that we might expect to see today, so—

[00:06:14] Maxi: Mm-hmm.

[00:06:15] Ben: Here's the demo that you've put out. And so that was an actual page navigation, right? Like, you just went from page to page. This is an Astro site, but you went from page to page, but it was, it was smooth, it was immersive, it was fluid, it was continuous.

[00:06:34] Maxi: Right, exactly. Yeah, yeah, that's the kind of interaction that we're trying to build, where there's a seamless transition between one page and the next, even though both of these pages are separate pages. They're rendered on the server somewhere but we are transitioning between them on the client. Yeah.

[00:06:53] Ben: Alright. Cool! So I wanna, dive into doing this. What say you? Shall we get into some code?

[00:07:01] Maxi: Yeah!

[00:07:02] Ben: Alright!

[00:07:02] Maxi: Let's do it, yeah.

[00:07:04] Ben: Step one, I previously cloned a repo that Maxi sent me. I just wanted to drop a link in there so y'all know where we're starting from. And then, this is an experimental API, right? So it's not enabled by default?

[00:07:20] Maxi: Correct, yes. So, the first thing we need to do — we can do it now — is go to the…

I'm gonna share it in the Twitch. Ooh — no, I shared the wrong thing. Go to this. So we need—

The requirement is Chrome 104 or later. I think you're on 105, so you should be good. And this is an experimental API, so you need to enable it, because it's disabled by default. So yeah, it's called that. You have to go to that URL and change that to enabled.

[00:07:52] Ben: Oh, I have to relaunch Chrome, but you're on Chrome. Hang on just a sec.

[00:07:57] Maxi: Oh.

[00:07:58] Ben: We may… I may end up dropping this call real quick.

[00:08:06] Maxi: No worries.

[00:08:11] Ben: Hello, hello. Yeah, the perils of having to restart things mid-stream, huh? Let me… yeah. Alright. I maybe should have done this ahead of time. Are y'all still there? Can y'all still see in here? Hopefully, hopefully things are still fine. Alright. Yeah, David, you're absolutely right. It's not live unless you have to restart something! Okay! Let me join this call. Oh, man. Wait, wait, wait, wait a sec. Oh! Oh, oh, I see! Okay. I'm using a new service called Ping.

[00:09:02] Maxi: Hello, hello.

[00:09:03] Ben: There we go. Can you—

Wow, okay. I had to, like, restart everything, get access to the call. I can. Wow, okay. Wild. Ping is cool, wow! Alright! Yeah, for what it's worth, just so y'all see, if you're doing any sort of streaming, Ping — ping.gg — is rad! This is what I'm using to bring Maxi into my streaming software

[00:09:26] Maxi: I never dropped the… I never dropped the stream.

[00:09:30] Ben: Gotcha. Okay!

[00:09:31] Maxi: Yeah.

[00:09:32] Ben: Interesting. Alright, yeah, we should be good to go now. We should be good.

[00:09:37] Maxi: Perfect.

[00:09:38] Ben: So…

[00:09:38] Maxi: Oh, I can't see your screen anymore on…

[00:09:41] Ben: Oh, let me fix that real quick.

[00:09:43] Maxi: …on Ping.

[00:09:46] Ben: Did I share the right screen? I don't know that I shared the right screen. I did not share the right screen. Let me stop that. Let me… there we go! It's not live unless it's broken! There we go! Alright! Cool.

[00:10:06] Maxi: Nice. So yeah, so you told me you pulled this repo already…

[00:10:10] Ben: Yeah.

[00:10:10] Maxi: …for your local machine. And… I believe there is a branch. Are you on the "main" branch of the repo?

[00:10:18] Ben: I'm on the "starter" branch.

[00:10:20] Maxi: "starter," perfect.

I'm hearing an echo on my call. I don't know if that's…

[00:10:27] Ben: Oh, man. Are y'all hearing the echo as well? Let's see. Would you be echoing? No echo for the chat, okay.

[00:10:40] Maxi: Okay.

[00:10:40] Ben: Interesting.

[00:10:42] Maxi: Cool.

[00:10:43] Ben: Yeah, I wonder if my mic is just extra sensitive on here and you're picking up the fact that I'm playing you out of my speakers. Hopefully– hopefully it's bearable?

[00:10:52] Maxi: Yeah, no worries, no worries.

[00:10:54] Ben: Okay, cool.

[00:10:55] Maxi: Alright, so you have the repo pulled. So, this an Astro project. It has a couple of pages in it, so we're not starting from scratch really. But yeah, we're gonna start implementing something. So if you wanna run this repo with… on the CLI using npm run… npm run dev, I believe it is. There you go.

[00:11:23] Ben: Yep. Alright!

[00:11:27] Maxi: Those should hopefully open the page we're gonna be working on, which is… it's a sort of e-commerce website, more or less, for guitars. We're gonna see how it looks like.

[00:11:47] Ben: There is something about my machine whenever I run Astro. Like, it is purely in my thing, like, for whatever reason. If I'm running Astro while streaming, Astro just absolutely hangs. So—

[00:11:58] Maxi: There you go.

[00:12:00] Ben: No knock on the wonderful folks of Astro. This is purely my setup, and purely just when I'm streaming. But okay, yeah.

So let's take a look at what we've got here. And so you've built… it's a bit of a music-themed site, right?

[00:12:17] Maxi: Right. Yeah. Full disclosure, I cannot take any credit for the UI of this website because I… there's a link to a blogpost where the original UI was built, so.

[00:12:31] Ben: Okay.

[00:12:32] Maxi: Which I probably share on the screen. But yeah. This is the page we're gonna be looking at. A bunch of guitars. The UI looks really, really nice, but like I said, I can't take any credit for it. [laughs] And there's two pages, actually. There's this homepage. And when you click on one of those… one of those guitars, it'll take you to the next page, which should not be…

Yeah, we need to implement it. So we're gonna do that first, very quickly. So we might go to… dive into the code. And first of all, let's see the, yeah, that pages structure. As you can see, we have an index.astro. That's the index page, of course, where we are showing the list of products. And then we have, inside a products folder, we have another Astro component, which is… takes an ID, [id].astro, which… the route for this would be products/ and some ID, right? For example, 123.

And as you can see, both of these components, the index and the ID, they look almost exactly the same. And that's because what we're doing — and this is sort of a convention that we're using for when we transform this page into an SPA — is that we have kind of the content of the page be part of a different, a separate component, which in this case, I'm just calling a fragment. So the actual contents of the pages are in those fragments, those fragment components that are also on the pages directory.

[00:14:12] Ben: Okay. And so we wanna be implementing the product details fragment?

[00:14:17] Maxi: Exactly, yeah. So if you go there, you'll see that it's mostly just a raw HTML page with some…

[00:14:25] Ben: Yeah.

[00:14:25] Maxi: …some stuff. Yeah. So we can delete all of this. We can remove all of it. And we're gonna be doing—

So this is another thing I didn't mention. This is using Astro SSR. So by default, Astro behaves… does static site generation. So it would create, like, HTML files during build time when you create an Astro page. But now we're gonna be doing SSR, which means that we're running this on a server, so the pages will be created on the fly whenever you request one of these pages.

[00:14:59] Ben: Okay.

[00:15:00] Maxi: And that's important because we're gonna be fetching the data from an API. You could do this with SSG as well, if you don't wanna do SSR. But since we're building a website that could potentially have, you know, thousands and thousands of products, then SSG could become, like, a bottleneck in those cases. So we're doing SSR.

And the first thing we're gonna do, we're gonna create one of those code fences for Astro, which lets you execute the JavaScript that will… that will execute at build time or in the server. And here — let me actually pull up my cheatsheet. We're gonna be making our request here to the API.

[00:15:45] Ben: Okay.

[00:15:46] Maxi: So, first of all, we need to grab the ID. So as you can see — as you saw — this file is called "[id].astro," and that ID is actually a parameter that we can get. So here we can say "const," and then open bracket like we're destructuring an object. And grab the id from the Astro.params object. So that will give us the ID. When we navigate to products/123, that id variable will be 123, right?

[00:16:16] Ben: Cool.

[00:16:16] Maxi: And then we're gonna make a fetch request. So we're gonna say "const response = fetch()." And I forgot I should have sent you the URL of this before, but there you go. That's the URL of the API that we're gonna be using. This is also, like, a very simple API.

[00:16:37] Ben: Gotcha.

[00:16:37] Maxi: And we're gonna pass the… we're gonna pass to the API, to the endpoint, we're gonna append the product ID at the end.

[00:16:51] Ben: Like that?

[00:16:52] Maxi: Yep, perfect.

[00:16:53] Ben: Cool.

[00:16:53] Maxi: And I think we need to await the fetch, right? We need to await that fetch call. In Astro, we can do top-level await, so we don't need to wrap this in an async function. So in the next line, we can do… we can say const data equals to, await the response.json() of this call, right?

So we have the data now, and now, since we're now gonna be building all the HTML for the details page, we can just render an Astro component that we already created. So below the code fence, below line 5, you can render the component, which is an Astro component called <ProductDetails>.

[00:17:39] Ben: <ProductDetails>, okay.

[00:17:41] Maxi: Yeah. You might get autocomplete like auto-importing. Maybe not. If you don't, you can import it from the components folder, which is, I think, three levels… three levels down.

[00:17:54] Ben: Got it. Um… yeah. And is it a named export?

[00:18:01] Maxi: It's a default expert. I think, I believe all Astro components, when you're importing an Astro component, it's always a default export.

[00:18:08] Ben: Ah,

[00:18:08] Maxi: gotcha.

Uh, yeah, I think it's three levels back, and then components. ProductDetails.astro.

[00:18:20] Ben: Gotcha, okay.

[00:18:26] Maxi: Yeah. And to <ProductDetails>, we need to pass the data. So, <ProductDetails> receives a "product" prop, which we can just pass that data object that we have because the type is exactly as the data returned by the API.

[00:18:41] Ben: Gotcha.

[00:18:43] Maxi: Yeah. So this should work now. If we navigate back to the browser, you should see that now we get a product page for every time we click on a guitar. See if that's true.

[00:18:56] Ben: Alright! It's… there we go! Okay!

[00:19:00] Maxi: There we go!

[00:19:01] Ben: Gotcha! I do kinda wanna look at that <ProductDetails> component just to see, like — it was doing a lot of things there. Like, basically the whole page contents were in there.

[00:19:12] Maxi: Yes.

[00:19:12] Ben: But I just kinda wanna see more clearly what's going on, so…

[00:19:18] Maxi: It's just HTML. As you can see, this is just HTML. We are kind of pasting the data that we're passing in as a prop.

[00:19:25] Ben: Yeah!

[00:19:25] Maxi: But it's just HTML. There's a little bit of a loading screen, but yeah. It's nothing more than that.

[00:19:30] Ben: Nice.

[00:19:30] Maxi: Which is one of the really nice things about Astro: it's just HTML.

[00:19:36] Ben: Yeah! Yeah, it's, like, there's nothing, like, nothing unusual about any of this, except for, you know, the JSX-esque brackets there.

[00:19:46] Maxi: Right, exactly, yeah.

[00:19:48] Ben: Cool.

[00:19:49] Maxi: So yeah! Okay, so now we have this, which is a traditional MPA, as we were talking about before. If you navigate back, that will render — that will request the homescreen again, which is another, like, another page request, right?

So the first step, before we do the transitions, we have to transform this into, like, a sort of SPA. And we're gonna do that with the Navigation API. And again, this… we only have to do this step now, what we're gonna do, because the API doesn't support MPAs at the moment.

[00:20:23] Ben: But it's going to.

[00:20:25] Maxi: It's going to. It's going to in the near future, hopefully. But for the meantime, we have to do something like what we're gonna do now, which we're gonna use the Navigation API. The Navigation API is another sort of experimental API. It's much more stable than the Transition API, but it still is not implemented across all browsers. And you can think of the Navigation API as kinda like the evolution of the History API.

[00:20:52] Ben: Okay.

[00:20:52] Maxi: If you look at clientside routers like React Router or any router for any framework that works on the client, they use a History API to kind of intercept the navigations to different pages, and instead of having the browser go to the next page, they say, "Okay, I'm gonna handle the rendering of this new component on the client."

But it's a bit clunky. It has some things that are not…

It predates SPAs, so it wasn't built with the needs of the SPAs in mind. So the Navigation API kind of changes that, so that's why I will not be using that API.

[00:21:31] Ben: Gotcha.

[00:21:32] Maxi: Alright. So, if you go to… there should be a file called spa-navigation.js in the scripts. There should be a "scripts" — yes, there you go. And it should be empty. We're importing a couple of… or, a few utility methods that we might or might not use. But for now we're gonna… we're gonna be working on that. Yeah, we can — I guess we can talk about this once we start using them.

[00:21:59] Ben: Yeah!

[00:21:59] Maxi: We're doing a bit of boilerplatey stuff that we need to do if we're kind of building this for, like, a real application. But for now, we're gonna start by doing this intercept of the navigation request, which we do by hooking into the navigate — or, sorry, the navigation object. So this there's a global object called navigation, which we can call. So we're gonna do navigation.addEventListener(). And we;re gonna listen for the 'navigate' event.

[00:22:30] Ben: Okay.

[00:22:32] Maxi: And I'm gonna pass a callback as a second parameter here, which can be an arrow function. And this callback receives an object called… we're gonna call it navigateEvent, which contains information about the navigation we're about to do.

[00:22:48] Ben: Gotcha!

[00:22:49] Maxi: So, yeah, so this is a global event and this is kind of the main difference between this API and the History API, is that with the History API, we don't really have a way to intercept all navigations. You have to manually say, "Okay, you know, when someone clicks on a link or a button or submits a form, then do this." With this, we are intercepting all types of events, which is, you know, it's handy.

[00:23:17] Ben: Gotcha!

[00:23:18] Maxi: Yeah. So… and with this — let me actually pull this up here. Alright, so… I said before we wanna know which page we're navigating to. And what we're gonna do is we're gonna grab the HTML fragment that corresponds to that page that we're navigating to, and we're gonna paste it in the DOM without having to navigate to the new page.

[00:23:43] Ben: Okay! Gotcha, alright. So yeah, we're… I mean, the file's called "spa-navigation," but like, yeah, so you're basically prefetching all of the assets, and then instead of, like, doing a hard page load, you're just saying "Actually the DOM looks like what the new page would look like."

[00:24:02] Maxi: Uh, yeah! We're basically saying, "Okay, I'm about to navigate to the product details page. Now, instead of loading the entire page, I want to fetch the piece of HTML for the body of the new page and I want to replace the body with this new content," right?

[00:24:19] Ben: Interesting. Okay.

[00:24:20] Maxi: I don't wanna request the entire page again. I don't wanna do a full page navigation. I just wanna change this bit in the DOM with the new contents of the data that we can get, right?

[00:24:30] Ben: Again, the reason we have to do this is because currently, Page Transitions API doesn't support single-page — or, sorry, multi-page applications. It only supports single-page.

[00:24:39] Maxi: Exactly.

[00:24:39] Ben: So we have to basically create our own single-page app.

[00:24:42] Maxi: Exactly, yeah, yeah.

[00:24:43] Ben: Cool.

[00:24:44] Maxi: So this is essentially a clientside route. So we're building our router on the client, which is what SPAs actually do.

So, alright, now we have this, we have to first get the URL of the page we're navigating to. Like, the destination URL.

[00:25:00] Ben: Okay.

[00:25:00] Maxi: And we can get that. We're gonna define a constant called toUrl. And we're gonna get it, we're gonna say this is a new URL, so we're gonna use the URL object. And we're gonna pass in the navigateEvent.destination.url.

[00:25:20] Ben: Okay. Destination…

[00:25:22] Maxi: So that's an URL object. Yes, ".url." That's a URL object with the page we're navigating to.

[00:25:30] Ben: Cool.

[00:25:31] Maxi: And we're gonna also grab the path from this — so, like, everything after the domain name — by saying… we're gonna define another… another constant called toPath. We're gonna say that this is equals to the toUrl.pathname.

[00:25:49] Ben: Cool.

[00:25:50] Maxi: Right. So in this case, when we navigate to the page that is, you know, localhost, /products/1, toPath will be "/products/1," right?

[00:26:02] Ben: Makes sense, makes sense.

[00:26:04] Maxi: Yeah. And now what we're gonna do is we're gonna define a handler, and this is just an asynchronous function.

[00:26:15] Ben: Okay, do we wanna do that in this callback or outside?

[00:26:18] Maxi: Yes. Yes, inside. Like, right next to the toPath, we're gonna say "const handler."

[00:26:23] Ben: Okay!

[00:26:23] Maxi: And we're gonna assign it to an asynchronous function because we want this to return a Promise. And inside of this function, we're gonna make a fetch request. And in this, we're gonna do a "const response = await fetch()."

[00:26:43] Ben: Alright!

[00:26:44] Maxi: And the URL that we're fetching is gonna be "/fragments." And then right next to it is gonna be the path, the toPath that we already defined. Yeah. And I don't think we need that second slash, because the path already starts with a slash, so…

[00:27:03] Ben: Oh, okay. Oh, I see, got it.

[00:27:04] Maxi: Yeah.

[00:27:05] Ben: Okay, this one right here. Cool, okay.

[00:27:07] Maxi: Yeah. So this will make up a request, and as you can see in the… if you show the directory…

Ohhh, okay!

If you show the pages, right. We have this fragments folder, and as you can see, the fragment… the files inside the fragments, they resemble, they have the same structure as the files in the pages directory. And this is just a convention that we're using. We don't have to follow this path, but I thought it would make it easier.

[00:27:37] Ben: Ohhh, okay. Gotcha. Gotcha, so we're — like, yeah, this is just the HTML that makes up the specific, like, page contents for this specific product.

[00:27:48] Maxi: Exactly.

[00:27:49] Ben: It gets nestled inside of this larger products page, which is what we actually saw when we did the navigation. Okay, got it.

[00:27:56] Maxi: Exactly, yeah. You can think of the fragment as just the body, the main content of the page.

[00:28:02] Ben: Got it.

[00:28:03] Maxi: And the actual page is the page with everything else: the header, the <head> tag on the HTML, and all of that, right?

[00:28:10] Ben: It is worth calling out in this case, then, like, the fact that we can do this, like, fragments thing, this is specifically because you've set up this project to do that in that way.

[00:28:19] Maxi: Exactly, exactly. And this is a convention because we can request the fragment for any path just knowing the pathname. We don't have to do things this way. We can define fragments in a different folder and just have, like, a map, maybe, a map of paths to fragments. We can do that, definitely. But in this case, it will make it easier if the pathname of the fragment just matches the pathname of the page.

[00:28:47] Ben: And just to, like, I guess, really, really hammer this home, I can go here, like, into my browser, and I can say "/fragments/products/5," and we're gonna get…

[00:28:58] Maxi: Exactly.

[00:28:58] Ben: Yeah! Yeah, yeah, yeah.

[00:29:00] Maxi: We're gonna get just the HTML.

[00:29:01] Ben: Yeah, it doesn't have the CSS that was applied at the page level, but yeah, okay, gotcha.

[00:29:06] Maxi: Exactly.

[00:29:07] Ben: Alright, alright.

[00:29:09] Maxi: It's just the HTML. No CSS, no <head> tag, no header, as you can see.

And since it's just HTML, we can grab it as a piece of text. So, now we can do… we can define, on the next line, we can say that we wanna get the response.text().

[00:29:27] Ben: Okay. I'll just call that, I guess, "html," perhaps.

[00:29:31] Maxi: Yeah, exactly.

[00:29:33] Ben: Okay.

[00:29:34] Maxi: ".text," and then you have to await this as well.

[00:29:38] Ben: Okay, and that's a method. Okay.

[00:29:40] Maxi: Exactly, yeah.

[00:29:42] Ben: Gotcha.

[00:29:43] Maxi: Mm-hmm.

Alright. So now we have the HTML for the new page and what we need to do is we're gonna update the contents of the page. Now, there is a function already that we are importing that's called updateTheDOMSomehow. We can see how that looks like, but it's very simple. Like, we're not doing anything super fancy here. We're just grabbing an element.

[00:30:05] Ben: Okay! [giggles] Gotcha.

[00:30:06] Maxi: And we're just setting the innerHTML.

[00:30:09] Ben: Okay, alright.

[00:30:11] Maxi: And this will work really well for us. It has a couple of gotchas. One is that using innerHTML doesn't support streaming of HTML. So if we had… I don't think Astro supports kinda SSR streaming. But if it did, if we were building an app with a backend that supports streaming, innerHTML won't work that well for that. So for that, there are some… there's another method we can use instead of this. But the main drawback of this approach is that when you call… when you set the innerHTML of a <div>, it won't execute scripts automatically. So if the page has JavaScript, if the page that we're loading has any JavaScript, we will have to kind of recreate those <script> tags using the DOM for them to execute. But we don't have any JavaScript to worry about in this case, so we don't…

This works really well for us.

[00:31:06] Ben: I am thinking that at one point, when I cleared out the product page, I think I…? Or was it here…? I think I might have cleared out a content <div>. Do I need to add that back in somewhere?

[00:31:22] Maxi: Ah, no. No, no, no.

[00:31:23] Ben: Okay, cool.

[00:31:24] Maxi: No, this content <div> is in the layout.

[00:31:27] Ben: Okay.

[00:31:28] Maxi: We can take a look at this inside of layouts, Layout.astro. And it's just a <div>.

[00:31:34] Ben: Okay, yeah.

[00:31:36] Maxi: So yeah, so you can call that updateTheDOMSomehow function.

[00:31:43] Ben: Gotcha - yeah, there it is. Okay, cool, cool, cool. Yep, alright. Yeah, we'll update the DOM somehow. Alright. And that's a beautiful name by the way. I wish I could… I wish I could bring a name like this into production.

[Maxi laughs]

[00:32:03] Maxi: It is a good name actually, right? It doesn't say anything about how you're updating the DOM, you know? So it's a good… it's a good abstraction, I think.

[00:32:10] Ben: Yeah!

[Maxi chuckles]

[00:32:14] Maxi: Yeah, and that's all we need to do. Now if — oh, sorry, one more thing. So we just created this handler function, but we're not doing anything with it yet, so.

[00:32:21] Ben: Right, okay.

[00:32:22] Maxi: Right after the handler function, we need to call navigateEvent… dot… we're gonna call the transition — a method called transitionWhile. And inside of this, we're gonna pass… we're gonna call the handler function.

[00:32:38] Ben: You said transitionY?

[00:32:40] Maxi: Sorry, "while."

[00:32:42] Ben: "while," okay.

[00:32:45] Maxi: Yeah.

[00:32:46] Ben: And handler.

[00:32:48] Maxi: Yes, and we're gonna call the handler, actually, because this method accepts a Promise—

[00:32:54] Ben: Oh!

[00:32:54] Maxi: —which is what we're returning with the handler.

[00:32:58] Ben: I see now, okay.

[00:33:01] Maxi: Yeah. So now, if we save this and go back to our page or website.

[00:33:09] Ben: Yeah. Let me bring it back to the homepage.

[00:33:11] Maxi: Yes.

[00:33:13] Ben: And so if I click Space Fantasy here, it did subst— I mean, it looks the same, 'cause we haven't added any fancy transitions, right?

[00:33:22] Maxi: Mm-hmm.

[00:33:23] Ben: But that sort of… yeah.

[00:33:27] Maxi: Let me… let me get my video to catch up. I think it's seeing an old…

Hmm. I'm still looking at the details. Oh, are you on the details page now?

[00:33:38] Ben: Yeah, I'm on the details page.

[00:33:40] Maxi: Okay. Can you… okay, can you go back to the homepage and then click again on the product?

[00:33:44] Ben: Yeah. So, yep. I'll click on Mighty Eighties here.

[00:33:49] Maxi: Yep. Yeah, but this is… now this is, like, an SPA type of navigation. We didn't…

[00:33:54] Ben: Mm-hmm.

[00:33:55] Maxi: We didn't change the page. So, if you wanna… if you wanna make this clear, or more clear that there's a difference between what we had before and now, you can go to the Layout.astro page.

[00:34:09] Ben: Alright.

[00:34:11] Maxi: Or component. And when we are rendering the <Header>… you can pass to the <Header> there, on line 33, you can pass a prop that is called "withAnimation" and another called "withCounter." We're gonna add both to…

[00:34:31] Ben: Okay?

[00:34:34] Maxi: Yeah, we can say just "withAnimation" that with the default, I think, is true.

[00:34:40] Ben: Okay.

[00:34:41] Maxi: And also "withCounter."

[00:34:44] Ben: "withCounter," okay.

[00:34:45] Maxi: Mm-hmm. And these are just visual… I guess, visual helpers to help us see the difference between the traditional MPA navigation and this SPA navigation that we're building.

[00:34:55] Ben: Gotcha. So that would be, if I go to Diamond Crafter here…

I didn't see anything different this time.

[00:35:05] Maxi: Um…

[00:35:07] Ben: Oh, it could just be that Astro's taking a while to rebuild, too.

[00:35:10] Maxi: Oh, maybe.

[00:35:12] Ben: Yeah.

[00:35:12] Maxi: So you should see, the header should have, like, a little animation.

[00:35:19] Ben: Try refreshing. Oh, okay, we've — yeah, okay. So we've got an animation. We've got a counter. This counter should not be interrupted. The animation shouldn't be interrupted.

[00:35:28] Maxi: Exactly.

[00:35:29] Ben: And I'll just go here. Yeah, counter's still going. Animation's still going.

[00:35:36] Maxi: Mm-hmm.

[00:35:37] Ben: Nice, okay! Oh, that's a good illustration of that! Well done.

[00:35:41] Maxi: Yeah. Well, yeah, I guess it helps visualize, because right now we didn't do anything super fancy, so it's hard to see the difference between what we did before and now.

[00:35:50] Ben: Mm-hmm.

[00:35:51] Maxi: But yeah! So, alright. Now that we have this, we can start using the transitions.

[00:35:56] Ben: Yeah!

[00:35:57] Maxi: And it's very… it's very simple once we have this. We can go back to the, to the spa-navigation file we were working on.

[00:36:07] Ben: Alright. Ooh… editor has decided to think about things. Okay. Cool, cool, cool. Yep!

[00:36:17] Maxi: And here in the… we can go back to the… where we're updating the DOM. We can comment that out. The call to updateDOMSomehow, comment that out. So we're not gonna be updating the DOM right away. We're gonna do it as part of a transition.

[00:36:35] Ben: Okay.

[00:36:35] Maxi: So, what we're gonna do here is we're gonna call… we're gonna create a new transition object. So we're gonna say "const transition = document.createDocumentTransition()."

[00:36:54] Ben: Okay…? And this… this right here, this is part of the experimental APIs, right? You wouldn't have—

[00:37:00] Maxi: Exactly.

[00:37:00] Ben: —or, we wouldn't have this if we hadn't turned on…

Okay, cool.

[00:37:04] Maxi: Exactly, yes, yes. This is only available because we have enabled that feature flag.

And now with this transition object, it has a start() method that we can call, so we can do transition.start().

[00:37:17] Ben: Okay.

[00:37:18] Maxi: And we can pass a callback to the transition.

[00:37:24] Ben: Alright. So, and any ol' new callback?

[00:37:28] Maxi: Any… yeah, any callback. Doesn't have to be async. And here we can… now we can update the DOM.

[00:37:36] Ben: Gotcha.

[00:37:37] Maxi: So the call to update the DOM, we can do it inside of this callback.

[00:37:42] Ben: Alright!

[00:37:43] Maxi: So if we see how it looks now, we will see that it should transition between the two pages with a sort of, fade in, fade out kind of animation.

[00:37:56] Ben: Yeah! My frame rate is borked, but yes. It's so subtle, but, like… hopefully, chat, you can see this just fine. Let me know. Hopefully the frame rate lines up there, but it's just a subtle kind of jump.

[00:38:12] Maxi: We can… yeah! And we can, actually, since this can be targeted with CSS, we can now slow down the transitions so there is more visibility.

Okay!

So, if you go to… I think there's a file called transitions.css. Should be a file over there. Mm-hmm.

[00:38:30] Ben: transitions.css.

[00:38:34] Maxi: Yes.

[00:38:34] Ben: Alright.

[00:38:36] Maxi: Yeah. Yeah, it's an empty file. And now here we can target the elements that make up the transition. So basically the way it works is, when we called transition.start(), that grabbed a screenshot of the page before updating the DOM.

[00:38:52] Ben: Okay.

[00:38:53] Maxi: And then when the callback that we passed finished executing, it grabbed another screenshot, this time containing, like, the new, updated DOM. And then the transition happened. It's just a CSS animation happening between the incoming and outgoing screenshots.

Okay.

So in this case, one is fading in, one is fading out. And the way we target those screenshots is using a pseudo-element CSS value I guess, or target.

Okay.

So we can do colon-colon, and it's called "page-transition."

[00:39:31] Ben: "page-transition," okay.

[00:39:34] Maxi: "-outgoing-image."

[00:39:39] Ben: Alright.

[00:39:40] Maxi: And here, we can pass in parentheses, after "image," we can pass the name of the element, because we can transition multiple elements. In this case, we're transitioning the entire page and that entire page has an ID called "root," so we can pass "root" here. And sorry, it's not an ID. It's a tag, so we don't have to prepend with…

[00:40:05] Ben: Okay, so like that.

[00:40:08] Maxi: Right. That's right, yeah. And we can also target the incoming image. So if we… you can add, like, a second line there to target the incoming image.

[00:40:19] Ben: Okay.

[00:40:21] Maxi: And to both, we can say that the animation duration, we want to be, like, 5 seconds, for example.

[00:40:26] Ben: Gotcha. Should we keep these two separate, you think? Or are we just gonna basically do the same thing?

[00:40:32] Maxi: We can combine them. Yeah, we can combine them.

[00:40:34] Ben: Gotcha. You said 5 seconds animation duration. There we go.

[00:40:41] Maxi: Exactly.

[00:40:42] Ben: Yeah.

[00:40:43] Maxi: And we can combine the rules, too. We don't have to duplicate the CSS.

[00:40:48] Ben: Alright, cool.

[00:40:48] Maxi: Yeah, exactly.

[00:40:49] Ben: Happy with that. Alright!

[00:40:52] Maxi: Uh, yeah. So now, if you refresh the page, you should see that it takes five seconds to the transition, which should be hopefully more visible. There we go.

[00:41:02] Ben: Okay!

[00:41:02] Maxi: This is slow!

[00:41:03] Ben: It's nice and graceful! I love that.

[00:41:05] Maxi: Yeah. [chuckles] Yeah!

Yeah, so we have… this is the default behavior, right? We didn't define anything about fading in or fading out, right?

[00:41:16] Ben: Right.

[00:41:16] Maxi: We just used the defaults. But we can, of course, change those. So I believe I sent you some animations…

[00:41:28] Ben: Yeah…

[00:41:28] Maxi: …via chat for you.

[00:41:29] Ben: Oh, you're gonna have to resend that to me because I restarted the browser!

[00:41:34] Maxi: Absolutely. Let me see.

[00:41:36] Ben: Cool, cool, cool.

[00:41:36] Maxi: So these are just CSS… regular CSS animations. So, I just sent it.

[00:41:46] Ben: And am I putting this…? I'm not putting this inside the… I'm just putting it here at the, like…

[00:41:51] Maxi: Yeah, you can put it anywhere outside of this… this file. I don't think you could do it before or after, I think.

[00:42:03] Ben: I thought my editor was gonna reformat that for me, but…

[Maxi chuckles]

I'm listening. You go ahead. I'm just gonna make this a little more readable.

[00:42:14] Maxi: Alright, so now we're gonna… now we're gonna have to split the two, because we're gonna have a different animation for the incoming and outgoing images.

[00:42:23] Ben: Okay.

[00:42:23] Maxi: So, we're gonna have to split the rule at the top.

[00:42:28] Ben: Right, yeah, let me go ahead and do that. Alright. And I'll have the top one be our outgoing.

[00:42:37] Maxi: Mm-hmm.

[00:42:37] Ben: And this bottom one be our incoming. Alright.

[00:42:40] Maxi: Yeah. So now, in the outgoing, we're gonna use the fade-out and slide-down animations. We're gonna do both of those. As you can see, we have four animations here: fade-in, fade-out, slide-up, and slide-down.

[00:42:57] Ben: Okay.

[00:42:58] Maxi: So, on the top one, we're gonna, we're gonna do "animation," and I can send you the whole screen so we don't have to type it down.

[00:43:08] Ben: Oh, if you'd like, yeah!

[00:43:10] Maxi: Yeah. Because, you know, it has the cubic Bézier curve… kind of parameters that…

[00:43:21] Ben: Gotcha.

[00:43:21] Maxi: There you go.

[00:43:22] Ben: Okay.

[00:43:22] Maxi: And I'll send you the one for the… send you the one for the incoming as well.

[00:43:29] Ben: Okie-doke. And… there we go. Alright, cool! And so this cubic-bezier, this is… so, it's doing two things. It's fading in and it's sliding down, so…

[00:43:52] Maxi: Mm-hmm.

[00:43:53] Ben: I'm excited to see what that's like.

[00:43:54] Maxi: So the outgoing image is gonna fade out and slide down, and the incoming image is gonna fade in and slide up, right, in this case.

[00:44:05] Ben: Gotcha. Back to the homepage…

[00:44:10] Maxi: Yeah. If you click on the cross icon on the top right, it should take you back to the homepage. That's a regular HTML link.

[00:44:18] Ben: Okay. Good to know.

Alright, so I'm gonna click this. The browser's — okay, yeah, the browser's not entirely happy with me, but yeah, I see what…

[00:44:31] Maxi: Yeah, this might not be the best type of animation to… maybe to demo this, but yeah, we changed the default behavior, which was the fade in, fade out thing.

[00:44:41] Ben: Yeah.

[00:44:41] Maxi: It's moving around a little bit.

Oh, yeah, we can comment it out. We're gonna go back to this animation later. We're gonna use it for something else. We're gonna reuse it.

[00:44:51] Ben: Okay!

[00:44:52] Maxi: But yeah, for now, you can comment that out. At least those, yeah.

[00:45:03] Ben: That… that…

[00:45:07] Maxi: Alright. So, so far what we did was we're transitioning between the two pages and we're transitioning them as a whole, right? We're not targeting different elements. We're just transitioning the entire thing.

[00:45:20] Ben: Mm-hmm.

[00:45:21] Maxi: But the API provides a way to transition multiple elements separately with different animations and different transitions. So what we can do here is, in this page, we can have that guitar image on the homepage to kind expand into the full image on the next page.

[00:45:38] Ben: Okay! This is, when we were showing the Twitter video you'd posted, you were doing that with movie posters, like having that just kind of move around as, like, the idea of, like, having something consistent that, like, persists between, you know, quote–unquote "pages."

[00:45:55] Maxi: Exactly, yes.

[00:45:56] Ben: Cool, okay.

[00:45:56] Maxi: Yes, that's a great way to show, yeah, to show that where this element on page one exists on page two, right? It gives you context.

[00:46:05] Ben: Yeah.

[00:46:06] Maxi: So… so, yeah! So, we can do that. And to do that, we need to do… we need to apply what's called a transition tag to an element on the page. In this case, it's gonna be the movie poster. I believe those, on the details page, they have a class called "product." If you go to… yes. The image, you have a class called "product-image," right?

[00:46:32] Ben: Okay.

[00:46:32] Maxi: Yeah. So we're gonna define that class in the CSS file.

[00:46:38] Ben: Gotcha. Alright. So let me put it in.

[00:46:42] Maxi: Yeah.

[00:46:43] Ben: Just here?

[00:46:44] Maxi: Yeah.

[00:46:44] Ben: And it's just .product — ooh — .product-image.

[00:46:49] Maxi: And we're gonna set two attributes. One is called "page-transition-tag."

[00:46:59] Ben: Okay.

[00:47:00] Maxi: And here, we can pass any value we want. We're gonna pass "product-image" as well, so that it matches the classname.

[00:47:07] Ben: Okay, "productimage," like this?

[00:47:10] Maxi: Mm-hmm. We can say product dash image.

[00:47:13] Ben: Okay!

[00:47:13] Maxi: I don't think it matters too much, but just in case.

[00:47:16] Ben: Sure.

[00:47:16] Maxi: And then the second property is gonna be one that is required. It's called "contain."

"contain" or "contains"?

"contain: paint." And the value's gonna be "paint."

[00:47:28] Ben: "paint."

[00:47:28] Maxi: This one is to… yeah, this one, we're saying that kind of that no elements are overlapping the contents of this image, right? And this is kind of like a requirement for doing these transitions that… I don't know exactly if they need the "contain: paint" property or just they need, like, a new stacking context similar to when we have, like, a "position: relative" or "position: absolute," right?

[00:47:52] Ben: Okay.

[00:47:53] Maxi: But the first tag there is the one that is defining the transition tag. And now we can…

Basically, what the browser is gonna do is that when an element has a transition tag, it will take a separate screenshot of that element.

[00:48:07] Ben: Oh, okay!

[00:48:08] Maxi: So we can transition between… we can animate that element separately from the rest of the page, right?

[00:48:15] Ben: Gotcha, so I could do… I'm expecting to be able to do something like copy these down here and instead of saying "root" in here…

[00:48:22] Maxi: Mm-hmm.

[00:48:23] Ben: …I'm expecting to say "product-image."

[00:48:27] Maxi: Exactly, yes.

[00:48:28] Ben: Okay.

[00:48:28] Maxi: You can do that, yeah. You can definitely do that. You can control the animation and the duration, all of that, of these elements.

[00:48:36] Ben: Gotcha.

[00:48:38] Maxi: Now, the only thing now is that that product-image only exists on the details page, right? Because on the index page…

[00:48:46] Ben: Okay.

[00:48:46] Maxi: …we can't really… we can't really give the tag, the product-image tag, to every image on that document, because the browser then wants to know which image to use to transition…

[00:49:01] Ben: Yeah!

[00:49:01] Maxi: …to the next page, right? So, we have to do… we're gonna have to do this with JavaScript. And we can do this in the spa-navigation file that we have.

[00:49:13] Ben: Okay! And what, are we just… you know, when you…? May I take a guess?

[00:49:20] Maxi: Yes.

[00:49:21] Ben: My guess is that on this navigation event, like, probably as part of our… somewhere in this event, we would query for the specific image in the, like, link that was clicked.

[00:49:35] Maxi: Yes.

[00:49:36] Ben: And we'll add, like, a class or some unique selector to it, and then we can use that as our transition tag.

[00:49:44] Maxi: Exactly, yeah. That's exactly what's gonna happen. And we have to do this before we transition, before we update the DOM, so…

[00:49:52] Ben: Okay.

[00:49:52] Maxi: And we can do this before starting the transition? Yeah, we have to do this before starting the transition, actually.

[00:50:01] Ben: Alright, cool.

[00:50:02] Maxi: Yeah.

[00:50:03] Ben: So let me…

[00:50:03] Maxi: So, before you call transition.start(), yes.

[00:50:07] Ben: Yeah.

[00:50:07] Maxi: And there is actually a helper function that… over there, is called getLink.

[00:50:13] Ben: Okay.

[00:50:14] Maxi: And that should give you — if you pass the path, the toPath here.

[00:50:20] Ben: Okay.

[00:50:22] Maxi: That should give you the anchor tag element or the link element for the link we're navigating to.

[00:50:31] Ben: Alright.

[00:50:34] Maxi: And now, here, we can say… we can query the… within the clicked link, we can query the product image. Yeah.

[00:50:46] Ben: Yep. And I can just do "img," right? You don't have multiple images in there?

[00:50:52] Maxi: I believe so, yeah! I think this should work. I didn't check, but yeah, we can do that.

[00:50:56] Ben: I can confirm it real quick.

[00:50:59] Maxi: You can go to Card.astro. That's it. That's the component that has the UI.

[00:51:04] Ben: Okay, gotcha. Card.astro. Yeah, it's the only image, but you know what? I'll go ahead and use the class that's been set here for that.

[00:51:15] Maxi: Mm-hmm.

[00:51:21] Ben: There we go. And my guess is we would do "image.classList.add()," and then do you have a name you like to use for this?

[00:51:34] Maxi: Well, we can add the same classname we already defined that it's product-image, right?

[00:51:38] Ben: Oh, okay, yeah!

[00:51:40] Maxi: Because the idea is to… we just wanna give it the transition tag, and that .product-image class is doing that, deciding the transition.

[00:51:47] Ben: Gotcha, makes sense.

[00:51:50] Maxi: Yeah. So the only bit of cleanup that we need to do is, when we start atransition but before the updated DOM, we want to actually remove the classname.

[00:52:04] Ben: Okay. So here, it's gonna be "image.classList.remove('product-image')."

[00:52:16] Maxi: Exactly, yeah.

[00:52:17] Ben: Okay.

[00:52:19] Maxi: And I think that should be it! I think that should now transition between the two images, if you go to…

[00:52:26] Ben: Alright. Do we need to… do we wanna…? What do we wanna have in our CSS for this?

[00:52:33] Maxi: We don't need to have anything. We can just… yeah, we can just use the default animations, because the API will also automatically transition the position and the size of the elements.

[00:52:48] Ben: Ohh! Okay! That's cool! That is so cool!

[00:52:55] Maxi: So if you go back, yeah, we have to do something else to transition back because…

[00:53:02] Ben: Oh!

[00:53:02] Maxi: …on this page, the transition… the link doesn't exist, right? We're calling the link—

[00:53:08] Ben: Yeah.

[00:53:09] Maxi: —but it doesn't exist on the page. So you might need to refresh the page here, because we…

[00:53:15] Ben: Okay.

[00:53:16] Maxi: Yeah.

[00:53:17] Ben: Cool.

[00:53:17] Maxi: But you should see, yeah. It should now expand. Yeah, you can try it with anything. Yeah. So… yeah. And then we can… yeah, we can, I guess, handle the back piece where—

[00:53:36] Ben: Yeah!

[00:53:36] Maxi: —we're navigating back. To do this… so, we have to do this with JavaScript because now we need to do kind of the opposite.

[00:53:44] Ben: Okay.

[00:53:46] Maxi: We need to find… we need to find the link after we've updated the DOM, right?

[00:53:54] Ben: Okay.

[00:53:56] Maxi: So, let me think. We can do this in a couple different ways. I think… probably the easiest way would be to…

So, there's a utility function there that I am… should be…

[00:54:13] Ben: Okay.

[00:54:14] Maxi: getNavigationType.

Yeah, getNavigationType… that basically returns a string that's either "home-to-product" or "product-to-home," based on the path that you're gonna pass, right? So it's saying if we're navigating from the homepage to the product page, it will return "home-to-product."

[00:54:32] Ben: Gotcha.

[00:54:32] Maxi: So we can call this function. We can call this function that I believe accepts, both paths, the fromPath and the toPath. Oh, we don't have the fromPath, sorry. We need to define the fromPath first.

[00:54:43] Ben: Gotcha. That's just…

[00:54:45] Maxi: And that is just gonna be location.pathname.

[00:54:48] Ben: "fromPath = location.pathname."

[00:54:53] Maxi: Yes.

[00:54:54] Ben: There we go. And then where do we wanna call our getNavigationType? Like, where's the best place in here to…?

[00:55:00] Maxi: We can add it just below that line, below line 13, I think we can just call it—

[00:55:06] Ben: Okay.

[00:55:08] Maxi: —there. Yeah, we just defined a navigationType object and… sorry, variable. We pass those. Exactly.

[00:55:16] Ben: Yep, okay.

[00:55:19] Maxi: Yeah.

[00:55:21] Ben: And then we could do "if navigationType is 'home-to-product'…"

[00:55:31] Maxi: Mm-hmm.

[00:55:32] Ben: Then we wanna do our handler and our transitionWhile, right? Like, we just wanna move both of these up?

[00:55:38] Maxi: Uh, yeah. Yeah, I think we can move both. Yeah.

[00:55:41] Ben: Is there a way you'd prefer to do it?

[00:55:43] Maxi: No, that's…

No, that will work. I think we only need to move the handler. Let me think.

[00:55:49] Ben: Okay.

[00:55:50] Maxi: Yeah. Oh, but yeah, but then we need to define the handler outside of the "if." So that's fine. It doesn't.

[00:55:55] Ben: Yeah, we'll have it in… yeah, in there. Okay.

[00:55:58] Maxi: Yeah.

[00:55:58] Ben: Otherwise, if the navigationType is going the other way, which would be…

[00:56:03] Maxi: Mm-hmm.

[00:56:04] Ben: "product-to-home…"

[00:56:05] Maxi: Mm-hmm.

[00:56:07] Ben: Then we're gonna need a different handler.

[00:56:14] Maxi: Yes, and it will be largely the same as the other handler. We can just copy that and make the…

[00:56:20] Ben: Alright.

[00:56:20] Maxi: …make the adjustments, I guess.

[00:56:22] Ben: Yeah.

[00:56:24] Maxi: So, the fetch call, everything is just the same because we are requesting the fragment from the path. But now…

Let me see. Now we have to get the…

When we get the link, we're gonna do it inside of that transition.

[00:56:46] Ben: Oh, okay. So we'll move this down, alright.

[00:56:49] Maxi: Mm-hmm.

And it's inside of the transition and after updating the DOM, right? Because the link exists on the page only after we updated the DOM with the new index page, right?

[00:57:03] Ben: Alright.

[00:57:05] Maxi: So we get the link, we get the image, and we add the classname here.

[00:57:10] Ben: Gotcha. So… like that.

[00:57:13] Maxi: Mm-hmm, exactly.

And then the removal of the classname. We also need to remove the classname. And we can do this in the… we can do it after the start() method finishes, we can chain a .then().

[00:57:28] Ben: Oh, okay. Yeah.

[00:57:30] Maxi: So that will be after the transition happens, now remove the classname. And we need to do this because if you go back to the homepage, right? If we don't remove that classname from that… from that element, then the next time you click a different element, then two elements will have the classname, right? And the browser will not know which one it should use to do the transition.

[00:57:55] Ben: Gotcha, okay. Alright!

[00:58:00] Maxi: So now it will navigate also, when you navigate back.

[00:58:08] Ben: Let me refresh.

[00:58:10] Maxi: Oh, yeah, sure.

[00:58:11] Ben: Alright, so… there, we're able to go to this page.

[00:58:17] Maxi: Mm-hmm.

[00:58:17] Ben: Then if I click our X… it does take us back.

[00:58:22] Maxi: Uh-oh.

[00:58:22] Ben: I think I was expecting a bit of a smoother transition?

[00:58:26] Maxi: It should be smooth. Maybe we're not…

Mm, let's see. What could be…?

[00:58:32] Ben: Yeah, it just kind of jumps back.

[00:58:35] Maxi: Yeah, it could be because it's not finding…

Oh, I think—

Go back to the… yes, because I think…

Hmm. Oh, when we are fetching the link, when you were calling the getLink() function, I think we need to pass the fromPath here, because we are fetching the link that has the URL of the page we are currently at. Remember that in this scenario, we are on the product page, and we're navigating back to the homepage.

[00:59:09] Ben: Oh, okay! Okay. Right. And so having done that, we think that this is gonna fix our transition.

[00:59:19] Maxi: I believe so. Yeah, I think so.

[00:59:23] Ben: Oh, and then Mayank thinks that the image might not exist in "then." Oh! No, there we go. Nice!

[00:59:31] Maxi: Now we get back, yes.

[00:59:35] Ben: Alright!

[00:59:35] Maxi: "'image' doesn't exist in 'then.'" I don't…

Oh– yeah, that's right.

[00:59:42] Ben: Oh, yeah.

[00:59:42] Maxi: So "image"… we need to define "image" outside of the transition. So…

[00:59:47] Ben: Okay.

[00:59:47] Maxi: Yeah, we just define it as a let…

[00:59:49] Ben: Oh, yeah.

[00:59:50] Maxi: …binding, I guess. And then we… yeah, that's a good call.

[00:59:58] Ben: Alright.

[01:00:00] Maxi: Yeah.

[01:00:02] Ben: Give it another refresh. That seems to be necessary.

Nice. Then I'll let that finish, 'cause why not? And… this just kind of zapped back there, but it's very cool! That is so cool.

[01:00:22] Maxi: Yeah!

[01:00:23] Ben: Alright!

[01:00:23] Maxi: So, yeah, so we're not handling things like latency in this example, but we could definitely— like, for example, if the request takes too long, right? We'll run into a case where we click on the link, the URL updated to the new page… but I'm still looking at the old page because the new page fragment is still fetching, right?

So we can fix this in a couple ways. One is using prefetching. That kind of solves the issue, but we can't prefetch every product detail page. We have to choose, you know, the most popular one or something like that.

[01:01:02] Ben: Sure. Makes sense.

[01:01:03] Maxi: Yeah. Or we could use some templating as well. If we wanna update… we could update to a template instantly and then let it finish, and once it finishes, it will load. Once the actual data is loaded, we replace the template with the actual data.

[01:01:24] Ben: Okay! Yeah, that makes sense.

[01:01:26] Maxi: Yeah.

[01:01:26] Ben: Okay.

[01:01:27] Maxi: So we can try to do something like that. Let me see if I have…

[01:01:30] Ben: Sure!

[01:01:31] Maxi: Yeah.

[01:01:33] Ben: Let's go for it. Do you…? Yeah, you're checking your notes for that?

[01:01:37] Maxi: I'm checking my notes. Let me see. I don't really have very good notes, so let me see if I can make sense of what I have. But yeah, basically we wanna do—

Oh, and we can try this. If you wanna remove the animation delay so that it's… or make it, you know, not so big. Maybe, like, a second or something.

[01:01:57] Ben: Yeah. Just a second all around?

[01:02:00] Maxi: Yeah, just a second should be… should be good. And then what we can do is we can delay the request from the API. So if you go back to that [id].astro fragment, there you go. You can pass a query param to the URL of the API called "delay." You can pass it, like, three seconds or something.

[01:02:23] Ben: Okay, is that just gonna be "3"?

[01:02:24] Maxi: Oh, sorry, it's "3000."

[01:02:26] Ben: "3000," okay.

[01:02:27] Maxi: Yes. So now, you should see what we were talking about, where you're on the homepage, you click on a product, but that product takes three seconds to load, but you're still looking at the homepage, right? You don't have any visual cues.

[01:02:43] Ben: Ah. Oh, yeah.

[01:02:43] Maxi: You don't have any cues that something is happening.

One thing that I didn't mention: if you go back… if you go back and do this again, you'll see that the favicon in the browser kind of starts spinning when you…

[01:02:57] Ben: Yeah.

[01:02:58] Maxi: …when you are loading the new page, right? And that's because we get this for free from the Navigation API. This is something that you don't get when you're using the History API. You have to kind of build this yourself. But with the Navigation API, we get… it's basically mimicking a native browser navigation, and you get all of the benefits that come with that. You get all the, you know, all the accessibility features that come with that because you are announcing, you know, to the user—

[01:03:25] Ben: Oh, bless.

[01:03:25] Maxi: —that the page is actually loading.

[01:03:27] Ben: Oh, bless. Oh my god. Finally! Finally! Routers that make sense!

[01:03:35] Maxi: Yes. Yes. And it has a bunch of other features, like it handles scrolling automatically. It handles focus state. So it's a really good API.

[01:03:44] Ben: Okay.

[01:03:46] Maxi: So… so, yeah! We have a slow network request now.

[01:03:51] Ben: Yeah.

[01:03:52] Maxi: And we can do what we talked about, where we're gonna, instead of waiting for the data to finish and then update the DOM, we're gonna update the DOM right away with a template and then once the data comes in, we're gonna replace the template with the actual data.

[01:04:07] Ben: Okay.

[01:04:08] Maxi: So, first thing we need to do is we need to go to…

Let's go to the index.astro fragment.

[01:04:17] Ben: Got it! And… there we go, okay. index.astro fragment.

[01:04:26] Maxi: Mm-hmm.

Let me… there we go. So here we are mapping this—

We didn't see this file before, but we're just making a fetch call to some API endpoint, we're mapping through the products, and we are just rendering the <Card> components, right?

So what we can do here is, below the <Card> component, in the next line, we can actually render the <ProductDetails>

I forgot how it's called. <ProductDetails> component that is the one that we are using for the product details fragment, so it's the same Astro component.

[01:05:05] Ben: Okay!

[01:05:05] Maxi: And you get the auto-import, which is really nice.

[01:05:10] Ben: Yep.

[01:05:13] Maxi: And here, we're gonna pass the product. We're gonna pass the product object, just like that product we already have defined there, so we just say "product={product}."

[01:05:27] Ben: Alright, gotcha.

[01:05:28] Maxi: Yeah. And I forgot. So we're gonna wrap this details, the product details, in a <template> element. So we're gonna create a <template> with an ID. We can give it whatever ID we want, as long as it has kind of the ID of the product.

[01:05:46] Ben: Okay, wait. So ID of the product, you said? So like…

[01:05:51] Maxi: Yeah, so it can be, like, "product-id," for example.

[01:05:55] Ben: Okay, gotcha. I see.

[01:06:01] Maxi: Yeah, exactly. So now we have this <template>, and here we have one <template> for each card on the page. In theory, if we wanna…

If we were doing a real website, I guess for performance reasons, we'd only have one <template>, and then we'd replace the contents of that <template> with the product that was clicked. But in this case, this is much easier, so we're gonna just do that.

[01:06:28] Ben: Okay.

[01:06:29] Maxi: So now if you go back to the spa-navigation page. I'm sorry, file. When we are doing the home-to-product navigation, when we are updating the DOM, instead of updating the DOM here with the HTML, we're gonna… we're gonna get the template data from the DOM. It should already be a template here. So we're gonna say document.getElementById(), and the ID of our <template>.

[01:07:04] Ben: Okay. document.getElementById(). It should be…

[01:07:13] Maxi: Product, dash.

Oh, and we don't have the ID here. We only have…

There is a getPathId utility that should be included already. There you go.

[01:07:24] Ben: Gotcha.

[01:07:25] Maxi: You can pass the toPath to that function and that will just give you the ID from the path.

[01:07:33] Ben: Alright, and this gonna be toPath.

[01:07:36] Maxi: Mm-hmm.

[01:07:36] Ben: Alright. And use that new ID here.

[01:07:42] Maxi: Mm-hmm.

[01:07:43] Ben: And then…

[01:07:44] Maxi: And now, instead of… yes, exactly. That would be template.innerHTML. So we're gonna grab the contents of the <template> and we're gonna update the DOM with that. Right.

[01:07:52] Ben: Gotcha, okay.

[01:07:54] Maxi: So now, we can do in the…

We're still making the fetch, so this is still not gonna work very well, because we're making the fetch call before. So we're gonna move the fetch call to after… after we do the transition. So…

[01:08:10] Ben: Oh, okay, gotcha.

[01:08:11] Maxi: We can chain the .then() function to the start() method, yeah.

Alright.

And we're gonna move all the fetching code to this function. We're gonna do it after the transition.

[01:08:29] Ben: Gotcha. And is that just…? Oh, it would be the "const response" and the "html" and…

[01:08:38] Maxi: Yes. And we need another call to update the DOM, this time with the real stuff.

[01:08:45] Ben: Okay. I see. Got it, okay. Gotcha. It needs to be async to use that await. And then updateTheDOMSomehow(). There it is. It took a while for the tooltip to come up. And it'll just be "html," right? So, we're doing, like, two substitutions where it's like, we've already got access to the <template> information, right? We've already, like, rendered that on the page. So we can go ahead and start using that, and that's our first substitution. And then the second substitution is the, like, real new page content.

[01:09:25] Maxi: Exactly, yeah.

[01:09:26] Ben: Okay.

[01:09:26] Maxi: Yeah. So now, no matter how slow the request is for a new page, we're still gonna transition instantly, but this time with a <template>. And then once the data loads, it's gonna replace that with it.

[01:09:41] Ben: Cool!!! Okay! That's so cool!

[01:09:46] Maxi: Yeah! And another kind of nice thing is that we didn't have to create this <template> separately. We used the same exact Astro component that we are using for this view to create our <template>, so that's another cool benefit, I guess.

[01:10:01] Ben: Yeah! That is so nifty. Wow, okay!

[01:10:08] Maxi: Yeah.

[01:10:11] Ben: Alright, is there anything more you wanted to show off or work through while you've got the floor?

[01:10:18] Maxi: Let's see. We can cover…

Well, we could animate. Hm. There's some things we could—

Like, I have, on the final, if you look at the final version of the animation, it also kinda expands that block of… that kind of gray <div> that you have there.

We could do that as well with kind of the same… yeah, we can, I guess, yeah, add some niceties to this animation to make it more complete. So, alright, so we're gonna animate that box as well, and we're gonna animate the actual contents of the page. So, the text on the detail page, we're gonna make it show up.

So we need to… again, we need to capture those as separate screenshots, so we're gonna create a couple of new classnames.

[01:11:07] Ben: Okay. And are these…? Are these already in the markup?

[01:11:16] Maxi: They are. Yes, they are in the markup, so we don't need to change them.

[01:11:22] Ben: Okay.

[01:11:23] Maxi: So we're gonna call them "product-bg" for the background and "product-info."

[01:11:32] Ben: Okay, like, here we're we're changing the classes in the markup?

[01:11:36] Maxi: No, no, we don't need to… no, we don't need to change the the template. We can just define the classnames here.

[01:11:41] Ben: Okay.

[01:11:42] Maxi: And these are… these exist on the details page. They don't exist on the…

[01:11:48] Ben: Oh, okay.

[01:11:50] Maxi: …on the index page, I guess.

[01:11:51] Ben: Alright, so then it's…

[01:11:52] Maxi: Oh, it's just, it's just a dash. "product-bg."

[01:11:54] Ben: Cool!

[01:11:55] Maxi: There you go.

[01:11:56] Ben: We're good!

[01:11:58] Maxi: And we can copy what we have above, the transition tag and the "contain: paint."

[01:12:05] Ben: Alright. This needs a new name, right?

[01:12:08] Maxi: Exactly. We can just say "product-bg." And then we're gonna define another classname called "product-info." Again, just a copy of those.

[01:12:20] Ben: Alright!

[01:12:23] Maxi: Yeah, and if you look at the <ProductDetails> component, the Astro file, that should already have those classnames applied.

[01:12:30] Ben: Yeah, okay!

[01:12:32] Maxi: "product-info," "product-image." Perfect. So for the info part, we're actually gonna reuse those animations that we commented out here.

[01:12:44] Ben: Okay.

[01:12:45] Maxi: Yeah, those commented lines. We're gonna reuse them, but this time we're gonna apply them to the product-info tag. So we can just copy those. Yeah.

[01:12:57] Ben: Yep. That. product-info…

Nuke the durations here, and…

[01:13:08] Maxi: Yeah.

[01:13:09] Ben: …restore the animations. Alright.

[01:13:12] Maxi: Mm-hmm. And actually, for the incoming image one, we're gonna add also an animation delay. We're gonna set animation-delay… let's say one second, because we have these other transitions that take one second, too. So we're gonna add this to animate after the others.

[01:13:32] Ben: Okay.

[01:13:33] Maxi: Alright. So, now for the product-info, we don't need to do anything special because product-info only exists on the details page.

[01:13:44] Ben: Okay.

[01:13:44] Maxi: But product-bg, actually, that… we have the same kind of problem that we had with the image, in that we need to add the tag kind of with JavaScript. So we don't need to define any new CSS for the…

[01:14:00] Ben: Do we not need, like, incoming and outgoing images for product-bg?

[01:14:06] Maxi: Yeah, if we wanna define a specific animation duration, we can. If not…

[01:14:12] Ben: Ah, right, yeah, there's defaults. Okay.

[01:14:15] Maxi: Mm-hmm.

[01:14:15] Ben: We can just comment that out.

[01:14:17] Maxi: Yeah. We actually don't need any of those, yeah, unless we wanted to slow down the animation duration, so…

We should be okay.

[01:14:27] Ben: Yeah, so I'll just use the defaults for those. Okay, so we need the detail. Okay, so where are we…?

[01:14:35] Maxi: Yeah, we need to do the same… kind of the same treatment that we did for the image, but this time for the background, that product-bg object. So here, we need to, again, query for the product background.

[01:14:50] Ben: Are we doing that…? Sorry, are we doing this in home-to-product? We are, right?

[01:14:56] Maxi: Yeah, we need to do it in both places, because this is the same kind of scenario that we have for the image, right?

[01:15:02] Ben: Got it.

[01:15:02] Maxi: So, here we can say, yeah, background can be clickedLink.querySelector(), and I think it's "product--bg," maybe?

[01:15:16] Ben: "--bg?" I thought it was just "-bg."

[01:15:20] Maxi: We're getting… so we're getting the classname that we have defined in the <Card> component. Oh, sorry, it's "__bg."

[01:15:32] Ben: Details under— right, right?

[01:15:34] Maxi: No, we're looking at the Card.astro component.

[01:15:39] Ben: Oh, okay. Gotcha.

[01:15:40] Maxi: Yes.

[01:15:40] Ben: I see, okay.

[01:15:41] Maxi: Yeah.

[01:15:42] Ben: Gotcha.

[01:15:42] Maxi: We're targeting… we're targeting the element in the card before transitioning into the details page.

[01:15:48] Ben: Gotcha, okay.

[01:15:50] Maxi: So that's underscore-underscore, yeah.

[01:15:53] Ben: Okay.

[01:15:56] Maxi: And now we need to give it the classname product-bg.

Could have used different, less confusing classnames here.

[01:16:07] Ben: Yeah, I feel like if this were, you know, if I were going to town on, like, setting this up on like my own site, I would…

[01:16:14] Maxi: Yes.

[01:16:14] Ben: I personally would probably use a convention of, like, "home-product-background" or something like that, you know, with a…

[01:16:22] Maxi: Yes, absolutely. So in this case… so to make…

So product__bg is kind of the presentational class. It gives it the styles.

[01:16:31] Ben: Mm-hmm.

[01:16:31] Maxi: And product-bg is the class that we just defined to give the transition tag. So yeah, sorry. Sorry, it's a bit…

[01:16:39] Ben: No, you're good.

[01:16:42] Maxi: We need to do the same thing to remove it after… yeah. Right after the…

[01:16:52] Ben: Yes. Where do we typically…?

[01:16:55] Maxi: Line 20…

Below line 24, we have an image.

[01:16:59] Ben: Oh, yep.

[01:17:00] Maxi: Yeah.

[01:17:00] Ben: Thank you.

[01:17:01] Maxi: And we do the same thing for bg.

[01:17:04] Ben: Remove class "product-bg."

[01:17:06] Maxi: Exactly. And we need the same thing on the other side of the "if." Could just copy what we have.

[01:17:14] Ben: Yeah. Fotcha. That's gonna be…

[01:17:19] Maxi: Yeah.

[01:17:20] Ben: …this, but bring it down here. Then down here, we need to remove that class again, so it'd be classList.remove().

[01:17:31] Maxi: Mm-hmm.

[01:17:33] Ben: "product-bg." Cool!

[01:17:35] Maxi: And we need to do the same thing, moving the definition of bg outside of the start() method.

[01:17:42] Ben: Yes, we do. bg…

[01:17:47] Maxi: There you go.

Alright, so now, hopefully, after all of these changes, it should be a nicer sort of transition between the two pages.

[01:17:59] Ben: Right, yeah.

Yeah it was quick, but the gray box is… yeah, the gray box is, like, popping out and now…

[01:18:11] Maxi: Mm-hmm.

[01:18:12] Ben: …it'll kind of collapsed inwards.

[01:18:15] Maxi: Yeah.

[01:18:15] Ben: That's so cool. So cool.

[01:18:18] Maxi: Yeah, yeah. It's, yeah, it's a lot of fun to play with the animations.

[01:18:23] Ben: Mm-hmm.

[01:18:24] Maxi: I'm not an animations or CSS expert, as you probably guessed by my classnames. But yeah, it's a lot of fun to play with that.

[01:18:34] Ben: Absolutely. Yeah, that is so fun, and I'm super thrilled to be seeing more and more of this stuff pop up in the, like, browser. 'Cause we were talking in the green room ahead of time, but, like, having seamless transitions like this… this is one of the things that, like, native mobile experiences have, like, quote, "always had" on the browser, you know? Like, oh, you could slide between pages and stuff like that. Like, it looks nice and seamless. And the "download a document, download another document, download another document" model of the web and browsers just hasn't cooperated with that. And to do this, you've had to… like, you've had to have a whole single-page application, which, you know, as we've seen, introduces its own, like, routing issues and whatnot. And so to see more and more of the stuff land in the browser, land in the way that, like… I imagine most browsers are gonna have an option where you could turn off transitions. Like as a user, you could turn off transitions, like "prefers reduced transition" or something.

[01:19:38] Maxi: Mm-hmm.

[01:19:39] Ben: Giving people more control, rather than JavaScripting it away.

[01:19:44] Maxi: Yes. So actually I've seen a tweet recently that the upcoming version of this API will have the transitions disabled by default if the user has prefers reduced motion.

[01:19:59] Ben: Okay.

[01:19:59] Maxi: This isn't the case right now, so the transitions are not disabled by default, but they will be, and you can manually opt in to say, "Okay, I actually want to have some transitions, but I'm gonna handle them separately, right? I'm gonna listen for the media query and do, like, slower transitions or something like that." So that's still the option, yeah.

[01:20:21] Ben: Absolutely.

Alright, cool. Is there any more you wanna show off, or are we good here, Maxi?

[01:20:28] Maxi: Yeah, no, we're good. There is a lot more to see. I'm gonna share the docs, a link to the docs that have some really cool demos as well about all of the things you can do with these transitions with all the kind of the power of CSS, so.

[01:20:42] Ben: Yeah!

[01:20:43] Maxi: So, yeah.

[01:20:44] Ben: Yeah. And while you're sharing links, would you mind also sharing a link to your blogpost about what we just did today?

[01:20:52] Maxi: Yes! Let me find that. The blogpost, yeah, explains a lot of the things we've been talking about. It has some nice, I guess, illustrations that make it easier to see what's going on behind the hood when, you know, you take a screenshot, you take another screenshot, those sort of things, so… yeah!

[01:21:15] Ben: Y'all, go follow Maxi on Twitter. He's got some cool, cool demos. It was super cool to just see this pop up. Additionally, Maxi and I like to both hang out in the Lunch Dev Discord. Let me just drop a link to that in the chat. So come join us on Discord if you wanna see more cool stuff going on as well. See a wonderful, inclusive, inviting, community of practice for web developers. Come join us there. I think you'll love it.

And y'all, if you're into streams like this where you see some cool, cool aspects of building great user experiences for the web, you should catch up on more Some Antics episodes! So, you should hit the purple follow button. That way you get notified whenever we go live. But also you should know about some of the streams coming up. This Friday, we're gonna be… I'm gonna be getting a first look at the new Enhance.dev metaframework, I suppose, for web components. It brings server-rendering ergonomics, file-based routing, the stuff you love from Next.js and Remix. It brings that into the world of web components. I'm super excited for that. Next week, Todd Libby is coming back to demonstrate some of the learnings he's had from his research with the W3C on deceptive patterns and the Framework for Accessible Specification of Technologies, I think is what FAST stands for. The following Tuesday, GrahamTheDev is coming on. We're talking about convincing the business to invest in accessibility. Lots of really cool stuff. So if you wanna see this schedule and just keep apprised, you're gonna wanna go over to someantics.dev, or you're gonna wanna follow Some Antics on Twitter.

Stick around, chat. We are going to find someone to raid. But in the meantime, thank you all so, so much for being here today. And Maxi, thank you so much for your time and for putting together this awesome demo. Like, it's stuff like this that make me just so phenomenally excited for the future of the web and what we're gonna be able to do in these robust ways. So thank you so much for coming on, Maxi.

[01:23:18] Maxi: Yeah, thank you for having me. This was fun.

[01:23:20] Ben: Absolutely. And yeah, with that, I will see y'all on Friday. Bye, y'all.