Reputation: 115
I have created a div that can be scrolled with a mouse just like on mobile, i.e you click on the container, move your mouse and the element gets scrolled.
Nothing fancy, here is the link to codepen: https://codepen.io/kulaska/pen/xxKdRGw
Here is the HTML structure:
<div class="container">
<div class="child"></div>
...
<div class="child"></div>
</div>
This is the main chunk of JS file:
container.addEventListener("mousemove", ({clientX: newX}) => {
if (!firing) return;
let newPos = currPos - (newX - prevX);
let availableOffset = container.scrollWidth - container.clientWidth;
if (newPos > availableOffset)
newPos = availableOffset;
if (newPos < 0)
newPos = 0;
prevX = newX;
container.scrollTo(newPos, 0);
currPos = newPos;
})
I use flex container to create a container that doesn't wrap the content:
.container {
display: flex;
flex-wrap: nowrap;
..
}
Imagine that there are 20 images in that container. The question is how would you make this div infinitely scrollable like when you scroll it till the end(till the picture number 20 shows up) and then the picture number 1,2,3,4 etc after it, so all the content is repeated.
And it can go on and on and on, so just putting a lot of DOM nodes in the container is not an option, because it would be too costly in terms of performance.
I thought of a few JS solutions, but they are all pretty bad for performance. How would you solve this?
Upvotes: 0
Views: 844
Reputation: 194
I did some edits on your codepen and i came up with this solution. I just added a few lines of code to your mousemove
event. You already check if you reach the start or the end of the container, right? So let's extend those adding just a couple of lines of code which does as follows:
when you try to go lower than zero (so, let's say from your child with index 0 to -1) you just cut the last-child element of the container and paste before the first.
Same thing when you reach the end of the container (let's say, element with index 19 and you try to reach for the 20)
here's the code i edited:
if (newPos > availableOffset){
elementToCut = container.querySelector('.child:first-child')
container.appendChild(elementToCut)
newPos = availableOffset - elementToCut.offsetWidth;
elementToCut.removeChild
}
if (newPos < 0){
firstElement = container.querySelector('.child:first-child')
elementToCut = container.querySelector('.child:last-child')
container.insertBefore(elementToCut,firstElement);
newPos = 0 + elementToCut.offsetWidth;
elementToCut.removeChild
}
then you add or remove (depends if you're scrolling to the start or to the end) the width of the "moved" element to your current newPos. Let me know if it fits your needs or if helped !!
edit: i'd create a couple of functions to make some order in this code (for example, the code which cuts&pastes the elements)
const container = document.querySelector(".container");
const children = document.querySelector(".child");
let [firing, currPos, prevX] = [false, 0, 0];
container.addEventListener("mousedown", e => {
prevX = e.clientX;
e.preventDefault();
firing = true;
});
container.addEventListener("mousemove", ({
clientX: newX
}) => {
if (!firing) return;
let newPos = currPos - (newX - prevX);
let availableOffset = container.scrollWidth - container.clientWidth;
if (newPos > availableOffset) {
elementToCut = container.querySelector('.child:first-child')
container.appendChild(elementToCut)
newPos = availableOffset - elementToCut.offsetWidth;
elementToCut.removeChild
}
if (newPos < 0) {
firstElement = container.querySelector('.child:first-child')
elementToCut = container.querySelector('.child:last-child')
container.insertBefore(elementToCut, firstElement);
newPos = 0 + elementToCut.offsetWidth;
elementToCut.removeChild
}
prevX = newX;
container.scrollTo(newPos, 0);
currPos = newPos;
})
container.addEventListener("mouseup", () => {
firing = false;
})
container.addEventListener("mouseleave", () => {
firing = false;
})
.container {
display: flex;
flex-wrap: nowrap;
white-space: nowrap;
overflow-x: scroll;
overflow-y: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
}
.container::-webkit-scrollbar {
display: none;
}
.child {
flex: 1 0 200px;
height: 200px;
margin: 10px;
}
.green {
background: green;
}
.blue {
background: blue;
}
<div class="container">
<div class="child green">1</div>
<div class="child blue">2</div>
<div class="child green">3</div>
<div class="child blue">4</div>
<div class="child green">5</div>
<div class="child blue">6</div>
<div class="child green">7</div>
<div class="child blue">8</div>
<div class="child green">9</div>
<div class="child blue">10</div>
<div class="child green">11</div>
<div class="child blue">12</div>
<div class="child green">13</div>
<div class="child blue">14</div>
</div>
Upvotes: 3