skip to content

Recreate a Cool Shuffling Effect in Pure CSS

When Chris Coyier saw Toni Lijic’s hobbies widget, he wanted to figure out how it works and build his own version. He shows us how he reverse engineers CSS in this episode.

Full Transcript

Click to toggle the visibility of the transcript

Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

JASON: Hello, everyone, and welcome to another episode of Learn with Jason Today opt show, doing one of my favorite things, trying to solve a complicated problem with CSS. To do this, bringing on one of the legends of are CSS, Chris Coyier. Chris, thank you so much for being here. How are you SNRI

CHRIS: Thanks for having me, Jason! I'm so excited.

JASON: We have crossed paths in our careers, and we need to hang out together.

CHRIS: We're three or four hours away.

JASON: Really feels we should be better friends given our proximity. I have no excuses.

CHRIS: It's all me. I have a little kid and that's just the way -- yeah, thanks for having me here, we're doing this.

JASON: I'm pumped. And doing one of the things I loved when you speak. Reverse engineering, coming up with a complicated problem and talking through the process to figure it out. I'm excited to be part of that. For anyone not familiar with you and your work, you want to give us a background on who you are and what you do?

CHRIS: Yeah, I did what most people think of me as the CSS-Tricks guy. That's solid. It was a solid 15 years of writing for that website. Which was great. Probably the biggest thing I've ever done. I had the opportunity to sell it a couple years ago, DigitalOcean owns it. And Jeff Graham had his journey with the site and has been at the helm there for a bit. They're doing a good job. Warms my cold little heart. Nice to see that thing back in action. He's doing better than I ever did.

JASON: Truly is nice to see CSS-Tricks up and running. It was a sad period of time where it was quiet.

CHRIS: Yeah, just a bummer CEO. I don't know what he did for DigitalOcean, he's gone now. Probably not good, you know? Because the new guy is much cooler. And he said with his mouth to mine. Which I'm like an old school guy, man. When you say something to my face, you better mean it.

JASON: Yeah.

CHRIS: He said he was going to take care of it, he better. And a third of the way through CSS-Tricks life, we should have a better place to write these demos. That would be fun. I was working with guys at Wufoo and SurveyMonkey. I still run CodePen, that's the site we ended up building to make all these demos on with Alex Vazquez. He's up in Seattle too. Just a little triangle of PNW up in here. Yeah, so, CodePen became a much, you know, bigger thing than even CSS-Tricks was, you know? We got a whole staff of full-timers on CodePen. CodePen is a little bigger enterprise, you know? It's like a code editor in the browser. Super fans of this show, you've used it a couple times during the show here and there.

JASON: Yeah.

CHRIS: Yeah, it's like a code editor in the browser. You know, that's the pitch for it. Essentially. Because that's what people use it for. But it can be all kinds of stuff. Show off, look at the sick stuff I built. Troubleshoot, use it in educational context kind of like this. So, any reason you need to use a code editor in the browser, you can use that. But it's really front-end focused.

JASON: Yes.

CHRIS: And it's been around a long time. It's a lot different than something like StackBlitz or whatever. You got a terminal.

JASON: Right.

CHRIS: You're freaking npm scripting your way through that. That's not what CodePen can do right now.

JASON: Right. One of the things I've loved about CodePen. It seems to be the place when I'm trying to figure out how something works, I can probably find an example on CodePen of whatever frontend thing I'm trying to do. I like it, there's a community aspect to it, put up an idea, won't tell people about it. I'm getting comments on it, people are sharing it around. That's cool. You have a community of people interested in doing more and better stuff on the frontend. You've also done a ton of speaking. I tell people often you are one of my favorite people to see to speak live. Because you have this tendency when you talk about really anything your excitement for the thing you're talking about is very infectious. I remember I watched you give a talk on container queries at Cascade JS. It was the first time I was excited about them and kind of understood them what they were about. I still needed Miriam Suzanne to teach me for real later. But it's so fun to watch someone who is genuinely interested in something. That was the secret sauce to CSS-Tricks, it was you being curious and summarizing whatever it is you were curious about. I think that's kind of been the blueprint for people who have made their way in blogging and learning in public is just try to do the thing you're interested in. And remember to write down what you learned so maybe anybody else who is interested can have it.

CHRIS: I'm a big fan of that one. Writing it down, making stuff. You don't have to be full -on Jason. You're super content guy. But writing some stuff down is like good for your brain and good for your career.

JASON: Yes.

CHRIS: It just is, absolutely.

JASON: Absolutely.

CHRIS: Feel free to steel that one.

JASON: Please do. Let's talk a little bit about what we want to do today. We talked about this before we went live. This idea of you see something cool online and you want to see if it works. You have to grapple with the idea, aim stealing someone's idea if I try to build this? We are specifically today building something neither of us has met before, a guy named Toni Lijic. It's a very cool little demo. Look at it in a second. But one of the things that's interesting and challenging about art, right? Is that if you are going to make anything, you're almost certainly making something that's been made before. The trick then becomes, like, how do but show the provenance of where your ideas are coming from so it doesn't appear you're erasing the history of where an idea came from.

CHRIS: Yeah, fascinating, right? You can immediately imagine that that's just as big or bigger of a struggle in music and art and things like that where you're like, you're not the first person that's ever created something just like this. Maybe you are doing something very legitimately new, but for the most part, especially when you're early in learning, you do well to copy some stuff, you know? And there's the classic great artist steel thing that's worth thinking about.

JASON: Right. That's the core advice that every artist gives you. Don't make every idea net new, you're borrowing ideas from people who have been doing this a long time. It's on you to be creative and iterate on it. Not just take it wholesale. This is an idea I've seen somewhere else, I've plopped it down with no changes and something like that. It's not like that. You're remixing. You're building on it.

CHRIS: Especially if you can do it with a degree of credit. You can't can tell them what the exact line is. We're clicking on to Toni's site. I can rebuild it pixel-for-pixel, or I could try to. Or I know I could, I could steel his code. It's all right there, that's the nature of the web. If I did that exactly, put it on my website, you know, even if I didn't even say anything, that's more like theft. There's a line there. That's a little bit like, meh, not a great idea. But, you know, with a bunch of credit we could ask him, you know? You always could do that. Or in this case, we're gonna be a little bit more like taking a sketchbook into the museum kind of thing. Re-creating, not looking at his code. Try to re-create it just for learning purposes on and just see where it goes. So, I hope that's good enough. I also think other people have different lines. And if this crosses a line for you, super-sorry. But I asked my morals and it said it was cool.

JASON: That's how I feel about it too. We're gonna be spending a lot of time looking at Toni's work. We are not going to be looking at Toni's source code. We're just going to be trying to figure this thing out as a learning exercise.

CHRIS: Yeah.

JASON: We'll link back for credit. All the things that you would want to happen. So, let's flip over to this screen here.

CHRIS: Okay.

JASON: And I'm gonna turn on the banner. First and foremost, a quick shout out to our live captioner. We've got Amanda here from White Coat Captioning. Thank you so much, Amanda. And that is available at this link that is gonna start scrolling along the bottom of the screen here. Closed captioning is made possible by Tuple. Thanks so much, Tuple, for being part of the show. We are using Tuple a little bit today, we can pair program remotely so Chris has the ability to do things like draw on the screen, copy some code, share a link, all sorts of stuff, that make it easier to do remote pairing. Thank you Tuple for that.

CHRIS: It's pretty impressive. High five. I feel like a high five is due. I was up and running in this software in like 3 minutes. I'm a little freaked out by how nice it is.

JASON: It's really nice. We're talking to Chris. This is Chris' website. I highly recommend you should get into this website because it is not -- it's a blog unlike most blogs that I've seen, where it's actually just a person sharing some thoughts. It doesn't feel like marketing. It doesn't feel like quote, unquote, networking or thought leadership. It's someone living their lives in public. Which is what I think a lot of us should be striving to do. And looking at CodePen today. CodePen is where we're doing all of our work. If you're logged in, it will take you to your page. But CodePen is -- I love it. I use it all the time. It's a wonderful place to just kind of work through some ideas and share them for feedback and remix other people's work, all sorts of good stuff. And we are gonna be trying to build this little widget from Toni Lijic's site. Which let me drop a link to the site here and then we'll scroll down to the part we're gonna try to re-create. So, Chris, do you want to tell us a little bit about this particular widget here?

CHRIS: Yeah, sure. I mean, I can't even remember how I came across it. I'm sure it was just social media or something. I think Toni redesigned his site at some point. Who knows? Maybe it was a long time ago at this point, I can't remember the right dates. I love the Internet and people's experience and websites and stuff. Look at this thing. First of all, it's striking just to look at. The stack of photos is fun. That's what you see on CodePen, people experimenting with that thing. That's a great little idea. But, of course, you're attracted to it for a number of reasons. Look, one out of seven. It clearly has some interactivity. There's some pagination. So, what happens when you click it, you might as well just do it, the photo comes flying out. Another one comes to the top. The text changes. It has just really kind of what I might call juicy interaction. And I was like, dang, you know? It turns out there's a lot of details. Because it's not just the one photo that flies out, the rest of the stack kind of temporarily straightens, which is just like classy, you know? That's fun. I happened -- it's not every day where I have the time to just sit down and re-code something like this. Because, you know, we value jobs and responsibilities and stuff. But it was one of those kind of days. I'm sure you can relate. You know? You're sitting on the couch or whatever, and it actually is like fun for me to do this sometimes. Like a hobby. Like somebody picking up a violin to practice.

JASON: Yes.

CHRIS: I get some enjoyment out of that. It happens to be related to my career too. I feel lucky in that way that these things that I get enjoyment out of sometimes have direct effect. Although I'll tell you, CodePen doesn't need a photo widget. There's no ticket saying that's needed. But it's still practice. It's practice in crafting HTML, CSS and JavaScript.

JASON: Right.

CHRIS: It's decent. I make content too sometimes. Write and blog. Maybe it will turn into something, maybe it won't. I wasn't particularly concerned about that. Look at us now.

JASON: Here we are.

CHRIS: Pretty cool. And I was thinking about web components too. They have been on the mind. The React 19 release has better interactivity. Better, it's a little bit more head turning these days. And this was the kind of thing where I immediately think web components. Because it starts with this base HTML, that I think we can imagine as being a number of different ways. But, you know, just like it could -- it doesn't have to be interactive, this content.

JASON: Right.

CHRIS: If JavaScript didn't load on this page, you can imagine a fallback of sorts.

JASON: Kind of turn into a list or something.

CHRIS: Sure. But then we're like, well, JavaScript boots up, why don't we do it that way. And we can contain all of that -- I do think of web components as an opportunity to do like sprinkling in JavaScript. Perhaps in a way that we used to think about jQuery. I don't know. There's some HTML already there, we're gonna sprinkle in some JavaScript. But in this case, we could do it in a self-contained way. It could have other benefits too. Maybe Toni has a contact page. That's a bad example. What if you wanted three of these on the website too. Could we design it in a way that makes it more reusable than one chunk? And might accomplish that goal too.

JASON: And anybody thinking of this from a, okay, this is going to help us learn something we can use for work. Reusability, drying out components, if you're working on a design system, doing any of those things. How can I make this not just a bespoke thing that goes on this particular page, but something that I could create another instance of or move to another place. Or any of those, I think is a good exercise for just -- you know, is this code portable? Or have I built myself a little pile of spaghetti?

CHRIS: Right. Dependencies are part of it. Web components used to be part of the story, aren't really anymore.

JASON: Sure.

CHRIS: You can use it literally anywhere. Plop it in your markdown or in your Vue component or HTML. Or just use HTML. There's no limitation to where a web component can go. Especially if it's a you will kind of contained. You jack the JavaScript in there, load up the script, use the component. Something I like.

JASON: Right.

CHRIS: We don't have to dwell on that. It will only come up because the JavaScript that we end up writing for it, what little there is, will be in the format of a web component. You know?

JASON: Yeah. I get you. And just as an informal poll, anyone who is watching, get into the chat if you have worked with the web components, and no if you haven't. So, we can get a sense of how familiar everybody is with it.

CHRIS: That would be cool.

JASON: You found this effect. You're scrolling around and come to this page, and dang, this is cool. You're going to try to rebuild this. What's your process? How do you start to break something like this down?

CHRIS: I mean, I probably did open the web inspector to look. I said we aren't going look at the code. I can't help it. I think I saw a bunch of React and stuff. I don't really remember. But I feel like it's a framer motion. There's a lot of markup slinging around. I actually don't care. Whatever. I'm just gonna do this myself. I don't really mind what he did. I'm sure it's smart and cool. But I'm gonna attempt to do it, especially considering that I'm not going -- we don't have -- I don't want to use React.

JASON: Right.

CHRIS: I don't want to use any of that stuff. I'm not changing out the content, I'm going to start with the HTML and go from there. And I don't know that's how he approached it. But that's how we're gonna approach it. So, there's that. And what does one of these look like? One of them looks like, I don't know. There's a little two column grid. Photo on the left, text on the right.

JASON: Right. Yep.

CHRIS: Called the media component. There's a bunch of those and stack them on top of each other, and not render the stuff that's intentionally dynamic in JavaScript. Not put those little left and right pagination things. We're just gonna not put that in the HTML. Because that's exclusively JavaScript-powered. And if JavaScript doesn't run, those things are not useful to us. Forget them. Even one out of seven, eh, let JavaScript deal with that, you know?

JASON: Excellent. Okay. So, we're gonna re-create hobbies widget.

CHRIS: Yeah. Nice name. The credit is in the dang title.

JASON: And so -- so, how would you like -- in my mind, I always think about these as being like, you know, either a -- like I probably put a section around the entire thing, this is my hobbies section. And then probably each of the items themselves I would make a div and then I would have --

CHRIS: Sure.

JASON: -- two subcontainers in there. One for the immediate, one for the details on the right so I could kind of flex them up.

CHRIS: Absolutely.

JASON: Is that the way you typically approach it too?

CHRIS: For sure. In your case, when I think of section, I probably wrap the section all the way around where he says hobbies on his site. Because the H tag will label the section and all that. Whereas the widget, why don't we just rock a -- skip the section, just make the widget itself.

JASON: Just the widget.

CHRIS: It has to have a dash in it. Why don't you call it activities widget. You're gonna put a dash. Anybody listening, if you haven't used web components, the dash isn't just for fun. It has to have a dash in them.

JASON: That's how you guarantee it doesn't clash with a semantic HTML.

CHRIS: It's a prerequisite of web components. They have to. It's a namespace without a namespace. Now there's that. That's just a wrapper. It's behaving like a section for us. And I don't think we need any more wrappers. Why don't you just call it an activity? Like div class activity or something. And we're intentionally doing this in HTML and not JavaScript. We could be rendering all this out in JavaScript. Because, you know, another approach would be like, I don't know, pass it some JSON data and render it that way or something. There's lots of approaches. But I like this way because it's saying JavaScript doesn't need to run, you're going to get content anyway. If Toni puts them in the blog post, people get the content in the RSS feed.

JASON: Right.

CHRIS: Make the text, your class equals image or something. And class --

JASON: Oh, do we have -- oh, we do have -- okay. That will make us faster here.

CHRIS: The dot text tap will get you there. Part of me thought, hey, why use a div wrapper around the image? Because it's just an IMG. There's no other content over there. And I feel so much safer using a div.

JASON: Every time I don't, I get caught because you can't put pseudo classes on images.

CHRIS: Totally.

JASON: There's always some little thing that gets me whenever I don't wrap my images.

CHRIS: Yeah. The one that got me here, images have intrinsic sizing. They care about their width and height and aspect ratio in the way that a div doesn't. It was freaking me out, we are going to end up using CSS Grid on the dot activity, and making an image a grid cell is okay, but causing browser issues for me. Wrap your images, everybody. That's great. Maybe do the text first. I don't know. It's easier to deal with. How interesting is this? Why don't we move along.

JASON: Yeah.

CHRIS: And I'll give you this HTML. Only because handwriting HTML has limited value, I feel like.

JASON: Yeah, I agree with that.

CHRIS: I'm just gonna copy and paste.

JASON: Just take over.

CHRIS: Flipping Tuple, I got it. Select all, paste my HTML.

JASON: Okay.

CHRIS: At the top, credit Toni. Look at how close we did. I didn't tell you what to do for the most part. I just largely did what you did. It was like, ah, we're gonna need a wrapper. We'll just call it activity. Put two dives in this of it and text. And the image. And the image there is just some crap stolen from Unsplash.

JASON: Yep.

CHRIS: Maybe interesting to folks, click assets on the bottom of CodePen and click on to photos, you got Unsplash right there.

JASON: Oh, slick. Okay. Cool.

CHRIS: Yeah, just grab URLs.

JASON: Look at that one.

CHRIS: I think I used that one. That's in the slider somewhere, that little shoe tying one.

JASON: Perfect. Yeah. And that's -- I mean, and I like that because all I did was I searched for the thing I wanted and I clicked. Which is exactly whey would have done on Unsplash, but this saved me a ton of time.

CHRIS: Don't have to go over there. Good deal. That's probably all we have to do with the HTML. Like I want everything else to be controlled by CSS and JavaScript. That is really raw HTML there. And I'm fine with that. I don't think HTML needs any other jobs in this case.

JASON: Yeah. I think this is pretty -- and I like it because now like, again, as you were saying, this is what you would see if everything else is disabled. And this is good. This is clear, semantic HTML.

CHRIS: Totally. You used it on another website, it would pick up the fonts and crap that is already there.

JASON: Right.

CHRIS: We're not trying to impose any particular styles just yet. Okay. So, I thought maybe we would leave that the way it is. And before we get to CSS, we'll do JavaScript instead. Because we also need some, like, I don't know, essentially scaffolding there to --

JASON: Right, for the activities widget because it's a custom component thing, right?

CHRIS: Yeah, exactly. And then it will have everything we need and then just make it all work with CSS. None of these steps should take too long. But having the JavaScript there first makes a little bit more sense to me. We're going to use lit element, have you seen or heard of it?

JASON: I have, but I haven't used it.

CHRIS: What would be a good way to do it? As far as learning is concerned, maybe just Google it first or something. Lit element.

JASON: Lit element.

CHRIS: Lit element is the kind of name of the project. And I think there'll be probably some boilerplate. Yeah, hit get started. I don't know if they have a CodePen or anything. But they'll probably have some copy and pasty kind of code. Look, we're gonna import it.

JASON: Okay.

CHRIS: Probably just that first line, I don't think we need decorators. And that should do for now. If you drop that into CodePen, it's gonna remind you to replace the imports. And it's gonna --

JASON: Does this work if I just click this?

CHRIS: It works. It's gonna say use a CDN instead.

JASON: Yeah.

CHRIS: Rock and roll.

JASON: That's a great feature!

CHRIS: Yeah, mostly works. There's some limitations what you can pull off with CDN, you know? Okay. There's a lint of a special syntax for lit. Maybe we'll just hand-type it. All the web components use the class syntax. And class, and the widgets, maybe capital case, activities widget, sure, extends lit. The lit element, the thing you just imported.

JASON: Okay.

CHRIS: Now you have a class. The class -- this isn't that different from writing a web component from scratch. There's a few different, any class of constructer. Constructer doesn't need to do anything. It can just call super. It's construct OR.

JASON: Oh, you actually -- man, it's been so long since I've actually written a class.

CHRIS: You nailed it.

JASON: I forget how they work.

CHRIS: That's the constructer. You nailed it. This is going to work. There's a really nice one that I like to talk about called connected callback, starts with a lower case C and then capital call. It's like DOM-ready back in jQuery days.

JASON: Okay.

CHRIS: When this thing shows up, run this code. Meaning when this shows up in the DOM, like an activities widget will -- it will be like execute this little bit of code however that thing shows up. It could get slapped down the page at any point through other JavaScript or whatever. And this instance of this component will execute this code. Awfully handy, I think. Let's jump down after the class and make sure that our activities widget is a real thing. There's a web API called lower case C custom and then capital elements. Custom elements. That's just a global in JavaScript. Dot define. And then pass it a string that's the name of the HTML widget. The lower case thing. Comma, the name of your class. Now, when that JavaScript executes, now this -- now this component is real. It does absolutely nothing. But now it's like, the page and the DOM and everything is like absolutely knows that that's a real widget and we're gonna do stuff.

JASON: Got it, okay.

CHRIS: Fair enough. All right. A little trick we're gonna do after the connected callback. Now, this is a lit-ism. So, just another, I don't know, method on the class, I guess? This is just one that's specific to lit. So, create render root, camel case. Create, render root. And then just say return this. And this is just --

JASON: Okay.

CHRIS: A little trick in lit element that's just gonna say I want you to like, render all the crap we put inside of you. Meaning kind of light DOM instead of shadow DOM. Let's not dwell on that for a moment.

JASON: Sure.

CHRIS: But that means styling this is going to be easy. Make a render, and then return. See that other thing that you imported? HTML. It's gonna -- yeah, now put back ticks, okay.

JASON: It's a template tag, I had a hunch.

CHRIS: Tagged template. Now you can render HTML in here. It's going to render. Just comment out create render root, that whole thing. Just that method. And then return like a div or whatever. You can write HTML in there like you would JSX. I think what's gonna happen is it's gonna blast out the whole element. Why didn't that work? Are we seeing any runtime JavaScript errors or anything?

JASON: Yes. I screwed something up. Where did I screw up?

CHRIS: Failed to execute define. Two arguments required. That's old. Hit clear or whatever. And let's just try to run it again by just -- yeah, that works. Now do we see any runtime errors? We don't.

JASON: Hm. Did I typo this? Activities?

CHRIS: I was looking over your stuff...

JASON: Widget...

CHRIS: See if we did okay.

JASON: Activities, widget. Let me copy-paste this. Let me see -- no. Activities widget.

CHRIS: Yeah, it looks fine. To me it looks fine. Let's see...

JASON: And it should be...

CHRIS: Connected callback. We can also look -- just look in the web inspector. Are we getting a shadow DOM or anything?

JASON: Oh, wait. There it goes. There it goes. It was the connected callback.

CHRIS: It was the connected callback.

JASON: I guess because we weren't doing anything.

CHRIS: You need to write a super in there. Uncomment and write super.connected callback. Sorry, I don't know all the details. Thing might be a little bit of a lit-ism as well. There you go.

JASON: There it is.

CHRIS: You see how it wiped out our content entirely.

JASON: Yeah.

CHRIS: A little weird. Because lit element is shadow DOM by default. Shadow DOM a whole thing, it's important, and interesting to think about. We're keeping the light DOM. We want the content in the HTML. The create render root where it returns the crap from the HTML, that's what we want. Now you're gonna see it. Scroll to the bottom after it renders now.

JASON: Hey!

CHRIS: Scroll all the way to the bottom. You're also gonna see high. We're gonna get the best of both worlds in my opinion. We can dynamically generate some HTML here and we get the content from the HTML. And we're gonna need some dynamic content. I want that one out of seven on Toni's site. I want the JavaScript.

JASON: Right. We want the one out of seven, we want the previous, next. If JavaScript isn't rendering that, we want that.

CHRIS: Let's give ourselves HTML to know how to work w it. Give it a div, class.activities dash count --

JASON: What did you want do call it?

CHRIS: Activities-count or something, yeah. And then just close it. But now we have to think, all right. We're gonna need some variables to work with in here.

JASON: Yeah.

CHRIS: And variables, you can do whatever you want. But it's nice in something like lit element that's going to handle DOM updates when you change variables. We like want that behavior. Lit gives us that. That's one of the reasons we are reaching for lit here at all. Because we get cool stuff like that. So, within that activities count, use you know how in template literals you get dollar sign, the thing to use a variable. Just put dollar sign, curly bracket. This dot, because we have to pull it off the class. And then call it active activity, let's call it. Yeah. Sure. That doesn't exist yet. And then put a slash. Yeah, sure. And then put like this.count or something. It's like, how many slides are there? Those variables don't exist yet. Scroll to the bottom, you're not going to see crap. Probably a slash.

JASON: There's a slash.

CHRIS: Those don't exist. This is a lit-ism. Go to the top of the class. That's where you declare things like this. Status, space, properties equals, and then object. And inside the object we need, yeah, active activity. And this is a little bit weird. Now, it's also an object. Because this is where we're -- we're just saying that these exist is all.

JASON: Oh, I gotcha.

CHRIS: And it's a little Typescripty. You have to say type equals capital number. You have to declare a type in lit or whatever reason.

JASON: Got it.

CHRIS: There's another one and it's count, and it's also a number. So, now these things exist and I don't know what it does. I think it helps lit like understand that you've got some stuff that was probably gonna be dynamic or whatever. Now, in this constructer, because you don't really need to wait for the DOM to be ready. This.activity equals 1 or something. Sure. Now that has a value. Now if you scroll down, it will say 1/.

JASON: Yeah.

CHRIS: And then we can't count stuff in the constructer because the constructer is gonna run before the DOM is ready, yeah? You're in the connected callback. Now, how are we gonna figure this out? Now, check it out. This is like in the HTML, the element with the dash activities widget.

JASON: Right.

CHRIS: That's what this is.

JASON: And it's like a standard DOM thing?

CHRIS: Yeah. So, you can just do a --

JASON: I can do this.count = this.query selector --

CHRIS: I don't know, activities?

JASON: What did we call it? Activity?

CHRIS: Activity maybe?

JASON: Activity.

CHRIS: Activity, I guess, dot length.

JASON: I think that works.

CHRIS: Yeah. It will probably say 4. And I think that's how much HTML I gave you.

JASON: Yes.

CHRIS: Look at that.

JASON: One, two, three, four.

CHRIS: Sick. Now we have some dynamic stuff going on. Let's just make sure it's dynamic. Let's just keep making a lint more HTML. So, dynamic -- let's make those buttons real quick. So, after your div that you've already created and at the bottom, make like a NAV tag or something. Or whatever, you know? And then put within it buttons. We're going to use the properly semantic and accessible button elements.

JASON: Okay.

CHRIS: And just put a left arrow and a right arrow, you know? Make two of them. I like that technique. I would put as a attribute on the button, put like ARIA label equals previous or something. Because then you're -- it's an arrow, but it says move to previous slide. This is an incident. An opportunity to be cool to someone instead of it just being like left arrow.

JASON: Okay.

CHRIS: That looks good.

JASON: So, then we've got these.

CHRIS: Yeah, not only do we get like some dynamically-updating buttons to -- or, you know, some variables -- now let's like make a click handler on those buttons which changes those variables just to see lit element actually doing something useful. The syntax in lit element to do this is like an at -- like an at in an email. At click equals with quotes, because we're in HTML. And then within the quotes, you can do like the dollar sign curly bracket thing. Dollar sign -- and then in there, you can type this dot whatever you call it.

JASON: And we're gonna make a new --

CHRIS: Move previous or something.

JASON: Okay.

CHRIS: This is now gonna be a method on the class since it doesn't exist yet.

JASON: And we'll do that for both of these.

CHRIS: Yeah, call it move next or whatever. Move next. So, now you have to make sure that those are defined. You click them, it will throw. It will throw. That's not a method.

JASON: Okay. So, we need to make move previous and move next. And inside this what we would want --

CHRIS: Just do something really chill. This activity, minus zero or whatever, you know? When was the last time you wrote minus-minus.

JASON: We can probably just be -- we'll just count it. This.active activity.

CHRIS: Fun. Look at you.

JASON: Is it minus-minus, does that come before? You know what? We're just gonna do this because I don't actually know how that works.

CHRIS: Plus it mutates it right on there.

JASON: So, we're gonna do one of these. And set this to a min. And we'll make it this.count. And that way we should not be able to go lower than 1. Oh, shit.

CHRIS: Something's wrong. But is it, though? This.active activity...

JASON: Let's see... let's clear. Let's try it again. Ah, we're gonna go down here.

CHRIS: Yeah, sorry to make you scroll to the bottom. Console probably doesn't need to be that big. Can you drag it -- maybe not at this screen size.

JASON: Split these up.

CHRIS: Yeah, just split the tabs.

JASON: Separate the tabs.

CHRIS: Do that with the CSS work. Now you can see both. That's fun.

JASON: All right. We'll bump this up, make it little bit bigger.

CHRIS: Something hasn't re-rendered, right?

JASON: That's my fault. I have everything zoomed in and weird.

CHRIS: Look at us. We did it. Maybe do like a console.log some crap in the...

JASON: Yeah, let's see.

CHRIS: This is professional troubleshooting, everybody.

JASON: Move next. Should...

CHRIS: Help! If you hit --

JASON: Oh, oh! I know what we're doing. I know what we're doing.

CHRIS: I know what you mean, right? Like it's calling the function immediately rather than -- but you should not have to do that. You should not have to do that.

JASON: Or like, I call it.

CHRIS: Or just don't -- yeah. Don't put the parens. You know what I mean?

JASON: Okay. Okay.

CHRIS: It's gonna totally work.

JASON: This is gonna work. This is the one.

CHRIS: That killed me as a beginner.

JASON: There we go. There we go.

CHRIS: It can't go higher.

JASON: We can get to zero, though?

CHRIS: Later, you know? You go backwards, it will go to 4.

JASON: Yeah, that's nice.

CHRIS: We don't have to worry about that now. We're doing okay.

JASON: Okay. So, right now, now we're in business. We have the ability to navigate forwards and backwards.

CHRIS: Yeah, we do. But let's just do one more method. Because I think it will be satisfying. And we'll call it something like, I don't know, like make active or something. We need a function that's going to put a class on whichever activity is the one that should be at the top. You know?

JASON: Got it.

CHRIS: And make active will take an index, I guess. It's gonna take 1 through 4 and make that particular activity the active one. So, we're gonna use a class of active. How are we gonna apply that? Well, you know, usually I think of removing it first. So, we're gonna need a reference to all the activities. I would probably put that in the connected callback. We just need a variable that is all the activities. So, make like this.activities be the actual query selector all that you have already written. And then make this dot count this.activities.length, you know? So now we have both of the things that we need.

JASON: Okay. We got that. Do I need to put this up here?

CHRIS: Yeah, you probably have to do it in the right order there to make JavaScript happy. Oh, do you need to do that? It doesn't need to be dynamic. So, I don't think you need it.

JASON: It doesn't.

CHRIS: It's never gonna change.

JASON: Okay.

CHRIS: It doesn't really need to be a static property.

JASON: Okay. Let's just make sure that one change worked. That change worked. And so now we have...

CHRIS: We have this all.activities. And you can loop over them real quick, this.all activities --

JASON: What and I call it? Did I call it --

CHRIS: Or this activities? This.activities. That's fine.

JASON: Okay. ForEach.

CHRIS: Do a forEach on it, and it's the element.

JASON: So, element.

CHRIS: Sure, element.class list.remove. I know this class doesn't exist yet. But I think it's good practice to remove the class before you add it. And then the one that we pass in, we're gonna call active on it, which is gonna be --

JASON: We need the index.

CHRIS: Yeah, you already have the index. It's this.activities and then in square brackets use the index.

JASON: And it would be index -- that's right. Wait.

CHRIS: Before the --

JASON: Because our index is gonna be off by one.

CHRIS: Yeah, it probably will. The DOM is gonna be zero index.

JASON: L dot --

CHRIS: You're done with the forEach.

JASON: I'm done with the forEach.

CHRIS: Square brackets index. Fancy.

JASON: Wait. What am I doing? Plus one?

CHRIS: The variable that came in.

JASON: This is going to come in for each one. We need to do it for the active one.

CHRIS: It's not gonna do it for each one. We haven't called this function yet.

JASON: For this index. You are correct. I was thinking --

CHRIS: It's probably index, if it's all by one, we're going to fix it. Add active.

JASON: Okay.

CHRIS: In this case you need to have the real browser opened. I know we're a little space-constrained. But it might be nice to see in the DOM... yeah. You might have to just point at it and it should dig into the iFrame okay. There you go. So, the activity -- none of them have a class active on it. That maybe we would want to do that in the connected callback too is just give it the first one it finds a class of active on it. I hope everybody is following this okay. How are we doing, chat? Does this make sense? All these little internal things? Hopefully it's not too... yeah, give the first one a class of active.

JASON: All right, yep. People are following.

CHRIS: Oh, good.

JASON: So, this one should be active and it is not.

CHRIS: Activities at zero. Why not? Plus you got a red too. Or that means there's probably a runtime error.

JASON: This.activities.at is not...

CHRIS: Why not? I don't know if you can use at. I don't know what at it. At negative 1 is like just give me the last one. Yeah, just use square brackets. That will get it. Did it not re-run?

JASON: That works.

CHRIS: There it is.

JASON: I know what the problem is. It's because at is an array method. And the query selector doesn't give you an array. Yeah.

CHRIS: Some array stuff works still anyway. I don't know where the line is between list and --

JASON: I'm sure it's one of those things, it's got such a good reason for why it would do things the way it does them.

CHRIS: Surely. That other at that you wrote will probably break too, then.

JASON: Oh, yeah, good point. Good point. Let me fix that.

CHRIS: The rendering... I don't know what makes it do that. I'll look into that.

JASON: Oh, wait. Not like that. Okay.

CHRIS: Yeah. And that's the one that might have troubles with the -- the Plus one or minus one. Because you don't want to zero index the numbers that people see, because that's just nerdy and stupid. So what about it we probably will have to manipulate the index at some point. I can't remember where the best place to do it. Okay. How are we doing? Don't we need to call that make active thing?

JASON: Yeah. And if do we want to do that from here?

CHRIS: Each of those. Right after it does it, call this.make active, active activity.

JASON: And...

CHRIS: This.active activity.

JASON: And that's gonna be minus 1 in all cases because that will get us to the right index.

CHRIS: And then minusing 1 to it, it feels very strange.

JASON: Yeah.

CHRIS: Whatever, though.

JASON: But it should now, if we did this correctly, we've got our activities here, we've got one active. As we move... oh, no. Okay. So, something -- it didn't like something that I did. What didn't it like?

CHRIS: There's the red thing. It should scroll to it. Look in your browser console. There's so many errors.

JASON: Properties of undefined reading too. Oh, we're not getting our active activity...

CHRIS: Make active...

JASON: Make active activities. This activities.

CHRIS: I don't think -- it's not that. Not read the property of undefined.

JASON: I think it's saying at activities widget... okay. So, it's not liking this one.

CHRIS: Is that the exact line?

JASON: Oh, it's because I spelled it wrong.

CHRIS: Hell yeah. Really nice error.

JASON: Okay. Try this one more time. Just pretend that didn't happen. This was gonna be our first try. And look at that. It worked on the first try.

CHRIS: Look! It's moving the right DOM node. Look at that. Let's write a little CSS to make this just slightly more satisfying. In the CSS tab, and this is light DOM. You can just do it right from wherever the hell you want. In there, write like -- I don't know. Put activity widget, like without the dot. Just select our whole...

JASON: Right. Because it's an element. Activity widget. It's activities.

CHRIS: Activities.

JASON: Yeah, I think you're right.

CHRIS: And then you can nest within there. Say like dot activity. There's something nice about nesting in a web component like that.

JASON: I have found...

CHRIS: It's like display none. And inside of this, put and active, display: Block. You only ever see one of them. Right? That one is annoying because it has a tall image in it.

JASON: Ah, but you know what we can do really fast is dot image, dot image, at aspect-ratio, 1. And what is the object-fit? Cover.

CHRIS: Contain? Cover?

JASON: And let's just make it -- no, inline-size because we are -- we are writing accessible code here.

CHRIS: Yeah, I'll make it a tiny little square. I hope, right? Now you have to say 100% there.

JASON: And that should solve that problem. So, now they all are the same.

CHRIS: The same.

JASON: Look at us go.

CHRIS: Look at you. Yep. That's pretty satisfying right there, I'll say.

JASON: That's super-satisfying.

CHRIS: So, that's a working slider with web component-style. So, the only thing that's not coming along for the ride is just that little bit of CSS that you wrote. You could just copy and paste that and just drop it into the JavaScript as -- like there's a you know how you imported lit element and imported HTML from lit element, there's also a thing you can import called CSS with tag template literals and you can drop that in with the CSS method. That way, you have one chunk of JavaScript that you can ship out to npm and people could install it and use it and they would get at least this far.

JASON: Oh, so we can take this and put it down in the -- in here?

CHRIS: In the JavaScript. There's -- totally. There's a method that you have to look up in the lit docs.

JASON: Okay.

CHRIS: I feel that's more contained for distributability. Which I kind of like. But I also like not shipping any styles at all. Here you go. Here's a -- or just what you have written so far. Like that minimum, hide the one that's not active. So, anyway, let's not do that. Let's just write CSS here to make this thing work. Because who cares, you know? Like we're not actually shipping this thing, we're just learning. We're doing really good, aren't we?

JASON: I'm -- I'm, you know, I'm proud of us.

CHRIS: I think so. I don't know if I have that much more JavaScript that I think we should do. Like we might want to -- with making it rotate. Let's forget it.

JASON: Even the rotation we can do in CSS, I think?

CHRIS: Oh, we'll definitely -- literally ever other line of code, write there. But just as a fun thing for everybody watching, go into the JavaScript and select all and delete it.

JASON: Select all and delete this.

CHRIS: All the JavaScript. And as a reminder, look, when there is no JavaScript, we still have content. It's fine.

JASON: Oh, you know why? Because we don't have -- because our activities --

CHRIS: Yeah, we did that.

JASON: Which is why you contain it in the element.

CHRIS: Yeah, might be a good reason to do that. It's funny. But it's a good reminder. There is quality HTML sitting there. All that JavaScript that we just wrote doesn't matter. It's just a sprinkle on top of interactivity. So, pretty cool. Let's -- like the CSS part is feeling pretty satisfying. So, let's just truck on with that, I guess?

JASON: Yeah. Let's do it.

CHRIS: Where to start? I think the grid itself is a good place to start. There's a point I think is worth looking at like the rendered DOM real quick. Let's look at what the browser is doing to the DOM of this thing. We have our activities widget. And within it is activity, activity, activity, activity, right? And then below those you see that weird little comment? It must be lit HTML being like, okay. Now is some other stuff. I don't know why it's doing that. It must be some way for it to do its dynamic selection of stuff or managing those properties. What I want is for that count and for those buttons that navigation to participate on a grid together with the activities. But notice how they're like -- they're all siblings of each other. We're gonna end up using subgrid to solve that problem.

JASON: Oh, I've never done subgrid so this is exciting.

CHRIS: I wanted to set up the grid on the widget itself.

JASON: Okay.

CHRIS: Go to activities widget, and I want you to define the grid there. At the very top level of the CSS. Because that's not really where we want the grid, but we want all of the stuff to have access to it. So, we're just thinking ahead and we're gonna put the grid there, gosh dang it. So, display grid, yeah, absolutely. And then yeah, it's going to be left and right. A way to do that. And grid is grid template columns. 1FR, 1FR. One fractional unit, another fractional unit. This is going to look awful in our CSS for a minute. It thinks the activities are -- let's not worry about what it's doing now. You probably want a little gap between them. More maybe began on the left and right than otherwise. As the first two values, put a half a rem or something? And then more left and right. So, the two is in between. And the other ones will be up and down. We don't have any rows yet. But we're gonna need some rows. I think rows in this case was actually interesting. If we define all the rows and the columns, then we can be really explicit where we want crap to land on this grid. It's almost tempting to draw it out. Actually go to Toni's site real quick. We can envision what's going on. Actually I don't know if we have a grid or not.

JASON: This kind of like one big square and then rectangle. Text area. And like rectangle.

CHRIS: Yeah. You see how you naturally circled the navigation on the bottom there. That's where I want the rows to work with too. I want one out of seven to be in the first row, I want all that text in the middle row and the navigation to be in the bottom row. I want to be explicit like that. And I think we kind of need to be because of how --

JASON: We actually can draw this out because we've got --

CHRIS: We can indeed.

JASON: This thing.

CHRIS: We have done that one, 1FR, 1FR on the left and right. You would have to make a sub-element. It's kind of -- imagine the horizontal purple lines are gonna cut right through the image too. We're just not gonna use them.

JASON: We are gonna do it this way and then have the image be three tall?

CHRIS: Yeah. In the old school HTML table, you could be like whatever row span -- row span that baby. That's the grid we're gonna set up . let's do the grid template resource. I don't care how tall they are. It literally doesn't matter. Be as tall as the content inside you are. When the grid template grows, auto, auto, auto, or say repeat three auto. Repeat 3, auto. Give three automatic. Now that we've defined those rows, we can be explicit in a way that feels better than not doing that.

JASON: Now we can see the grid in the thing here, which is really nice.

CHRIS: It's kind of like the wrong crap is landing on the grid for now. Let's do one more little trick to do this. Go -- right after this, after what you just wrote, make a new selector that's just like the direct descendants, you know how you do that with the little arrow guy? Yeah. And then say grid area -- do this for me, grid-area: 1-1-1-1, slashes instead of slashes.

JASON: Do you need spaces on these?

CHRIS: I don't think so. So, what we're saying there is any child on this thing, place it -- just stretch it over the entire -- all the columns and all the resource. Because we don't actually want -- what we want is the children using that grid, not whatever. Don't worry about it too much yet. But now go -- go into the activity selector you have in the CSS and write a couple of special lines. Say it needs to be display grid. So, you have to get rid of the display none for now, unfortunately. They're all gonna come flying back at us.

JASON: And then the pile.

CHRIS: And two grid template rows and columns again. The value you're going to give for them is the magic subgrid. Just use the word "Subgrid." I want you to have the same grid as what your parent does is what that's saying. The same columns and the same rows.

JASON: Okay.

CHRIS: Now that stuff is laying on that grid that we set up. Which is exactly what we want. I know it looks funny for now. But now each activity should be a grid too. It's saying it's not -- you're going to have to -- remember that line we wrote, dot active is making a display block.

JASON: Right. We can hold that for now.

CHRIS: Yeah. We will certainly style the active one. But now --

JASON: Now we get our subgrid.

CHRIS: It's just we're not placing anything on that subgrid just yet. So, let's do that job. And start putting stuff where we want to put it.

JASON: Okay.

CHRIS: So, yeah. Well, we also need to make the activity itself also stretch the entire thing. So, you could use that same grid area trick activity rather than just being placed in that first column also stretches it. Use that same trick, I guess. I guess that should do it. That's gonna make it go across both, isn't it? Or am I wrong? I want it to literally stretch, instead of the right grid column, grid/column, and then 1/negative 1. That's like start at one, go to the other one. And grid-row, 1/negative 1, span all the rows. That's exactly right. Seeing them on top of each other. But seeing this stuff about happen where we can approximately want it to. Let's deal with the rest of the placement. What's the first thing? IMG? our mind, it's the first column.

JASON: Column one and then top to bottom, right?

CHRIS: Totally. Grid column, 1/negative 1 or negative 2. Not grid line 1 or grid line 2 is really the way to think about it.

JASON: And they're numbered here. 1, 2, that's what we're telling it to do.

CHRIS: 1 and 2, and then grid row, 1 through 4 using that thinking. There's four rows. See how it's going across the lines now? That's exactly what we want. All this work, it might seem convoluted, but the point was now we need to pick up like the buttons, for example. What was the class for the built-ins?

JASON: Activities count.

CHRIS: We want the count and you're gonna need a class on the NAV to select it, probably. You don't need to, but we're doing classes for everything else. Might as well use activities or something.

JASON: Let's call it.

CHRIS: So, now you can select it. And we can just be -- we have this opportunity to be really specific about where to put it. It should be in column 2. Grid column is in 2/3. And grid row is in whatever, 3/4, I guess? The very bottommost one. So, we should see those buttons pop down there, hell yeah.

JASON: There they go, and then we also have -- it was called activities count.

CHRIS: I think so, yeah.

JASON: Activities count. And for this one, we're, what? Grid column same. So, 2/3.

CHRIS: Totally.

JASON: And then grid, row, this one we want 1 and 2, right?

CHRIS: I think it's grid row. It might be 2/3.

JASON: Okay, let's look.

CHRIS: Oh, no, count should be -- yeah, exactly. The count's at the top. The count's at the top, the text is in the middle there.

JASON: Then we wanted text.

CHRIS: Yeah, it's dot TXT. That's the one that's in 2/3. Why is this hard to think about?

JASON: Here.

CHRIS: Second column, second row. Boom.

JASON: And there we go. Now we got everything where we want it to be.

CHRIS: We really do. I think that feels good.

JASON: And...

CHRIS: We're seeing the text on top of each other. Of course that's to be expected.

JASON: But a fun thing that we can do that I was just thinking about, because we do want all the images to be visible, even when not active. When we do the stack, we want them to be there, but the text to not be there. Do a dot active --

CHRIS: That should be fine.

JASON: Not active, let's get weird. Let's get weird, everyone. Then this can be display none.

CHRIS: Yeah.

JASON: Not active. Why didn't that work? Because you should be visible.

CHRIS: It's because the -- I think we're nested a little too far.

JASON: Nested weird.

CHRIS: Like not active is gonna apply to anything above the activities widget which there isn't anything. It's probably not quite the right approach. I mean, it kind of is. But I think like using the active class is a little more... maybe...

JASON: Wait. If I can do that. Which -- oh, my god, what key...

CHRIS: I got a good idea just to save us a little bit.

JASON: Jesus Christ.

CHRIS: Put opacity zero on it.

JASON: This text. You're doing easy stuff.

CHRIS: Nesting is too deep. It's hard to think about. We have the active class up there, commented out display log. Not do active text, and then opacity 1 there.

JASON: Oh, my goodness.

CHRIS: Yeah, we're asking a lot of our poor little computer.

JASON: Okay. There we go. We did it.

CHRIS: What's kind of nice about that is that you can animate opacity. So, why don't you...

JASON: Oh, good point, yeah.

CHRIS: So, you could do, I don't know. You always apply the -- apply the transition on the not active one. Like have it always be there were so, dot text should have a transition on it of sorts. You could just do like ease in, out or something. Yeah, just put transition, I don't know. Half a second. Ease in-out.

JASON: 5 second, ease in-out opacity.

CHRIS: Yeah, you can scope it. Why not?

JASON: Wait. What have I just done? The CSS, there we go. Ta-da!

CHRIS: That's relatively satisfying, isn't it?

JASON: Yeah, like that.

CHRIS: But look at Toni's. Look at what Toni's does. It's a little bit classier because Toni is a classy guy, you know?

JASON: Oh, he does a little drop.

CHRIS: One drops and the other doesn't, right? So, one of the cool things we could do there is just type translate on text and push it down on the non-active one, yeah. Right where you are. Just type translate and zero pixels. That's gonna push it downward. But then when it's active, just make the translate zero-zero.

JASON: And then we'll just take that out so it does everything.

CHRIS: Yeah. We're basically Toni now, aren't we?

JASON: Look at us go.

CHRIS: Uh-huh. I think there's probably a way to make it -- because his like goes down and then the other one comes up. Like they don't happen at exactly-exactly at the same time. I wonder if there's a way to do that. I feel like I figured that out. But I forget. I think you put the delay on just the active one. So, put like whatever trends transition delay of .2 seconds or something.

JASON: .2.

CHRIS: Then it's only delayed in one of the two states.

JASON: Look at us.

CHRIS: Well, yeah. That is some -- that's what you pay for, kids. That is some advanced CSS action right there. I do think there's a problem with z-index when you layer a bunch of crap over it like this. Can you even select that text?

JASON: No.

CHRIS: You can't. There's other crap on top of it. Put pointer events 1 on the text and all on the other.

JASON: Oh, I was going to mess with the Z-index. Should we not do that?

CHRIS: It feels dangerous to me. If you put backgrounds on it and animate it, it will get janky. Yeah, put if all on that. If I can avoid z-index, I do. Pointer events none. That will select all of them then. Just at active one. There you go. Now keeping our content accessible as well. Absolutely beautiful. I think that's pretty satisfying.

JASON: Okay. Yeah, this is feeling pretty good.

CHRIS: Yeah. Here -- I'm gonna give you -- I'm gonna use our sponsor's thing here. I'll click and then I'll scroll to the top and I'll give you my -- oops. Is this -- wow, CodePen is awful, isn't it? Why is it rendering like this?

JASON: You can refresh the -- yeah, let's just -- let's just refresh the page, I think.

CHRIS: Yeah. apparently didn't -- it's code mirror needs to be refreshed once in a while. I'm just putting that there. Put the thing in the middle and use system UI. Just so that it's slightly more palatable-looking. You can take over again. I wanted to give you that chunk of code to make it a little bit more beautiful to look at. A couple of things to do. We need to look at the images. If people are waiting this long to see an image fly out and in. That's probably the time to make the image fly out and, you think?

JASON: Let's do it.

CHRIS: You already made them squares for me, that rules. Let's rotate them randomly. That might be satisfying. Let's do that with a custom property so people can see that kind of approach. For each image, you can just rotate that, the parent. I don't have a problem with that.

JASON: Let's do it on the image. I think it's gonna get weird if we don't. Doesn't that mess with the bounding boxes?

CHRIS: No, it's a rotation. I don't care. You can do it on either one. Yeah, rotate is fine there. Make a default of zero or whatever. And then put rotate and then rotate it by that value on whatever you want. Var dash, dash rotate.

JASON: My brain shorted out there.

CHRIS: Let's not complicate it. Now go under -- oh, we do actually need to do it on that. I think -- now -- yeah, that's fine too. Now put some spaces after that and then be like, and in child 1 --

JASON: Oh, right...

CHRIS: You know? That way, we'll use the Nth child to set the rotate property then to like, I don't know. 4 degrees. And is it gonna do it? Can we see one go?

JASON: We can see one go.

CHRIS: Did they all go? Four, negative 2... negative 9.

JASON: Negative 9.

CHRIS: And 7 or something. We'll deal with why it's not working shortly. It's funny that first one had no trouble working. So, we're setting them...

JASON: Okay. So, let's poke at the... we got this one. And then down here, we've got one of these. And they're all getting --

CHRIS: It's selecting --

JASON: Nth child.

CHRIS: Because the activity the Nth child. We grab all those.

JASON: Yeah, that's what it is.

CHRIS: Grab all of them and move them up a level in the nesting. We're poster children of doing too much nesting, apparently. But now, yeah. That should be fine, right? I think you still need and on it. Because it's... it's -- are we at activity level?

JASON: Why isn't that --

CHRIS: Or you're resetting it online 44.

JASON: Wait, I know what it is. We are -- we need to be on the activity itself. And then the images. So, because the --

CHRIS: Delete that. I'm telling you. Just delete it real quick. I'm telling you. Go to line 44. You're resetting rotate at a more specific level to zero. Just go down a few lines. See line 44? You're overriding what you're doing at a more specific level. Just delete line 44 and then it will work. No. Yes. Yeah. It's all right.

JASON: Okay.

CHRIS: I know what you mean, though. But it should cascade in. That rotate value. Even though you're setting an activity.

JASON: Oh, yeah.

CHRIS: Cascade down.

JASON: 100%. Yes, I understand what's happening. What was confusing me was that we're setting a variable that we're then using like lower down.

CHRIS: Yeah, exactly.

JASON: And I was thinking about this, which is Nth child one.

CHRIS: Your way would have worked too.

JASON: Okay. So, we're here, now we have the stack.

CHRIS: Yeah, they're all rotating and we're doing it with a custom property. Which is great. Yeah, you want to mess with that a little bit.

JASON: Make it pretty. A little bit.

CHRIS: Yeah, sure.

JASON: Something like a .25 rem. If I can type.

CHRIS: Yeah, throw like a white border, like a 3 pixel white border around them.

JASON: Solid white.

CHRIS: And maybe like a --

JASON: Like a little box shadow --

CHRIS: Yeah, box. You clicked, I think I screwed you up. Put like a pixel box shadow of like 25% or whatever.

JASON: How do we do that?

CHRIS: What's your favorite way in CSS? The zeros. That's how we do it. But then you have to do another. Is that it? Jason, blowing my mind with your -- that's the eight-digit hex code thing?

JASON: Yeah.

CHRIS: Oh!

JASON: Yeah, there we go. That's about right.

CHRIS: I love it. Good job! All right. I think we are gonna ultimately have to come back to JavaScript a little bit. Because we're gonna need to know a state, some kind of class selector that we can use in CSS when animation is happening. We just like need to know that. And that should not be particularly hard to do, I think. It would probably be in that make active class. Like in the make active class in JavaScript, just set a class that's calmed children animating or something. You know? Just give ourselves a little selector to work with.

JASON: For this one specifically?

CHRIS: No, for the whole thing. So, this.class.list.add. But do it in the make active.

JASON: Make active. That's what you said and I just didn't listen.

CHRIS: That's okay. Whenever anything is happening, when a... yeah.

JASON: And you said, children animating.

CHRIS: But then we need to remove it too, and you can remove it right here. Just go, well, there's -- the thing that's actually being animated is gonna be the thing that's receiving the class. So, do this.activities at the index, you know what I mean? So, this.activities, square brackets index. Now on a separate line.

JASON: Oh. Oh, oh.

CHRIS: Sorry. So, you've added it. Now we're gonna be animating that one because we're about to touch it. So, go.addEventListener. And listen for all lower case animation and --

JASON: So...

CHRIS: I think it's no dash.

JASON: Okay.

CHRIS: And then just do a function. You don't need any parameters. And there you can remove the class. Because the animation that happened to that thing is done. Children animating or whatever.

JASON: And because we're using this format, whatever this is, the fat arrow function, that doesn't mess with this, right? So it cascades through.

CHRIS: Good. We're using the correct JavaScript syntax there. You know what you should do? Add an extra object to addEventListener. Ever do this? It's a bonus object, in between line 45, to give it another object and just say once true. So, we're not stacking these addEventListeners over and over. It's just a one-off thing.

JASON: Oh. I had no idea you could do that.

CHRIS: Because otherwise we're just adding it constantly. It would probably still run, but be a memory leaky.

JASON: I would write a cleanup function. I had no idea you could do this just once. Today I learned.

CHRIS: It's like the jQuery.1. I used to love calling that. Let's look at the real DOM and see if -- see if that children animating class is doing what it's supposed to do. Yeah. It looks like it. But it didn't get removed. It just added it only. What did we do wrong there?

JASON: Let's see... remove children animating...

CHRIS: Activities index. I think it's maybe because we should move that stuff lower under -- because when you add the -- you add the class after that. And you want to, like, add the class right there and then listen for it.

JASON: Here.

CHRIS: Yes. Let's see if that does it. Sometimes the order of operations is important there.

JASON: Okay. So, here's our thing.

CHRIS: Yeah. It gets --

JASON: Is it this? Is it missing... I'm feeling like it might be that it doesn't like -- like it's maybe picking up...

CHRIS: Why? This should be... let me console.log this. Let's just be real -- real this. Why not?

JASON: I mean, whose old enough to remember this one?

CHRIS: Yes. Right there.

JASON: Let's see if it's working.

CHRIS: I somehow don't think it will. I am confused why that's not doing the trick.

JASON: Why don't you like the thing?

CHRIS: AddEventListener --

JASON: Is animation end incorrect?

CHRIS: You can put a console.log there. But it should be picking up that animation. Whatever it's on, will cascade up to it. I feel like that should log. But it... it won't.

JASON: Okay. So, we missed something. Activities.

CHRIS: That is not firing is the problem.

JASON: Yeah.

CHRIS: Why wouldn't -- are we adding -- you know what I think the problem is? We have a transition going and not an animation. It might be transitioning.

JASON: Oh!

CHRIS: But is it, though? We are gonna use an animation. I don't know what...

JASON: Let's find out. Well, it's doing a thing.

CHRIS: Well, it's totally working now. So, transition end is fine, then.

JASON: So...

CHRIS: Where were you, chat, on that one?

JASON: Yeah. Come on. You're supposed to save us from ourselves here. All right. So, we got -- we got ourselves moving here.

CHRIS: Yeah. Well, the beauty of that is now -- why we needed that children animating is because now we can have a class on that very top level like activities widget has it on there. So, let's make a dot children animating state in the CSS as part of that. Just, yeah. Wherever. Good place. Children animating. I think it's and dot. Because it's that and --

JASON: Oh! Because it's the same thing.

CHRIS: The class. Now we can mess with the rotation. Like that rotation, make it zero. Dash, dash zero. Will that work? Will it override that? I don't know. We might have to be more specific where we're applying it. In this case, you might actually put.img, just to be like...

JASON: Yeah, I think you're right. Yeah, we want to -- we want to override that specificity.

CHRIS: Yeah. Look at that. So, they're going to zero, right?

JASON: Ta-da.

CHRIS: But are they animating? No.

JASON: They're not animating because we have to use an at property.

CHRIS: We don't have a transition on it. They're not trying to rotate.

JASON: Right.

CHRIS: You've ruined the lede, Jason.

JASON: I'm sorry.

CHRIS: We put a transition on there, and it's still not going to work. And people are like, why? I put a transition on there.

JASON: We'll call this 25 seconds. Why don't we go linear on that and let it sit. Wait, why does that just work?

CHRIS: I literally don't know why.

JASON: I thought you needed an app property for that to work.

CHRIS: Oh, my god, maybe there's some new version of Chrome you don't need it. You really should need an at property for that. The degrees we're using, that's just a string as a custom property.

JASON: Yeah, that shouldn't work at all.

CHRIS: Some crazy beta Chrome that infers types or something.

JASON: I'm not. I don't think so, at least. So, at property...

CHRIS: Rotate.

JASON: Rotate.

CHRIS: That's really, truly not supposed to work.

JASON: A little magic for us today.

CHRIS: I can't remember the syntax exactly. Let me look it up. It's syntax -- this is very awkward syntax.

JASON: Oh, syntax.

CHRIS: Yep. And then in quotes as the value, you need then angle brackets. And it's an angle. Because we're using it.

JASON: That's right, that's right.

CHRIS: That is a very awkward syntax. But I don't blame them.

JASON: And then it's like default...

CHRIS: It's not default. It's initial-value.

JASON: Why not?

CHRIS: Yeah. All right. And I don't think we even need to set it. But we are setting it because who cares? What really does super matters is we are cascading this particular property. Inherits is true. Inherit true. Now that's gonna make it work. But for some reason it already worked.

JASON: I wonder if I have something turned on in here that's like -- no, there's no CSS pre-- none of it. There's no reason that should work.

CHRIS: There really isn't. I promise you that's not supposed to work without declaring the type of it. I honestly don't know what's happening there.

JASON: But isn't that neat-looking?

CHRIS: Yeah, we solved that particular problem. If we could set them to random in CSS. That would be sweet.

JASON: That's coming, isn't it?

CHRIS: I think so. I imagine there's weird accessibility edge cases. But that's the kind of thing I think about in CSS, why can't you do that? It would be runtime random. You could do that in SaaS right now. But then it would be random, it would be fixed. It would be one random value. That's not what we want. We want random each time it's invoked or whatever.

JASON: I think we did that.

CHRIS: But we have a tiny little bit left to do.

JASON: Which is great because we have a tiny little bit of time left.

CHRIS: We have the active class. Only using it on dot text right now, we can use it on .img as well. We need an animation, like a keyframes animation. The reason we need a keyframes animation, not just one state to another state. We want that image again to fly out and fly back in. Yeah. Sure. That's a good name. So, at -- I mean, how are we gonna think about this? It needs to move to basically translate negative 100% at halfway through. So, at the 50% keyframe, you would say I want the translate value to be negative 100% or so or 110. You know, that's move left as far as big as you are is what that's saying.

JASON: Right.

CHRIS: And then at 100%, which you are gonna need here. It should just go back to where it is. Because we want to be explicit here. We're gonna do something a little bit special. Just go back to zero-zero. And I don't think you need the zero percent at all. I mean, the zero percent keyframe. I think leaving it blank is probably fine so, if we call that during the active state, just make it -- however -- what are we doing? Half a second?

JASON: Let's see... it's 0.5 seconds.

CHRIS: Sure. And then just ease in out. Like make it nice.

JASON: Do I need to add all the other stuff?

CHRIS: I don't think so.

JASON: Linear and forwards?

CHRIS: Yes. Well, yeah -- you jumped ahead again. I think as you do that, they're gonna fly out.

JASON: They are flying out.

CHRIS: Set the max inline, put like max inline size equals 300 pixels way up on the top on the widget so we can center this thing. It's just being as wide as it wants.

JASON: Yeah. Max, inline size.

CHRIS: Get crazy.

JASON: What am I doing? I didn't mean to put it on that. We're gonna put it over here. And then we can... we can margin in -- oh, it's already doing it. Never mind. So, now look at them go.

CHRIS: Right. Now we see it fly out. But look at how not on the top it is. So, in this case, we actually --

JASON: Oh, and then we hit --

CHRIS: I would say in the keyframe, this is the trick we're gonna use at the 100% level, put it there.

JASON: Should we put it here so that it's like -- when it hits 50%, it's --

CHRIS: Or something. Maybe a 50%. I don't care. whatever. That might wait until the end of the animation to be on the top. It's still not on the top, that's the moment you need animation fill mode forwards. What that line says in that animation is, stay in the position of the end of the animation. Whatever that 100% keyframe is, stay looking like to, please. Forward. Yeah. See?

JASON: Hey! Look at us.

CHRIS: How nice is that?

JASON: We still got a little jank. But it's looking pretty cool.

CHRIS: It looks like it's happening just fine, right?

JASON: Well, it changes the order at the initial click.

CHRIS: I cannot hear you.

JASON: Oh, no.

CHRIS: You're saying words. I can not hear you. What did I do?

JASON: Did my...

CHRIS: Sorry, Jason. Just a moment. I still can't hear you.

JASON: Can the chat hear me? Am I still here? Check, check.

CHRIS: I think it may have just run out of juice.

JASON: All right. Checky, check. Chat's still got me. Let's see... so, yeah. So, the outstanding thing here is that we have this kind of stack order thing where we get the right thing flying out, but we're not persisting our order. Which isn't the end of the world.

CHRIS: Yeah. Everybody else can hear me and you, and I can't hear you.

JASON: Yes.

CHRIS: Why is this?

JASON: Check, check. Oh, no. The curse. So, let's see... yeah. So, I think -- and honestly, like we've got 3 minutes left so this might be...

CHRIS: Can I refresh Streamyard? Give me a thumbs up. Will that screw up the world.

JASON: All right. So, we're gonna see how this works. Live demo god for sure. Although I'm pretty satisfied with how far we got. I was truly -- oh. What just happened? Do we... Chris, can you hear us? Let's see... can you hear me through Tuple? Okay. We can hear you -- well, I can hear you through Tuple. I don't know if that's getting routed into Streamyard.

CHRIS: [Chris speaking, but...] -- the browser. So, now that I'm back in Streamyard, everybody should be hearing me and I can hear you through Tuple. So, thanks for saving the way, Tuple.

JASON: Yeah. Will you mute yourself in Tuple so that I'm not getting a double.

CHRIS: Did that do it?

JASON: It did. All right. Everybody can hear. We are just about out of time. And the one thing that I'm seeing left here is if we look at the stack, we get that flash of it always kind of goes back to the top image. And so, it's losing our like which image is on top when we start the animation. Which maybe doesn't matter.

CHRIS: Change the Z-index to not happen the at the 100%, happen at 50% state in the keyframe animation.

JASON: Okay.

CHRIS: It's waiting too long to change the z-index. Oh, it's almost worse now, right?

JASON: That's weird. It is actively worse. Do we need it in both places? That's... well, that's weird.

CHRIS: I actually am not sure at the moment. Now I'm hearing you twice. I need to mute you...

JASON: Let's see. So, that would mean that I'm back in -- let me mute.

CHRIS: Yeah, Streamyard is back.

JASON: Okay. Streamyard is back. Cool.

CHRIS: One thing that's annoying real quick. Go do that .img class and put in a max of 100%. The images -- not what it's active, though. Just all the time.

JASON: Oh, yeah.

CHRIS: It's being too big.

JASON: Let's get up to here.

CHRIS: That didn't do it, did it? Didn't we set it to some -- 59 --

JASON: We set it to 200. You want to get rid of it all together?

CHRIS: Now it will be the size of the grid, which is better.

JASON: Ah, yeah. I gotcha.

CHRIS: Anyway, I feel like we did the hard work. And I know this stream is over. But I feel like people can imagine tweaking typography and stuff to accommodate these things.

JASON: Right. The typography and the last thing is we need a little bit of JavaScript which we don't have time to do to just adjust the z-index. Probably like a -- maybe instead of removing active all together, we could have like a previous class and that would give us a z-index of like 2, 1, and unset. So, that we would have like whatever -- whatever the --

CHRIS: I kind of like that idea, yeah.

JASON: Because I think that would give us a stack, right?

CHRIS: On Toni's, I think it's pretty much like that. He does it so that the DOM, he plucks one off, and puts one on the top and leaves the rest of the DOM the way it is.

JASON: Oh, he's actually reordering.

CHRIS: Yeah. I think I saw that. Then you don't get the nice HTML that way. All the images then have to be siblings of each other.

JASON: Right.

CHRIS: There's tradeoffs in web development. That's just the way it is, you know?

JASON: But I think we could solve that. That would be a fun -- you know, if you're interested. Let me share the link to this CodePen. And anybody who wants to, go ahead and fork that and fix it. And send a message to me and Chris when you do it because we would love to see the way you solve that problem.

CHRIS: That would be interesting. You could put SVG icons in like he's got. Make it dark mode/light mode compatible. There's lots of stuffs you could do on a widget like this. But anyway, thanks for having me. I think we did the hard part.

JASON: I think we did the hard part. And, you know, go out there, check out Chris' stuff. Make sure you go and, you know, give CodePen a try. Like if you haven't tried CodePen, it is a fabulous way to just practice this stuff. It's low barrier to entry. It's really nice to work with. You know? Make sure grow and check out Toni's site and look at how he originally implemented it. Maybe peek at the source code and see how close we got to do. And one more shoutout to our captioning. We have had Amanda here today from White Coat Captioning. Thank you Amanda for being here. And in addition to Tuple, in addition to the fun pairing stuff, also just hear each other. Which was great. So, thank you all very, very much for hanging out today. While you're checking out things on the site, check out the schedule. We have fun stuff coming up later this year. More coming to the schedule soon, keep your eyes peeled. Chris, any final words for everybody before we wrap it up?

CHRIS: I don't think so. Thanks for having me. Take care, everybody.

JASON: Thanks. Thank you, Chris. We will see you all next time.

Learn With Jason is made possible by our sponsors: