 Have you ever needed an infinite scroller with some tags or other texts like this? Or maybe you needed it to have images instead. Right now I have faces here. It could be logos for companies, scrolling buyer, something like that. And of course, we can set this all up to be really easy to control as well with some data attributes. We can do a data direction and just switch that over to the right and it's gonna go the other way. And if we don't like how fast they're going, we can come in with a data speed and we could say that this is actually gonna be slow and slow it right down or we could even do fast and speed things up and build in some other customizations as well. But just before we dive into all of that, I just wanna let you know that my name is Kevin and here at my channel, I help people fall madly, deeply in love with CSS, even though today we're also gonna be getting into the wonderful world of JavaScript as well. Now this is a type of effect that can actually be kind of problematic to implement properly. So in this tutorial, we're not only gonna look at how we can set things up and make it customizable like we saw, but we're also gonna make this progressively enhanced when it comes to the motion as well because some people in their system preferences will opt out of having a lot of motion and these sideways things are some of the things that can actually cause the most problems with that. So we're gonna look at how we can start it off disabled. So that way if somebody has opted out of having a lot of motion on a page, they're not gonna get any. And then for people who have not opted out, they're gonna get the full experience. All right, so let's dive right into it. You can see I do not have very much set up yet. We have this tag list here. It does not look too fancy. I'll look at the CSS I have for that, but none of what I've done right now applies to anything. And what I'm gonna do here is I'm gonna come in with a scroller, it can be horizontal scroller, infinite scroller, call it whatever you would like. But we wanna have something like that that's our scroller right here. And the one other thing I'm gonna do, the tag list here, as I mentioned, I have a little bit of styling on it is just to give these a visual style to it. So it's a list. So I'm turning off my list styling and all of that. But the magic of this is going to happen with we're gonna have a, we'll call it scroller, inner. You could name that whatever you want. But the idea with this is it could work on a UL that has allies in it. This pattern could work with images. If you have logos, we're gonna change this to have images after as well or have a second one with images. It doesn't necessarily have to be a list. It could be other things in here. So I do want a scroller and an inner scroller of some sort. That's the really important part here. And in this case, the UL can also act as our inner scroller. So coming back into here, let's just scroll on down and we can add that in dot scroller. And the important thing here is we do need it to have a maximum width. So I'm gonna do a max width of 600 pixels because I don't want it to be too wide and that won't change anything yet. But this will be a very important step along the way because then we're gonna go on our scroller, inner and I'm gonna throw a display flex on here because I want the elements to go next to one another. Not nothing too complicated. Let's also add a gap in here because it's good to have a gap. And let's put an outline over on this. Outline of three pixels solid line just so we can see where it's going and you can see we actually already have a little bit of overflow and that's fine. We sort of need to lean into that overflow a little bit at one point. One of the things we will have to do though for this to actually work is to eventually hide that overflow. So one thing I am going to do on this is to do a padding block which is my padding top and bottom of two RAM. It could be, you know, we could probably get away with one. It's just, see, we have shadows on these cards right now. I wanna make sure we don't lose things like that. The left and the right aren't going to matter but the top and the bottom giving a little bit of padding for this is going to help us out and we'll sort of explore that a little bit. But the first thing that's important here is while we want this to eventually be moving I sort of wanna progressively enhance it. And so what happens if somebody has prefers reduced motion off? If I do, you know, we come on here and I said we want an overflow of hidden. If I do that now, we're gonna have hidden content. And you know what, let's just make this a bit smaller or pretend we have more. There's stuff that's missing. So we don't really want things to disappear if a user has prefers reduced motion off and this type of thing where you have moving stuff it can, you know, when you're scrolling down and something's moving this way it can be really disorientating to people which is why they enable prefers reduced motion. So for me, this type of thing is really one of those types of animations that really benefits from progressively enhancing and adding that animation in if somebody has an opted out of having reduced animations. So my idea here is actually let's not put the overflow hidden on. Let's leave it there. And here we're gonna come in with a flex wrap of wrap. And so if there's not enough room the stuff wraps around and all of the content is still there and still visible on the page so we don't lose anything. We're going to need some JS to actually animate this because one thing I know we could probably come in and throw in, I don't even know if this would work anymore. Mar, can we do this? Let's find out. Don't do this, please, please. So right, if we could sort of do something like that but it's not much of an infinite one because it's gonna go over and then loop over but we have those empty areas. So even the marquee in this case the good old marquee of old is not to help us with the type of thing or one we want where the each, it just looks like it's infinitely looping around in a circle. For this to work, we're going to need JavaScript. So because we're gonna need JavaScript everything else from this point on because this is sort of my base stage. Everything else is going to be added sort of via JavaScript. So let's come and take a look at my JavaScript file where we currently don't have anything. So we're gonna start off by looking for all the scrollers on the page. I only have one right now but potentially you could have multiple ones and we are going to have multiple on the page eventually so we'll do our scrollers is equal to a document.query selector all and we'll look for anything called scroller. So anything that has that class on it we will be selecting to be doing stuff with. And so what do we wanna do? So let's say if and this is where the progressive enhancement is going to come in we're going to use a media query here and I actually have this one off screen because I'm gonna muck it up if I don't copy paste it. So window match media. So you can check for media queries within JavaScript here. And so we're doing match media which is looking for the media query. And then it's weird because you have to open and close parentheses and then inside of there you'll have your our quotes and then we're gonna have another set of parentheses and you need this one inside the quotes. You definitely you have to have the outer one and the inner one here for this to work properly and we're gonna say prefers reduced motion reduced but I'm saying if we're not, right? So if window match media is not prefers reduced motion and then to actually check to see if this is true or not because when you do it, you'll get a matches and the matches can be true or false. So if this is false, we can keep going, right? So if it's false, what we wanna do is let's just say add animation and we can add our animations in with a add animation function. So what's that gonna be? We can say function add animation in and come up with what it's actually going to do. And the very first thing we want it to do is add in a data attribute. So let's get our scrollers. So scrollers for each and for each scroller, nice and simple, we want to do something. And what I'm gonna do is we're gonna say scroller and we're gonna do set attribute. And so we're gonna say that the attribute data animated is going to be equal to true. And so if I hit save on this, let's just go take a look at our page and see what that's given us. We can do an inspect on here. And if I come, let's just move some stuff around and see I've Grammarly installed there, but we have the scroller and you can see the data animated of true there. Make this a little bit bigger so you can see it easier. So is there data animated of true? In Chrome, with the DevTools open, I'm gonna do a control shift P. If you're in a Mac, it would be command shift P. And I'm gonna do emulate reduced. And you can see here, emulate CSS prefers reduced motion, reduce. I'm gonna select that and I'm gonna refresh my page. And there's my scroller and that data attributes not being added to it. Let's do it again, command shift P, emulate prefers reduced motion. And now it's already doing that. So now we're gonna say do not emulate it. We're gonna turn that back off. I'm gonna refresh my page and the data animated equals true is on there. So if the person has not opted for reduced motion, then we're gonna add the data animated true. And that means that we can now add all of our animations and do all of our fancy stuff. Awesome. So now with that done, we're gonna have a little bit more JavaScript in here eventually, but for now let's come back to here. And what we're gonna say is on our scroller, so scroller that has a data animated is equal to true. We can now say that this actually has an overflow of hidden. And we could use nesting for this. I'm not gonna do it with nesting and actually I'm gonna move, I'm gonna leave the default styles there and then we'll come down to these ones here. So we have that then what we can say is that we'll copy you paste it and then copy you because if we have the data animated true our scroll inner, right? So we wanna select that scroll inner if the scroller is data animated true. And I guess you could drop that off if you wanted to. I'm also doing it like this, instead of just like this, assuming that you might be adding this to other things as well. So, you know, if this is the only thing I guess you could get away with getting rid of the class selector, but I'm assuming that you might have different scrollers. In this case, you don't necessarily need it because we're specifically targeting the inner but I just find it more consistent if we keep it. So our scroller inner, now we'll do a flex wrap of no wrap which is the fun CSS property that doesn't have a hyphen in it, right? And that's actually been listed as a mistake by the W3C. But there we go. Now we can see that we've now hidden away some of our stuff there and it's vanished which is the start that we actually wanna have here. Now another thing we can do just this effect let's make this a bit wider again here, 600. And the reason I wanna do that just you can see we're still cutting off at the UI UX but this type of effect basically every time I've ever seen it done gets faded out on the left and the right side. So we only want this to happen when we have the overflow hidden and actually we sort of need this to happen for the effect to really work anyway which is probably why we see the fail off. What we're gonna do is we're gonna add a mask here and for the mask it's gonna be a linear gradient and you haven't used masks before they're super powerful and you can do really great things with them. You can do it with images which is why we can do it with a linear gradient. And if you want more info on masks I've done like three or four videos I'll put a playlist to them down below including one that covered the first video in the playlist will be the absolute basics of how they work. So the linear gradient on there we'll start with transparent and let's just go to white and it's basically it's all based on the opacity of the different things on here and that's not gonna work because here we probably need to have a web kit for yeah we did. So we need the web kit mask for chromium browsers but all the other ones can just use masks and then it works there. So with that in place and it's like one of the few places we still need to prefix things and it's funny that it's only with Chrome but there we go. So the transparent is the area that's actually gonna be faded out and just any like I put red here if you wanna save a character it's gonna work as well we just need something that doesn't have opacity on it you know in FFF whatever you want a color that has 100% alpha value and then you get it coming up. Now obviously we need this to be a little bit different so we're gonna say 90 degrees so it's going that direction. I wanna keep it close to the edge here so what I'm gonna do and actually let's just break this down a little bit to make it a bit easier to see on the screen because this is gonna get kind of long. I renewed transparent to white but my white is going to start at like 20%. So if I do that you can see it fades and then once we get to 20% I'm at my solid white and then we have it going all the way across but we need it on the other side here too. You could do this with the second gradient to do the other side but I don't really see the point since we already have a gradient so I'm just gonna do white and this one will go to 80% and then we'll do transparent at the other side so that one, oh there we go it broke it down for us so we do 90 degrees then we're doing transparent up to white starting at 20% all the way to 80% and then we fade back off to transparent and that means I'm just gonna copy this and do this as the regular mask for all my other browsers and that's probably gonna get prettier to being on a single line but there we go. It's working and we do need the webkit version and the mask one if you're using something like auto prefix or with post CSS then you could probably get away with just using your mask and that sort of sets the stage for what we wanna do now what we need them moving across. So let's come in and add an animation so we can say add key frames and we'll call it scroll and we can just do two so that means it's the end of the animation meaning the beginning will start wherever we're starting at by default and we wanna transform it and we're gonna start with doing a transform of a translate and I'm just gonna say 100% just so we can see how this is going to work and the problem this causes but this sort of sets the stage for everything. So then on my scroller I don't want it on the outside scroller I want the outside scroller to stay where it was so let's put that outline on there again outline five pixels solid line except I think we can't see that let's put a border maybe there we go I can sort of see the border the outline was outside of the hidden area but we can see like we wanna keep this there and we want the content inside of it to move if I added the animation to the scroller itself the whole scroller would move which we don't want and that's why it's important to have the scroller itself which is has the overflow of hidden and then the content inside of there that will sort of scroll by and get hidden because it's overflowed and masked away. So what we'll do is on this scroller we can add an animation of scroll and I like setting it at like a really long period of time because I find if not it moves way too fast. So 40 maybe for now we'll just do 20 seconds. The other thing that's really important here is that it's linear so the speed is always the same because if it started speeding up and slowing down once we get the loop working it would look really, really strange. So linear is really important to have on there and the other thing we do want is when it gets to the end of the animation it's always gonna reset, that's a good thing but once it resets back to the start we want it to start again so we want an infinite on there as well so it just means the animation is always running and now we're stuck with this big empty spot because everything is scrolled on away and then it starts on over again. That's sort of annoying that it does that but that's okay, it's going to work out perfectly fine with how we're gonna set this up. The first thing though is I actually want it to go the other way so let's do a translate negative 100 we'll see how we can get it to go in both directions though but if we do it with a translate of negative 100 it moves the other way which just makes it a little bit easier to deal with at the beginning though we will still end up with this blank area at the end and this is where we have two choices and the first one is not what we're gonna do but we could just come in here I could take all of this and duplicate it and then it's gonna work sort of. Let's just for now we'll do it this way and then I'll show you the better solution in a second but we'll do class of test on here and we'll come here and we'll say that test has a background that is red and the reason I wanna do that is because we're gonna see my first HTML went off and that wasn't working at first because I had to throw an important on here because I had a higher specificity selector but you can see this HTML is coming here and basically so this is the second one and this is the duplicate and when that one lines up with where the first one originally was that's when I want the animation to start over so this translate of negative 100 I actually want it to be around negative 50 and that's because I only wanted to move it half way across right because we have duplicated content so if I move it half way across it should be lining up with where it originally was ideally but we're gonna see that's not gonna work right now it's still gonna cause a jump and that's because this 50% isn't the number that we'd expect it to be and the reason for that is let's look at our dev tools because this left me a little bit confused at the very beginning if I look at this tag list right here it has a width of 600 even though there's the stuff inside of it we have the no wrap and the stuff is coming out of it the reason it has a width of 600 is because it has the width of the parent that we set to have a max width of 600 and the content inside is just overflowing out the side so the width of the scroller and the inner scroller are both 600 so the other thing we need to do to fix this is on the inner scroller right here the other thing we're gonna do is we're gonna say that the width on this width is going to be fit content we could also do this as a max content and that should also give us the same result and now you can actually see that the width of it is a lot bigger than it was before and this is actually what we want it to be we want this much bigger width by doing this the width is matching the content that is inside of it now and it's now this is overflowing outside of the original scroller so the inner scroller overflows out the side but it means everything's gonna line up a lot better and here my translate negative 50% let's hit a refresh on there my live server, I'm using VIT right now and it's having some issues when I update my CSS but we should see and I don't wanna speed it up because things move too fast so it gives me a chance to talk a bit but we should see it's roughly gonna be when this resets, which would be any second now it will go but notice how it jumped just a touch took me a while to figure this one out too but because we have the gap on here it's actually the positioning of this second one is moved over a little bit more than what we thought it would be, right? So the first one at the very beginning let's turn off this animation for a second and see I have to refresh anyway, that's okay, old school manually refreshing stuff, isn't that bad? If I take a look in here and I take a look at the parent the right that HTML, this first one is flush with the left side of the parent but then as we go through and we get to that second HTML there's a gap between these two so to compensate for that gap that we have on the second one but not on the first one we can come in and actually put a calc here so we can say calc, select all of that and then do a minus and you'd expect one because our gap is one but because this is 50% we're gonna do a 0.5, REM which is half of our gap and that could definitely be something you use a custom property for instead of setting it up this way just because then if you changed your gap this number would also change and we're gonna use custom properties for a few things to make it more customizable but just so you know those two numbers have to be related to one another this one's half of what your gap is and now we should see when it gets to that point the entire thing, see how it just changed colors because it was perfectly aligned so when the animation restarts and everything just jumps back to where it was everything's perfectly aligned with where it needs to be but right now we have a terrible solution because we have to double our content which we never wanna have to do it just makes everything really awkward it's more work, it's just not ideal so we're gonna do and we're using JavaScript anyway so we might as well set it up where the HTML makes sense and then we use JavaScript to set it up to actually work by duplicating the content for us and so let's jump back on over to our JavaScript so we can get started on this part here and what we wanna do is we're mostly we'll make that bigger just so we can, JavaScript's always long lines we want to be focused on here where we're working on each individual scroller and we're gonna need two different things here so we're gonna say we wanna get our, we'll call it our inner scroller or we call it scroller inner, right? So we'll say scroller inner so we have the same name is our scroller dot query selector and we wanna get the dot scroller inner and the other thing we wanna do is we wanna get all of the content inside each one of those so here we can say that our const scroller we'll just say scroller content I could do scroller inner content but it gets a little bit long so scroller inner and what we can actually say here is children but we don't wanna do this well we sort of need to do this but if we do that, let's console log that actually because this is gonna give us an HTML collection which is updated live so if the DOM is updated, that collection is updated which can cause some problems so let's just take a quick look here scroll content and if I go and look at my console scroll content is not defined scroller content would help and you can see it says HTML collection right there and then we get all of the allies as I mentioned, this can cause a problem though because the HTML collection will be updated if the DOM is updated so if we duplicate the content that is inside of our inner scroller then there's more children that get added in and it duplicates that content which then gets duplicated which causes an infinite loop and one way we can prevent that is instead by doing an array from and if we do an array from this is basically making a copy in that initial state that won't get changed later on so now if we come take a look at it instead of it being before we saw it was an HTML collection so now we just have a standard array with seven different things in it that won't get changed even if the DOM is updated and that's exactly what we want because now we're gonna duplicate them all so let's get rid of this and we're gonna say there's different ways we could loop over this that scroller, content we can just do a for each if you wanna do a different type of loop here there's no problem and we can say for each we'll say each item that's in there so for each item we wanna do something so we're gonna do a function and just use a arrow function there we're gonna say and I'm using const here for all of these even though we're obviously having more of them but it's limited to each time this runs so for each one this const is there if you'd rather use let or don't use var but if you'd rather use let you could but the const will work fine here for these just in case you're wondering because it's redone each time this is running so here we have const and we're gonna say duplicated item is equal to and then we can say item and we can actually just clone it so clone node and in here we just say true and let's do a console log of that so we can see duplicated item and if we come and take a look now in here in our console we get each of those items coming and each one of them is the ally and this is good because we wanna now I'm doing it as allies as I mentioned after we can have it as images we can have it as divs we can have it as whatever we want so it's taking that entire node and duplicating it we're not just getting the text or anything like that and then what we wanna do and this is a really important step because we wanna make the screen reader friendly as well so we can say that our duplicated content before we add it into the DOM because right now it doesn't exist anywhere we've duplicated them all in JavaScript world we haven't done anything with them yet but before we do anything we're gonna take it and we're gonna do set attribute and this is kinda weird with JavaScript sometimes I find that we can do stuff to an item that doesn't exist outside of the JavaScript yet like we've duplicated this item it exists only here it's not on the page but we can still add classes to it or set attributes to it and other stuff so I always found that was kinda interesting that we can do that but we're gonna say that we're not data we're gonna say an area hidden is going to be true and by doing that it just means or we'll see it in the DOM in a second but yeah we just wanna make sure that this content from a screen reader perspective the second the duplicated content doesn't show up in a screen reader because then it would read it all twice which would be really annoying but then for we can go back to our scroller content so remember that's our the scroller or no not the scroller content we want the scroller inner we can go back to that scroller inner which is always we're doing this for each individual scroller so our scroller inner and then we can just do append child and what's the child we wanna append we want it to be our duplicated item so now if I take a look you'll see it's even moving faster because we have more stuff in there and if we come and take a look in our dev tools we will see that we have the UL and in the UL HTML CSS all the way down to UI UX and then we get the second set that all has the area hidden of true on there so we've duplicated our content and again there's other ways we can loop through and get it if you'd rather do it a better way or if you have a better way leave a comment down below if you have a way that you'd prefer to do that let me know about it because I'm always trying to improve myself as well but I think that does the job and it gets it all there and you can see that this is actually working really well and we don't even notice when it loops so that's really awesome you just it just keeps going and it works and it's that's what it is though of course this is a little bit limited because right now it can only work in one direction and you might want it going the other way or you might wanna be able to have different ones that have different speeds on them or you know there's lots of different stuff we might wanna do here so let's look at how we can do that and so what we're gonna do is we're gonna come up to here and you have my animation my scroll this linear infinite so let's start here actually on the timing and this is come in with a var of animation speed or duration we'll say duration and what we'll do is I'm just gonna put 40 seconds here is the default so that will be like if we don't declare one it's just gonna run at 40 seconds that we can actually make a default or something but now this is going to work and if we wanna change it we just have to worry about changing this and I'll show you why I like like this in a minute and the other thing we're gonna add here is let's just put these on different lines pretty or might not like this and change it when I save but the other thing that we're gonna do here is have a var of the animation direction and the default for this one will be forwards and so if I hit save on that everything should continue working because we're moving forwards but what we can do now is we could come here and we could say that if we have a scroller and in this case we're gonna say that if as a data direction is equal to the right we could do a animation direction is going to be equal to reverse and let's just come over to here and I'll show you why I like doing with this approach so I'm gonna copy the entire scroller, bring it down and then I'm gonna say data direction direction is equal to right and now we have one moving left and the other one moving right and they both work and that's great and the reason I like the reason it's easier to have the default one going this way is just because the way we have things set up where things naturally line up on the left side whereas if we want it to go toward the right then you have to do the easiest way to do it is using the reverse direction so I mean if we want we could also have a data direction direction is equal to left and we could be explicit about it just so if somebody's coming into a system like this it could be handy so here we could say forwards and so the direction is to my left and you can see it's working great and the reason I like doing this with custom properties is because the animation's actually getting set on the scroller inner and it's kind of more convenient to me to be putting these modifier data attributes and I'm doing them as data attributes because you wouldn't wanna accidentally set a direction to left and right which we could do with a class especially if you need to manipulate it with JavaScript it becomes much easier to set an attribute than it is to remove a class and add a new class in just data attributes for this type of thing makes sense to me but yeah then because it's a custom property I just have to worry about setting it on the parent where the scroller is so it makes sense that this is controlling everything even though it's sort of applying this style down on to this one so for that the custom properties make sense and it's also why I put this underscore here it's just to say that it's like locally scoped to this it's not something coming from my route I think it was Lea Veru though I might be mixing people up that first introduced that idea of these private properties for CSS custom properties so the other one that we could do is our speed now what I'm gonna do is I'm actually gonna comment these out because I find if there's too many things moving on the screen it makes me a little bit dizzy especially if they're moving in opposite directions and I've already prepped some of these off-screen just to do it with images as well I have this one also going to the right but I think for now we'll leave them both going the same direction and just do it with the speeds but we could mix and match as much as you want but again I just use Pravatar because it's really easy if you want this to be logos or anything else you could throw whatever it is you want at this basically and what we'll do is you can see here I have a data speed fast and a data speed slow so I could come in and we could let's just duplicate these two and then we could take this one and this one and make these data speed do a slow and a fast and then this would be my animation duration right for that one and that one so here just to slow down a little bit because I did that really fast we have the scroller data speed slow and then the data speed fast and I'm changing the animation duration and maybe for the fast one it's 20 seconds and for the slow one it's 120 seconds and so you can see that we change the speed that way and you probably would have consistent speeds on all of them if you were doing something like this but it is also something that you could come in technically and do and say style is equal to and say that my animation duration is actually going to be I don't know 500 seconds and it's gonna barely be moving so you could come in with some presets and you could presets that you decide based on what you're using for different speeds and then if somebody needed something little inline style could be a way to custom set it up it'd be really nice if we could get data attributes to be used for things like this and just plug that in here sadly at this moment in time we can't do that though it is something that's on the table for be possible with CSS eventually but yeah let's go back I'm not gonna put the or this actually I mixed them up sorry guys this one should be my fast and this one should be my slow that would make a lot more sense so yeah I'm not gonna turn or we'll turn the other ones on really fast but I find with the motion in different directions and stuff it makes me feel a little bit awkward but you can see there it is you can throw basically any content at this I believe it should work fine and let's just cover that up for a second and just to say if you enjoyed this video that you might also enjoy this one that is right here as well and with that I would really like to thank my enablers of awesome who are Johnny, Tim, Simon, Michael, James and Andrew as well as all my other patrons for their monthly support and of course until next time don't forget to make your report on the internet just a little bit more awesome