Home > OS >  Javascript, same function, multiple elements one at a time
Javascript, same function, multiple elements one at a time

Time:11-05

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 ids. 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 ids. 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.

  • Related