I created a carousel slider similar to the one in UIkit slider, I want to use this function to multiple carousel in the same html page but it's not working as expected because I am using this line of code which query single carousel only:
const carousel = document.querySelector(".c-carousel-inner");
I've tried using this but apparently on button press it will trigger the other carousel also
const carousels = document.querySelectorAll(".c-carousel-inner");
carousels.forEach((carousel) => {
// code here
});
I've seen some solution which uses constructor, but I'm not sure if that's the best approach here
Here's my full javascript code without any applied code from above
const carousel = document.querySelector(".c-carousel-inner");
const carouselLength = carousel.children.length;
const carouselItemWidth = document.querySelector(".c-carousel-inner .c-carousel-item").offsetWidth;
const carouselButtons = document.querySelectorAll(".c-carousel-control");
let startX, scrollSum = 0, mouseIsDown, count = 0, mx, moving=false, lastDirection, prevScroll;
carouselButtons.forEach((button) => {
button.addEventListener("click", function(){
if (button.id == "carousel-right") {
if (prevScroll) {
mouseToLeft();
}
prevScroll = true;
moveToLeftSnap();
} else if (button.id == "carousel-left") {
if (!prevScroll) {
mouseToRight();
}
moveToRightSnap();
prevScroll = false;
}
});
})
function mouseToLeft() {
if (count >= carouselLength) {
count = 0;
}
count ;
for (i=0; i < carouselLength; i ) {
carousel.children[i].style.removeProperty("order");
if (i < count) {
carousel.children[i].style.order = "1";
}
}
}
function mouseToRight() {
if (count <= 0) {
count = carouselLength;
}
count--;
for (i=carouselLength-1; i>=0; i--) {
carousel.children[i].style.removeProperty("order");
if (i >= count) {
carousel.children[i].style.order = "-1";
}
}
}
function moveToRightSnap(value=carouselItemWidth) {
carousel.style.removeProperty("transition");
carousel.style.transform = `translate3d(-${value}px, 0, 0)`;
setTimeout(() => {
carousel.style.transition = `transform 0.342s ease`;
carousel.style.transform = `translate3d(0, 0, 0)`;
}, 1);
}
function moveToLeftSnap(value=0) {
carousel.style.removeProperty("transition");
carousel.style.transform = `translate3d(${value}, 0, 0)`;
setTimeout(() => {
carousel.style.transition = `transform 0.342s ease`;
carousel.style.transform = `translate3d(-${carouselItemWidth}px, 0, 0)`;
}, 1);
}
const startDrag = (e) => {
mouseIsDown = true;
startX = e.pageX;
mx = e.pageX;
carousel.style.removeProperty("transition");
}
const dragging = (e) => {
if (mouseIsDown) {
scrollSum = e.pageX - startX;
console.log(scrollSum);
if (mx < e.pageX) {
lastDirection = "right";
} else if (mx > e.pageX) {
lastDirection = "left"
}
if (scrollSum > 0) {
startX = e.pageX carouselItemWidth;
scrollSum = scrollSum - carouselItemWidth;
if (!prevScroll) {
mouseToRight();
}
} else if (prevScroll || scrollSum <= -carouselItemWidth) {
scrollSum = 0;
startX = e.pageX;
mouseToLeft();
prevScroll = false;
}
carousel.style.transform = `translate3d(${scrollSum}px,0,0)`;
mx = e.pageX;
}
}
const stopDrag = () => {
mouseIsDown = false;
if (lastDirection == "right") {
moveToRightSnap(scrollSum);
} else if (lastDirection == "left") {
moveToLeftSnap(scrollSum);
prevScroll = true;
}
}
carousel.addEventListener("mousedown", startDrag);
document.addEventListener("mousemove", dragging);
document.addEventListener("mouseup", stopDrag);
HTML code currently only single carousel is added here to make it short
<div >
<div >
<div >
<ul id="" >
<% topAiring.forEach((item, index) => { %>
<li >
<img src="<%= item.images.jpg['image_url'] %>" draggable="false" alt="...">
</li>
<% }) %>
</ul>
<button id="carousel-left" type="button">
<span aria-hidden="true"></span>
<span >Previous</span>
</button>
<button id="carousel-right" type="button">
<span aria-hidden="true"></span>
<span >Next</span>
</button>
</div>
</div>
</div>
CodePudding user response:
If you know which carousel is clicked or draged, you can do it easily. right?
-javascript
let carousel = document.createElement('div');
const carousels = document.querySelectorAll(".c-carousel");
carousels.forEach((e) => {
e.addEventListener("mousedown", () => {
carousel = e.children[0]; //get .c-carousel-inner from .c-carousel
});
});
//when you mouse down in any carousel (including button in it) this'll works and variable 'carousel' will be clicked carousel. ok?
...
...
carouselButtons.forEach((button) => {
button.addEventListener("mouseup", function () {
//I change click to mouseup because handling mousedown event on carousel must be first and then handle button event.
if (button.carousel-direction == "right") {
if (prevScroll) {
mouseToLeft();
}
prevScroll = true;
moveToLeftSnap();
} else if (button.carousel-direction == "left") {
if (!prevScroll) {
mouseToRight();
}
moveToRightSnap();
prevScroll = false;
}
});
});
...
...
-html
<button id="l_01" carousel-direction="left" type="button">
<span aria-hidden="true">.
</span>
<span >Previous</span>
</button>
<button id="r_01" carousel-direction="right" type="button">
<span aria-hidden="true">.
</span>
<span >Next</span>
</button>
I hope this answer can help you.
Good luck.
Oh, don't forget to vote my answer if it works.
CodePudding user response:
Thanks everyone! Much appreciated. I am not confident about how I approach this one but let me know if there's anything I can improve!
JS
const carousels = document.querySelectorAll(".c-carousel");
carousels.forEach((element) => {
const carousel = element.querySelector(".c-carousel-inner");
const carouselLength = carousel.children.length;
const carouselItemWidth = carousel.querySelector(".c-carousel-item").offsetWidth;
const carouselButtons = element.querySelectorAll(".c-carousel-control");
let startX, scrollSum = 0, mouseIsDown, count = 0, mx, lastDirection, prevScroll;
carouselButtons.forEach((button) => {
button.addEventListener("click", function(){
const btnDir = button.dataset.direction;
if (btnDir == "right") {
if (prevScroll) {
mouseToLeft();
}
prevScroll = true;
moveToLeftSnap();
} else if (btnDir == "left") {
if (!prevScroll) {
mouseToRight();
}
prevScroll = false;
moveToRightSnap();
}
});
});
function mouseToLeft() {
if (count >= carouselLength) {
count = 0;
}
count ;
for (i=0; i < carouselLength; i ) {
carousel.children[i].style.removeProperty("order");
if (i < count) {
carousel.children[i].style.order = "1";
}
}
}
function mouseToRight() {
if (count <= 0) {
count = carouselLength;
}
count--;
for (i=carouselLength-1; i>=0; i--) {
carousel.children[i].style.removeProperty("order");
if (i >= count) {
carousel.children[i].style.order = "-1";
}
}
}
function moveToRightSnap(value=carouselItemWidth) {
carousel.style.removeProperty("transition");
carousel.style.transform = `translate3d(-${value}px, 0, 0)`;
setTimeout(() => {
carousel.style.transition = `transform 0.342s ease`;
carousel.style.transform = `translate3d(0, 0, 0)`;
}, 1);
}
function moveToLeftSnap(value=0) {
carousel.style.removeProperty("transition");
carousel.style.transform = `translate3d(${value}, 0, 0)`;
setTimeout(() => {
carousel.style.transition = `transform 0.342s ease`;
carousel.style.transform = `translate3d(-${carouselItemWidth}px, 0, 0)`;
}, 1);
}
const startDrag = (e) => {
mouseIsDown = true;
startX = e.pageX;
mx = e.pageX;
carousel.style.removeProperty("transition");
}
const dragging = (e) => {
if (mouseIsDown) {
scrollSum = e.pageX - startX;
if (mx < e.pageX) {
lastDirection = "right";
} else if (mx > e.pageX) {
lastDirection = "left"
}
if (scrollSum > 0) {
startX = e.pageX carouselItemWidth;
scrollSum = scrollSum - carouselItemWidth;
if (!prevScroll) {
mouseToRight();
}
} else if (prevScroll || scrollSum <= -carouselItemWidth) {
scrollSum = 0;
startX = e.pageX;
mouseToLeft();
prevScroll = false;
}
carousel.style.transform = `translate3d(${scrollSum}px,0,0)`;
mx = e.pageX;
}
}
const stopDrag = () => {
mouseIsDown = false;
if (lastDirection == "right") {
moveToRightSnap(scrollSum);
} else if (lastDirection == "left") {
moveToLeftSnap(scrollSum);
prevScroll = true;
}
lastDirection="";
}
carousel.addEventListener("mousedown", startDrag);
document.addEventListener("mousemove", dragging);
document.addEventListener("mouseup", stopDrag);
});
HTML
<div >
<ul id="" >
<% topAiring.forEach((item, index) => { %>
<li >
<img src="<%= item.images.jpg['image_url'] %>" draggable="false" alt="...">
</li>
<% }) %>
</ul>
<button data-direction="left" type="button">
<span aria-hidden="true"></span>
<span >Previous</span>
</button>
<button data-direction="right" type="button">
<span aria-hidden="true"></span>
<span >Next</span>
</button>
</div>
CodePudding user response:
This code as it is has some fundamental issues. Most noticeably, in the case of multiple carousels you will get duplicated id
s. id
should be a unique identifier for any element.
(Please see the event bubbling solution below as my recommended approach)
1 - Building upon your existing approach:
Instead of id
, you can use data
attribute on your buttons. For example:
<div >
<div >
<div >
<ul id="carousel_01" >
<% topAiring.forEach((item, index) => { %>
<li >
<img src="<%= item.images.jpg['image_url'] %>" draggable="false" alt="...">
</li>
<% }) %>
</ul>
<button id="l_01" data-direction="left" type="button">
<span aria-hidden="true"></span>
<span >Previous</span>
</button>
<button id="r_01" data-direction="right" type="button">
<span aria-hidden="true"></span>
<span >Next</span>
</button>
</div>
</div>
</div>
Your function will then become something like:
carouselButtons.forEach((button) => {
button.addEventListener("click", function(){
const btnIdTail = button.id.split("_")[1];
const carousel = document.getElementById(`carousel_${btnIdTail}`);
const btnDir = button.dataset.direction;
if (btnDir == "right") {
if (prevScroll) {
mouseToLeft(carousel);
}
prevScroll = true;
moveToLeftSnap(carousel);
} else if (btnDir == "left") {
if (!prevScroll) {
mouseToRight(carousel);
}
moveToRightSnap(carousel);
prevScroll = false;
}
});
})
Get the carousel by their id
based on the button id
. In your handler functions use the specific carousel which you pass in, like:
function mouseToLeft(carousel) {
const carouselLength = carousel.children.length;
if (count >= carouselLength) {
count = 0;
}
count ;
for (i=0; i < carouselLength; i ) {
carousel.children[i].style.removeProperty("order");
if (i < count) {
carousel.children[i].style.order = "1";
}
}
}
function moveToRightSnap(carousel, value=carouselItemWidth) {
//...
}
function moveToLeftSnap(carousel, value=0) {
//...
}
and do the same for other functions.
Don't forget to id
your carousels with the same kind of numbering you used for its button
like:
<ul id="carousel_01" >
To implement dragging handles, get the target carousel from the event and pass it on to the functions as above. Like:
const startDrag = (e) => {
// get the carousel
const carousel = e.target;
mouseIsDown = true;
startX = e.pageX;
mx = e.pageX;
carousel.style.removeProperty("transition");
}
const dragging = (e) => {
// get the carousel, use and pass it
const carousel = e.target;
const carouselItemWidth = carousel.offsetWidth;
if (mouseIsDown) {
scrollSum = e.pageX - startX;
console.log(scrollSum);
if (mx < e.pageX) {
lastDirection = "right";
} else if (mx > e.pageX) {
lastDirection = "left"
}
if (scrollSum > 0) {
startX = e.pageX carouselItemWidth;
scrollSum = scrollSum - carouselItemWidth;
if (!prevScroll) {
mouseToRight(carousel);
}
} else if (prevScroll || scrollSum <= -carouselItemWidth) {
scrollSum = 0;
startX = e.pageX;
mouseToLeft(carousel);
prevScroll = false;
}
carousel.style.transform = `translate3d(${scrollSum}px,0,0)`;
mx = e.pageX;
}
}
const stopDrag = (e) => {
const carousel = e.target;
mouseIsDown = false;
if (lastDirection == "right") {
moveToRightSnap(carousel, scrollSum);
} else if (lastDirection == "left") {
moveToLeftSnap(carousel, scrollSum);
prevScroll = true;
}
}
You will need to localise scrollSum
and other variables that can impact other carousels. I will leave that to you
2 - Solution with event bubbling:
Alternatively, you can make use of event bubbling that will reduce the number of listeners and negates the need for id
s. Here is one way to achieve that:
const carCtrl = {
mouseState: "",
index: 0,
car: null,
width: 0,
len: 0,
dir: "",
isMoving: false,
startX: [],
mX: [],
scrollSum: [],
count: [],
previous: [],
lastDir: [],
setArrays: (qty) => {
const fill = (v) => new Array(qty).fill(v);
this.count = fill(0);
this.startX = fill(0);
this.mX = fill(0);
this.scrollSum = fill(0);
this.previous = fill(false);
this.lastDir = fill("");
},
set: (e, index) => {
this.car = e.target;
// check if the click event has button with direction data
// if not it returns false for early return to terminate the even
if (Object.hasOwn(this.car.dataSet, "direction"))
this.dir = this.car.dataSet.direction;
else return false;
this.width = this.getWidth(e.target);
this.len = e.target.children.length;
this.index = index;
return true;
},
getWidth: (el) => {
if (!el) return 0;
if (el?.children?.length) {
return el.children[0].offsetWidth;
}
return el.offsetWidth;
},
trigger:(e, index)=>{
if (!this.set(e)) return; // early return. not a click on a button
const motion = this.dir=== "right" ?
{dir: "left", pre: true, val: 0} :
{dir: "right", pre: false, val: this.width};
if (this.previous[index] === motion.pre) {
this.move(motion.dir);
}
this.previous[index] = motion.pre;
this.snap(motion.val, this.dir);
},
move: (dir) => {
const index = this.index;
if (dir === "left") {
// move left
if (this.count[index] >= this.len) {
this.count[index] = 0;
}
this.count[index] ;
for (i = 0; i < this.len; i ) {
this.car.children[i].style.removeProperty("order");
if (i < this.count[index]) {
this.car.children[i].style.order = "1";
}
}
} else {
// move right
if (this.count[index] <= 0) {
this.count[index] = carouselLength;
}
this.count[index]--;
for (i = this.len - 1; i >= 0; i--) {
this.car.children[i].style.removeProperty("order");
if (i >= this.count[index]) {
this.car.children[i].style.order = "-1";
}
}
}
},
snap: (val, dir) => {
const fromX = dir === "right" ? -val : val;
const toX = (dir === "right") ? -this.width : 0;
this.car.style.removeProperty("transition");
this.car.style.transform = `translate3d(${fromX}px, 0, 0)`;
setTimeout(() => {
this.car.style.transition = `transform 0.342s ease`;
this.car.style.transform = `translate3d(${toX}px, 0, 0)`;
}, 1);
},
startDrag: (e, i) => {
this.mouseState = "down";
this.startX[i] = e.pageX;
this.mX[i] = e.pageX;
e.target.style.removeProperty("transition");
},
dragging: (e, i) => {
const width = e.target.offsetWidth;
if (this.mouseState === "down") {
this.scrollSum[i] = e.pageX - this.startX[i];
console.log(this.scrollSum[i]);
this.lastDir[i] = this.mX[i] < e.pageX ? "right" : "left";
if (this.scrollSum[i] > 0) {
this.startX[i] = e.pageX width;
this.scrollSum[i] = this.scrollSum[i] - width;
if (!this.previous[i]) this.move("right");
} else if (this.previous[i] || this.scrollSum[i] <= -width) {
this.scrollSum[i] = 0;
this.startX[i] = e.pageX;
this.move("left");
this.previous[i] = false;
}
carousel.style.transform = `translate3d(${this.scrollSum[i]}px,0,0)`;
this.mX[i] = e.pageX;
}
},
stopDrag: (e, i) => {
this.mouseState = "up";
this.snap(this.scrollSum[i], this.lastDir[i]);
this.previous[i] = this.lastDir[i] === "left" ? true : this.lastDir[i];
},
};
window.addEventListener("load", caresouling);
function caresouling() {
const carousels = document.querySelectorAll(".c-carousel-inner");
carCtrl.setArrays(carousels.length);
const events = [
["click", "trigger"],
["mousedown", "startDrag"],
["mousemove", "dragging"],
["mouseup", "stopDrag"],
];
carousels.forEach((car, i) => {
events.forEach(evnt => {
car.addEventListener(evnt[0], (e, i) => carCtrl[evnt[1]](e, i));
});
});
}
<div >
<div >
<!-- First caresoul -->
<div >
<ul >
<% topAiring1.forEach((item, index) => { %>
<li >
<img src="<%= item.images.jpg['image_url'] %>" draggable="false" alt="...">
</li>
<% }) %>
</ul>
<button data-direction="left" type="button">
<span aria-hidden="true"></span>
<span >Previous</span>
</button>
<button data-direction="right" type="button">
<span aria-hidden="true"></span>
<span >Next</span>
</button>
</div>
<!-- another caresoul -->
<div >
<ul >
<% topAiring2.forEach((item, index) => { %>
<li >
<img src="<%= item.images.jpg['image_url'] %>" draggable="false" alt="...">
</li>
<% }) %>
</ul>
<button data-direction="left" type="button">
<span aria-hidden="true"></span>
<span >Previous</span>
</button>
<button data-direction="right" type="button">
<span aria-hidden="true"></span>
<span >Next</span>
</button>
</div>
</div>
</div>
⚠️NOTE! The snippet won't work without carousels being correctly populated!
(Please note that I did not consider the logic, so I cannot be sure if this logic works or is the best approach. My answer just is an attempt to give a working solution based on your existing logic.)
In this solution, you get the event listeners on the high level carousel, then we check to see if the use hit any of the directional buttons and trigger the move. The object carCtrl keeps the reference to all the variables your code's logic needs to track. Because they're arrays in an object they are passes as reference and so will be shared between all carousels.