 Hi there, my friend and friends. A little while ago somebody asked me if I could recreate YouTube's navigation with this cool little like underline that sort of zooms across to the selected tab that you're on and I thought it looked like a lot of fun. It's a cool little effect and it works really well. So I told them I would have a video out. I got it actually, the rough draft built pretty quickly and said the video would be here soon. And now it's been a few months and it's finally here. So I do apologize for the delay, but I think it's going to be a fun video. So let's move away from the actual YouTube and look what we're going to be starting with. Which is right here what we have. So we can dive right into it. But before we dive into that, just in case you're new here, I just want to let you know that my name is Kevin and here at my channel. It's all about helping people fall in love with CSS. And if I can't get you to fall in love with it, I'm at least hoping to help you be a little bit less frustrated by it. And today we will be diving into quite a bit of CSS and also getting into the world of JavaScript. And it should be a lot of fun. But as you notice, I am not starting from scratch. I'm starting with an existing tab system that's already working. And I want to focus really on that underline effect that's going to be going along here. So if you want to know where I'm starting from, I recently did a video on accessible tabs, and I've basically taken the same thing here. So there should be a card popping up to that one or a link down in the description below if you want to see just on how to make like a functional tab system like we have right here. The only difference I made from that original video is before when I used my arrow keys, it would actually move to each tab. But that's not how the YouTube one works. The YouTube does it where the focus changes. And then if I push enter or space bar, it will actually activate it. So the other one is considered automatic tabs. This is manual tabs. Both are completely acceptable. And yeah, I'm just changing focus instead of actually selecting the next one. But everything else is more or less the same. I also did this with just button straight up buttons like we can see here, instead of progressively enhancing everything. And that's just because they assumed that the YouTube one, they're going to be using JavaScript, they're not going to have a page with everything there, they're going to be loading in the content as it's needed. But the general breakdown is I have a whole bunch of buttons here. Each button has an area controls on it for a tab panel. And I'm just using that for my JavaScript. So I can know which tab panel when I click on one of them, I should go to. And then so if I click on tab panel one, it knows to activate tab panel one. And all my other ones are hidden. So we just add and remove the hidden stuff. If you want to see the starting code to know exactly with what I'm starting with, including my styles and my JavaScript, the starting files and the ending files are both linked down below. So you can check those out. Maybe pause just to get an idea of what's going on in here. I'll have some extra comments in the linked code down below just to make it a bit easier to follow along. But the big picture here is just we click on a tab, we do a switch tab. The switch tab is let's go down, I'm looking at the button that we click on, and it's seeing what the area controls are. So that gives me the ID, then I can remove my area selected from all my buttons. I can add a hidden to all of the panels. And then I go to the ID that I just got and remove the hidden one. And then on the button that I've clicked, I add the area selected of true. And it also will gain focus. So really quick breakdown there. Again, if you want to see sort of more of how this behavior is working, I would really recommend checking out that previous video because for this one, I just want to focus on the effect that we're going to be building. So yeah, it's also really hot here today. So I might be a little bit more shiny than usual because I'm sweating a little bit as well. So we're going to start with the CSS and then move into adding some additional JavaScript to actually get the functionality to work. And if we look here, the first thing is this is my tab list going here, right? So I have an area role of tab list, which is the list of tabs that I can switch between. So if I come, let's just actually, I closed, I minimized YouTube a second ago, but let's come and look at how YouTube actually does this, where we have like this white line that's going all the way along the bottom. And then we have the thicker line that's only on the one that's selected. So what I'm going to do, and again, this is on the tab list that has all of the tabs that I can click on. So in here, I do have, and this is what I call this semantic CSS. I didn't name that. I got that idea from Ben Myers, who's expanded on it from some other people too. And I'll link to in a video down below. And there should be a card popping up as well on a video that I did that covered this idea of semantic CSS, where we link into the semantics different roles or area attributes instead of styling it with classes since this attribute has to be on there as well. And if you're worried about the performance of attribute selectors, please do not be. So yeah, on here, I think the easiest thing to do is just to come in and add a border. So we're going to say a border bottom, we'll just do one pixel solid. And for now, we'll just do a white, just so we see, there it is, it has shown up. So white is probably a little bit too much. So we can just change this out. I like using HSL, because I can do a zero, zero percent. And then I just choose how light I want it to be. So I think maybe around like 30% would actually be pretty good. Yeah, that looks pretty decent. So we get the underlying going across. I have all this just in a, you know, YouTube has it stretching a lot bigger than what I have. But here I have mine a little bit smaller because I have it just within a wrapper, but we can always make this a bigger space as well. And just in case you are wondering my width of fit content, if I take that off, you'll see it's actually stretching the whole width of that container. I sort of wanted the underline to match the content that was inside of here. So I just didn't like that instead. So the fit content will match the width of my content unless things wrap, which actually, in this case, they won't wrap. So I could even go with a max content here and it would be fine as well. But yeah, there we go. We get my underline that is going. And now we need the thicker line on top. And this is where it's a little bit interesting, because there's different ways we could approach this. But because I need the line to be able to stretch, right? It's annoying that it jumps up every time we go and it loads the new page. But when it does do that and we are having it go across, it needs to be able to stretch. So I don't really want this to be a child of one of these elements. If I just needed a static underline, that would be fine. But what I'm going to do is let's grab this tab list. And we're going to say tab list, and I'll go with an after. And we're going to do this with a position of absolute. And because I'm using position absolute here, I'm going to come on my list itself, and I'm going to add a position of relative on here, just so this becomes the containing block or the offset parent. And then so this positioning is relative to this one. And so this is due for now, we'll do a left of zero, or you know what? Yeah, let's do left of zero, right of zero, bottom of zero, and give it a height of say three pixels, maybe four pixels, and a background that is white so we can see it show up. And it's not showing up because I forgot the content property, because it is a pseudo element. So we have to give it the content property for it to show up. And there we go, we have our line that is there. And that was the fun bit, because we could sort of, we're going to be using JavaScript so I could calculate the left and the right of all my elements or whatever for positioning purposes. But the problem then is you're animating positioning stuff, we'd be doing like our left and our right or other stuff like that. And I don't really like that idea. So for me, what actually makes more sense is using scale to shrink this down. And then that way I can, we're going to have our scale that we can animate. And then once it's the right size, I can just translate it into the right space. Am I doing that because we're looking at transforms, basically, where we can be doing all of this with more performant properties that are easier for the browser to actually animate. So we shouldn't have to run into any like jank or issues with that. Whereas when you're animating things like left and right or whatever it is, it's a little bit harder for the browser. So we want to stick to generally opacity, scale and translate, rotate, those are the ones that are sort of like the super safe ones. There's probably a couple of others that I'm forgetting off the top of my head. So what I'm going to do is I'm going to come on here and we're going to do a scale. I'm just going to use the scale property. So scale like this, you don't have to do it with scale. You can do this as a transform, but because I'm going to be doing scale and I'm going to be using my transforms, I just are the translates, I should say, I just feel like it's a little bit easier to break the two of them up for the animations. So let's say we're going to do a scale and this is we're going to do this as my bar and I'm going to do this as width and then comma and we'll start with a 100%. Just so as a default, like it shows up, maybe a default of zero would actually make more sense just in case there's a problem. It just doesn't show up at all. I'm not sure. But I'm going to go with width and I'm using this idea of the underscore here. I got that from Layoveru with the idea of it just being like I'm saying this is not a global custom property, it's locally scoped to where I'm using it. I'm also using width here instead of this instead of calling it scale because this is even though it's on the scale, the purpose of this is going to be to control the width of it. So I'm going to call it width. And then the next thing we want to do here is the translate. And again, this is its own property browser support is pretty good. Though we do want to be careful with it a little bit. But we're going to do a translate here and I'm going to do my var left comma zero. So zero will be the default. I'm going to hit save and make sure nothing moves. This does take two. So like let's just say I did 50 pixels here. You can see it is it is moving over that way. That's what I was hoping for. I wasn't sure if we'd need to come in. So that's the idea is the left will move it that way. And then the scale just for fun, let's make it like 1% or something, probably too small, 10%. You can actually see that it's over here and there's an issue with the scale. It's actually shrinking it too much. Let's do left of 10. You can see it's right there. So the issue that's happening with the scale is it's scaling it in both ways. And so I'm going to come here into a one and hit save. And so the scale, this is my scale on the X and I'm doing 10% there. Let's just say 0.5. So that'd be 50% or 0.1. And it's going to be easier instead of working with percentages, but we could do either one. But basically the scale here will control the actual scale of it, right? And we're only doing it on the X axis. And then the translate here will move it left and right. And if we want to be safe, we could put a zero here just to make sure. So this is our X translate, and that's our Y translate. Now, one other issue that we can see here by changing the scale, it's actually shrinking towards the middle. And that's just because the default transition, trans, there's too many T properties, the default transform origin, it's there in my list. So we need to say it will be left. So now you can see there we have a left. So if we did, so there we go, you know, we can come with like, I don't know, something for that. There we go. It looks like it's we have it on home as our default starting point. And then if we want to go to videos, what we'd want to do is update this. So we want to move it over, maybe, I don't know, let's say 100 pixels over a little bit more than that. And then the scale might get adjusted a little bit too. Maybe this is like a 120, whatever, it has to move over, we're going to be using JavaScript to figure all of that out for us because it'd be a lot easier. And then of course, we also need it when it does this to like stretch and then come over or stretch the other way and come on over. So let's this looked close enough, then I'm actually going to leave these as the default. I think that looks pretty good as the default starting point. So instead of having JavaScript do that, we'll start off in that spot because we got a little lucky there. And now, yeah, that's it for the CSS. Basically, we have the underlying coming on there. And now we need to move it around with JavaScript and calculate where things are. So let's go on over here. And what we're going to do, you know, this is all of our functionality for just making the tabs function. Now we want to do another, you know, a new functionality for making the underlying move. So let's do a move underline indicator. And we can do a function and call it move. Let's just move in the ktor just to make it a little bit shorter. So let's just start like super, super simply and not do anything too fancy. And say that we're going to do so we already actually let's just go look up here just to cover what we already have at my tab container, my buttons, my panels, the tab, this tab list is what I'm going to be focused on. Since that is where all of these tabs are. So I'm going to go to that tabs container. And so let's say tabs container, we're going to go into the styles on here. So style, and then we're going to do a set property. And the property that we want to set is inside of quotations here, we want to do our width, I know, let's let's start with our left. And we need to figure out what it's actually going to be. So we're going to say move it over by 100 and then say plus px. So how can I actually get this to work? Move indicator. So now let's come here. And then we're going to say when we switch tabs, all of this is happening. And we're going to also go to we're going to do a move indicator. And so if I click here, it should move over. And it doesn't look like it's working. So let's go look in my dev tools and just come up. I see, I saw, we didn't have an error. And here I see that it actually put the left 100 pixels. Oh, I don't have an underscore of left there. Hopefully you noticed that when I, when I, when I was doing it, so let's refresh now. So if I click here, it jumps on over decent start, not exactly what we need, but at least it's a decent start. So instead of doing that, let's sort of step things up a little bit. And we already have the tab that's being clicked on sort of running through all of this. So we can pass that new tab into here. So let's just say new tab and hit save. Now we also need the old tab. So we will just put in, we'll write for now, actually, yeah, we'll write old tab, old tab, new tab, just because I need two things being passed in. So the new tab actually lines up with this one, just because if I only did this, my new tab would be, it would think my new tab is my old tab here. Or you know what, let's just take off old tab for now. We'll bring it back in after it'll make a lot more sense. So we're passing new tab into here, and then we're going to get that over here to replace this 100. And that's easy to do. We need to say new tab dot offset left. And so the offset left is just the distance, the left it is basically. So if I hit save and I click on shorts, it lines up with shorts. If I click on community, it lines up with community. Now it doesn't line up on the other side. We're going to have to figure out the scaling and all of that fun stuff after, but at least it moves into the right position. So that's already like, you know, a good thing. That was a nice easy one. A nice easy win right there. So whenever I'm doing this type of thing, and you're trying to break things down, always go with the easy wins first, things that, okay, I can move it over. Done. Now's the harder part. Of course, I'm saying that's the harder part. Let's do the easier part again. Let's go for another easy win of just making sure it like slides into place. So what we'll do is let's come here. We already have this. Let's come here and do a transition as well. So let's do about 200 milliseconds. We could just do this. So it's doing everything. But we might as well say that we're doing our scale, comma, and we can also do our translate, translate over 200 milliseconds as well. So now it's same as what we just had, but we're being a bit more explicit. We'll probably want to come in with some timing functions on these, but let's wait until we have more of the functionality there and then we can make it look a little bit nicer as things are stretching and all of that. Now, to be able to get the width that we actually want it to be, I think is the next sort of logical step. And so to be able to do that, we're still focused on this new tab that's right here. And we want to figure out how big it is. But because I'm using scale and the original scale is like the full length of this, we basically need it to be a percentage of that full width. So let's figure out what that's going to be. So let's come here and we're just going to create a new variable because it'll be a little bit easier here. Just doing an offset left, I think is fine. But for we're going to have to have a little bit of a little bit longer. I like making a variable then. So let's do new tab width is going to be equal to our new tab offset width. But we need more than that. And if it was just my offset width, we'd be fine. But again, we don't want the actual width. We want to base it on the percentage because we're using scale. And I'm using scale instead of animating width just because it's better for animation performance. And it's not that much more complicated to do. So we have that. And then we want to divide it by we want the whole width of the parent that it's inside of. So there's a few different ways we could do that. But we already have the parent right here as my tabs container. So we can just do the exact same thing. But instead of on the new tab, we do this on the tab container. So let's hit save on that. And then what we can say here is that our let's duplicate this line that's right here, where we're setting the style property, we're going to set the width property. And this one will be and actually we don't need the pixels in this one, we're just going to say that it's my new tab width. So let's hit save, jump on over to the browser and take a look and see. And it's not working. So let's go and take an inspect at that. And take a look. When I let's just click, nothing's happening. Tab container, I think I called it tabs container. And so here tabs container, this one, there we go tabs container, we can fix that. Now we come there we go. And it's going to always match the right size. And so it's kind of hard to tell but you saw how off it was before when I was doing this. And like on the community, it was like cutting in the middle. So now it's always matching and we could stop there like already, that's kind of fun. Right. I think that's pretty neat. I could just click around and like watch that zoom around. And I quite enjoy that. But we want to match that YouTube behavior a little bit more closely. So let's go take a look at it as a reminder, I guess we'll move this up a little bit. So we can actually see it in action. And you can see it just sort of like goes all the way across. And then we do that and it's going to stretch. And then once it's done stretching, it sort of seems to like fall into place. So it's kind of fun. It's kind of awkward the way the page kind of reloads as it's moving sometimes. So it gets a little bit janky. And I don't even know how they're doing this. I just we could look at the dev tools to figure that out, but they have a lot more going on here. So I'm just doing it my own way. But yeah, it's looking good there. So let's let's see how we can figure out that behavior of getting it to stretch and everything. So to be able to do that, we do need to get the positioning of the new and the old tab like I mentioned. So we're going to pass through the old tab here as well. And we'll put old, old tab here. And so what I'm going to do is we need to get what the old tab was. What's the one we're actually clicking on? Because when I was doing it previously, I only needed to know the new one that was going to we never had to worry about the old one before. There's a few different ways that we could do this. I could get the event and probably get the event target or something like that. But whenever, when I first do something before we go through all of this other stuff and anything like here, we're turning our area selected to false. But before anything gets set to false, the one that currently has area selected on it is the active tab. So again, you could find another way to do this. But I'm just going to say that const current tab is going to be equal to my tabs container. And then we just do a query selector. And we want to get in here, our selector is going to be our area selected is equal to true. Just like that. So that's our, I'm saying current tab, it makes, this is our old tab, the original tab before we clicked on a new tab. So the old tab was the one that was area selected is true. We eventually remove that. So actually, because I'm adding this in, we could just set this for the old tab only. So if you want to clean things up, that would be an option right there. But what we're going to do is then we have the old tab there that's passing into here. And to me, or one of the important thing, the reason we need this is because we need to calculate the distance between them, but also is it to the left or to the right? Because that's really important for the way the animation is going to work. So let's do that one first. So let's say const new tab position is going to be equal to my old old tab. And then we have the compare document position, which is a really interesting or I found it interesting how this works. And let's say new tab because it returns a number and it's either a two or a four. So let's go and take a look at how this is going to work by console logging at first console log new tab position. Let's come in here and take a look. And we have this docked on the bottom now. I switched that in between just to make it a little bit easier. And so now if I click there, it's four and it's four and it's four. And so as long as the new tab is to the right, the number will be a four. If my new tab is to the left, the number will be a two. So anytime I go to the left, it's a two. Anytime I go to the right, it's a four. Cool. So that just makes life a lot easier. We're either getting a two or a four, we know which direction we are going in. Now, the other thing that is kind of interesting here is I'm not going to grab something. We're not going to make a variable right now for the size that we need because it is going to change a little bit depending on the direction we're moving. That might sound weird, but I'll explain it once we get to that. But just to have something that's around, let's do we're going to do a let and we'll say transition with just so we have it exists. And then we can apply it where we actually need it. But this number, as I said, the width during the transition will change depending on where the, you know, based on this tab position, is it moving to the left or moving to the right? So let's start if we're moving to the right. And if you remember, if we move to the right, it is the number four. So let's come here. I'm going to say if new tab position is equal to four. So that means if it's to the right, we want to do something. So we can even put here, if the new tab is to the right, what do we want to do? And this is where we're going to set our transition width. So transition with is going to be equal to. So what we're going to do is we want to figure out like we currently have this and we basically want to get from here to here and then also add the width of this button to it. So we want to get the distance between the two of them and then include the width of this one. So we're going to start by saying where like how far over should we move it? So that would be my new tab dot offset left, right? So that's, that would just be that's the distance from this edge over to where roughly where my cursor is right now to the left side of this one. So we want that offset width, but then we also want to add into that the size of it as well. So new tab dot, and then it would be the offset width. So that means when we're going to here, we're getting the distance from here to the left, then we're adding the space of the button to it. So let's leave it like this for a second and actually apply this. So here we're already setting all of these. Or you know what, I'm going to, for now, I'm going to comment this one out and we're going to come in where we have to do a little bit of stuff to like get it to that final value. But let's come here and just do this as my transition width for now transition width. Now this isn't going to work perfectly yet because it'll work, but it's going to get way too big. And that's just because this needs to be a percentage. If you remember, when we originally set that up, we had this right here. So we're actually going to steal the same thing because we want it to be a percentage of the size of the total container. So we're setting the property of width to transition width divided by the overall size. And now it's sort of going to work, but there's going to be a little bit of a problem. So if I click here, see, it is working. And if we go back now, it's, you know, things sort of sort of work, but it's kind of weird. And it's a little mucked up, but at least it's sort of working. The problem now though is we need to get, let's just refresh that. So if I click on shorts, what's happening is we're getting refreshed that again. So if I'm on home and I click on playlist, it's going and it's moving the left to the left side, but it's taking that entire size into account. And so it's way too big than what we actually want it to be. And that's just because it also depends on where it started from, because remember, this is only our transition width. It's not the overall width of what we want. But it basically, when we do this, like if I'm on home and I click on playlists, you know what we're going to do actually, just we're going to turn off this left one as well. It's going to highlight better what's happening. If I click on shorts, you can see there it's going from home. And actually, this one looks like it's working because it's ending at shorts. This is perfect, exactly what we want. If I'm on shorts and I go to community now, though, well, now it's going all the way over. Let's go to here. And now for some reason it got even bigger. This is where the direction does matter. So from home to here, it looks good. But then as soon as, right, we need to like subtract where it was starting from. Well, that's easy because we can subtract where it was starting from by saying old tab dot offset left, because that's where it was originally starting from. So the first one always was working. But now when I was going from shorts to community, it would just stay here and stretch even further. Now it's not stretching even further. It's still staying on the left because I'm not moving that one currently. But what it's doing is it's looking at, let's refresh again. So from home to videos, it's looking at the distance from this button to this one, and it's taking both of them. Now if I click on shorts, you see it doesn't really get much bigger because it's only looking at the distance from videos to shorts. So now it's actually working much, much better. And we can actually add that offset left back in. So when I click here, it works. And when I click here, it gets a little bit bigger. Now you'd think we could just come in here and add this offset left to it now. But the problem is when I do that, it moves. So it's stretching and moving at the same time. Basically, we need it to stretch. And then we need it to stretch to here and then sort of shrink down to there to get to its final point. So this left that we have here, I'm going to cut it out. So this one, I'm going to leave this here. We're just going to leave it just like that. What we're going to do is then come down and we're going to use a set timeout. So in the set timeout, we're going to do a function. So nothing too fancy, but we're just going to be waiting for a little while. And I'm going to drop that the move left into here. And we're also going to take this one and bring it into here as well. And so basically, we're going to stretch out. And then after a certain amount of time, it's going to shrink down to what the original values should have been when we calculated those. So how long should it take? Well, it should take about 200 milliseconds, because that's how long our transition was. So let's go take a look. And now if I click on shorts, it works. So we're stretching here to this width. So we know it's going from there stretching out. And then after it's stretched out, it's finished stretching after 200 milliseconds. And then after that's done, we're going to reset the left to the new tab offset. Now what we have is back to that original behavior was matching the size that we wanted it to be. But you'll notice when I go this way, it really bugs out. And that's where the reason it's bugging out is because it's now following, we were going from the home to the playlist and it works fine. You'll notice that little bounce at the end too. We'll fix that after. But when I go this way, it's stretching that way, because that's what we told it to do. We wanted it to stretch that way. So it's still stretching that way. And then it's flying into place. So that's where I said the transition width is actually going to have to be calculated differently depending on the tab positioning. And actually the the time out here, we're going to set one of these properties also right away. So let's come here. I'm going to say else. So if it's to the right, we do this. And then else would be like, if the tab is to the left, then we're going to say that the transition width is going to be equal to. And this time what we want to do is let's say we're on playlists here. We have this already in place. So we want to keep this exact thing, but then add the distance all the way over to, let's say home, or all the way over to video. So this should stay the same. So let's start with that. We can say the old tab offset left plus the old tab offset width. And this isn't perfect. But it's very similar to what we did here. It's just sort of like the other way around. So when I click here, it's going to go there. It's not ready yet, but you can see it's at least it's not like getting gigantic like it was before. So we have the that one. And then we can just hear it then say minus the new tab, new tab, and we want it all the way on the left side of the new tab. So we can say offset left. So by doing that, now it's still not going to be perfect, but the size of it will actually be better. So if I click there, you'll notice like the size of the transition is actually the size that we need it to be. The problem is we actually need this one to start moving right away. We need that left to be part of what's happening from the very beginning. So I'm just going to copy this here and paste it here and hit save. And now so if I came here and I go to shorts, it's going to go that way. If I go to home, it's going to go that way. And so when we're going to the right, we're delaying the movement of the left property, we're saying because we want to keep right, we want to, we want the left to stay in the left position. We want it to stretch out. And then once the animation is done, we want it to shrink. When we're going in the other way, we've calculated all of the positioning. So we want all of the different things, we want it to stretch out, but when it stretches, we need it to stretch back to where it was. We need that left property to change right away. And so we have that going on now where it's stretching right away. And you'll notice that one thing where every now and then it gets that little extra jiggle and I can't reproduce it every time, but we get a little bounce that happens sometimes. And the way I've found to solve it is just to put the set time out to be slightly longer than what your animation is. Maybe there's a better way of doing this. But by doing that, I found that it works really, really well and I never run into any issues. It just ensures that the animation is completely finished before we apply those last few properties and it prevents any issues from happening. One thing that's definitely here is like, I'm repeating this here. I found it easier to do that than having a set time out here and a set time out here. And then having, right, like I'd be repeating myself that way. So I just thought it was an easier one and it doesn't if this and then we're setting it to the same value if we're moving to the left. But if you prefer not repeating that, you could definitely have a set time out here that includes both of them and a set time out inside of this that only has one. It's completely up to you which one you'd prefer. But with that, we've done it. We're here, we've got it all working. If you didn't see that original video where I just looked at the basic tab functionality, you can definitely check it out. It is right here for your viewing pleasure. And with that, I'd really like to thank my enablers of awesome who are TTLLD, Bailey, Andrew James and Rico, Michael, Simon, Tim and Johnny, as well as all my other patrons for the monthly support. And of course, until next time, don't forget to make your corner the internet just a little bit more awesome.