最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

jquery - JavaScript - Draggable Gallery Slider - Stack Overflow

programmeradmin1浏览0评论

I'm using a function from W3Schools as a starter point to make a draggable gallery slider (.slider-all-items in my code). Few lines have been removed from the original, to prevent dragging vertically.

I'm trying to achieve something very similar to this jsfiddle example, a draggable slideshow, and:

  • always on full screen (.item { width: 100vw || 100%})
  • responsive even on window.resize
  • and without the infinite mode (check the jsfiddle above).

Added 3 slides, each slide is taking 100% of the window (responsive).

<div data-item="slider-full" class="slider-container">
    <div class="slider-all-items">
        <div class="slider-item slider-item1"></div>
        <div class="slider-item slider-item2"></div>
        <div class="slider-item slider-item3"></div>
    </div>
</div>

Now, I don't need the infinite mode, but the animation that toggles between the slides is a part of the Draggable Slider.
If you tried dragging the slides (the direction doesn't matter) in the jsfiddle example just a few px it won't get the (next or prev) slide and center it, in short the function is prevented.

To animate the left and right .items, I did something like this:

/* If the user is dragging to -1 (no slide), it'll set the slider `left` to *0* */

if (sliderLeft >= 0) {theSlider.style.left = "0px";}

The same for the last .item (slide):

/* If dragging greater than the last one, set to the last with animation */
if (sliderRight <= sliderWidth) {theSlider.style.left = on the last item}

Animation - Transition:
The property transition and it's value 0.5s ease is stored in a class named .shifting.

If any of both (the above conditions) is true, .shifting class will be added to the Slider, it'll transition: 0.5s (as mentioned above). And at the same time, a setTimeout() function, delay 0.5s will be executed, which will add and remove it. (The delay is to balance the time it takes to the transition to finish pletely before removing the class). Important to mention that the class .shifting should be removed to keep the drag fast and smooth. In short: It'll have the class only in action (when the mouse is free).

.shifting{
    transition: all 0.5s ease;
} 

What I'm trying to achieve:

Make the slides draggable (full screen - responsive - without the infinite mode), and:

if the drag is only X(small number)px prevent it (as in jsfiddle - using .shifting with my code).

if more than X(greater than the x before)px, get the asked slide and center it.

Live (Similar) Example:

EDIT: You have to sign in to see it, sorry didn't notice and couldn't find another example

  • Fiverr - scroll down and check the banner (slider). Ignore the infinite mode and the navigation dots.

Things I tried:

  1. Using for and forEach loops to get the items but couldn't connect between them and the theSlider.style.left.

  2. Tried The jsfiddle example above with some changes (set to full screen) and it works but the problem is on window.resize it glitches and need to refresh the page the make it works as expected.

  3. Refreshing a specific content in the page and not the page itself (not reloading), by using JavaScript or jQuery didn't work.

When refreshing a content in the page didn't work, I had an idea using the jsfiddle mentioned above changed the width and height to 100% and 100vh, and on window.resize` get the current slide index, remove the slider, and append it again with the stored index, but it may glitch sometimes so decided to stick with my code, and the questions is:

How can I connect between the slides to make the slider works as required?

My Code:

// get the slider
var theSlider = document.querySelector(".slider-all-items");
// get the items in the slider
var sliderItem = document.querySelectorAll('.slider-item');

// variables saved for later
var sliderWidth;
var sliderRight;

// run the function
dragElement(theSlider);


function dragElement(theSlider) {
    var pos1 = 0, pos3 = 0;
    theSlider.onmousedown = dragMouseDown;

    function dragMouseDown(e) {
        e = e || window.event;
        e.preventDefault();
        // get the mouse cursor position at startup:
        pos3 = e.clientX;
        document.onmouseup = closeDragElement;
        // call a function whenever the cursor moves:
        document.onmousemove = elementDrag;
    }

    function elementDrag(e) {
        e = e || window.event;
        e.preventDefault();
        // calculate the new cursor position:
        pos1 = pos3 - e.clientX;
        pos3 = e.clientX;

        // set the element's new position:
        theSlider.style.left = (theSlider.offsetLeft - pos1) + "px";
    }
    
    function closeDragElement() {
        // add the class .shifting to the slider for every css change (transition)
        theSlider.classList.add("shifting");
        
        // get each item width
        sliderWidth = theSlider.getBoundingClientRect().width  / sliderItem.length;
        // get the right side position of the slider
        sliderRight = theSlider.getBoundingClientRect().right;
        // get the left side position of the slider
        sliderLeft = theSlider.getBoundingClientRect().left;

        if(sliderLeft >= 0){
            theSlider.style.left = "0px";
        }

        if(sliderRight <= sliderWidth){            
            theSlider.style.left =  -Math.abs((sliderWidth * sliderItem.length) - sliderWidth) + "px";
        }
        
        // delay 0.5s, then remove the class .shifting when finished checking and styling
        // .shifting {transition: all 0.5s ease;}
        setTimeout(() => {
            theSlider.classList.remove("shifting");
        }, 500);

        // stop moving when mouse button is released:
        document.onmouseup = null;
        document.onmousemove = null;
    }
}
*, *:before, *:after {
    margin: 0;
    padding: 0;
    -webkit-box-sizing: border-box; 
    box-sizing: border-box;
}

.slider-container {
    position: relative;
    height: 80vh;
    overflow-x: scroll;
    overflow-y: hidden;
}

.slider-container::-webkit-scrollbar {
    display: none !important;
}

.slider-all-items {
    position: absolute;
    display: inline-flex;    
}

.slider-item {
    width: calc(100vw - 0px);
    height: 80vh;
    cursor: grab;
    display: block;
}

.slider-item1 {
    background: red;
}

.slider-item2 {
    background-color: blue;
}

.slider-item3 {
    background-color: yellow;
}

.shifting{
    transition: all 0.5s ease;
}
<div data-item="slider-full" class="slider-container">
    <div class="slider-all-items">
        <div class="slider-item slider-item1"></div>
        <div class="slider-item slider-item2"></div>
        <div class="slider-item slider-item3"></div>
    </div>
</div>

I'm using a function from W3Schools as a starter point to make a draggable gallery slider (.slider-all-items in my code). Few lines have been removed from the original, to prevent dragging vertically.

I'm trying to achieve something very similar to this jsfiddle example, a draggable slideshow, and:

  • always on full screen (.item { width: 100vw || 100%})
  • responsive even on window.resize
  • and without the infinite mode (check the jsfiddle above).

Added 3 slides, each slide is taking 100% of the window (responsive).

<div data-item="slider-full" class="slider-container">
    <div class="slider-all-items">
        <div class="slider-item slider-item1"></div>
        <div class="slider-item slider-item2"></div>
        <div class="slider-item slider-item3"></div>
    </div>
</div>

Now, I don't need the infinite mode, but the animation that toggles between the slides is a part of the Draggable Slider.
If you tried dragging the slides (the direction doesn't matter) in the jsfiddle example just a few px it won't get the (next or prev) slide and center it, in short the function is prevented.

To animate the left and right .items, I did something like this:

/* If the user is dragging to -1 (no slide), it'll set the slider `left` to *0* */

if (sliderLeft >= 0) {theSlider.style.left = "0px";}

The same for the last .item (slide):

/* If dragging greater than the last one, set to the last with animation */
if (sliderRight <= sliderWidth) {theSlider.style.left = on the last item}

Animation - Transition:
The property transition and it's value 0.5s ease is stored in a class named .shifting.

If any of both (the above conditions) is true, .shifting class will be added to the Slider, it'll transition: 0.5s (as mentioned above). And at the same time, a setTimeout() function, delay 0.5s will be executed, which will add and remove it. (The delay is to balance the time it takes to the transition to finish pletely before removing the class). Important to mention that the class .shifting should be removed to keep the drag fast and smooth. In short: It'll have the class only in action (when the mouse is free).

.shifting{
    transition: all 0.5s ease;
} 

What I'm trying to achieve:

Make the slides draggable (full screen - responsive - without the infinite mode), and:

if the drag is only X(small number)px prevent it (as in jsfiddle - using .shifting with my code).

if more than X(greater than the x before)px, get the asked slide and center it.

Live (Similar) Example:

EDIT: You have to sign in to see it, sorry didn't notice and couldn't find another example

  • Fiverr - scroll down and check the banner (slider). Ignore the infinite mode and the navigation dots.

Things I tried:

  1. Using for and forEach loops to get the items but couldn't connect between them and the theSlider.style.left.

  2. Tried The jsfiddle example above with some changes (set to full screen) and it works but the problem is on window.resize it glitches and need to refresh the page the make it works as expected.

  3. Refreshing a specific content in the page and not the page itself (not reloading), by using JavaScript or jQuery didn't work.

When refreshing a content in the page didn't work, I had an idea using the jsfiddle mentioned above changed the width and height to 100% and 100vh, and on window.resize` get the current slide index, remove the slider, and append it again with the stored index, but it may glitch sometimes so decided to stick with my code, and the questions is:

How can I connect between the slides to make the slider works as required?

My Code:

// get the slider
var theSlider = document.querySelector(".slider-all-items");
// get the items in the slider
var sliderItem = document.querySelectorAll('.slider-item');

// variables saved for later
var sliderWidth;
var sliderRight;

// run the function
dragElement(theSlider);


function dragElement(theSlider) {
    var pos1 = 0, pos3 = 0;
    theSlider.onmousedown = dragMouseDown;

    function dragMouseDown(e) {
        e = e || window.event;
        e.preventDefault();
        // get the mouse cursor position at startup:
        pos3 = e.clientX;
        document.onmouseup = closeDragElement;
        // call a function whenever the cursor moves:
        document.onmousemove = elementDrag;
    }

    function elementDrag(e) {
        e = e || window.event;
        e.preventDefault();
        // calculate the new cursor position:
        pos1 = pos3 - e.clientX;
        pos3 = e.clientX;

        // set the element's new position:
        theSlider.style.left = (theSlider.offsetLeft - pos1) + "px";
    }
    
    function closeDragElement() {
        // add the class .shifting to the slider for every css change (transition)
        theSlider.classList.add("shifting");
        
        // get each item width
        sliderWidth = theSlider.getBoundingClientRect().width  / sliderItem.length;
        // get the right side position of the slider
        sliderRight = theSlider.getBoundingClientRect().right;
        // get the left side position of the slider
        sliderLeft = theSlider.getBoundingClientRect().left;

        if(sliderLeft >= 0){
            theSlider.style.left = "0px";
        }

        if(sliderRight <= sliderWidth){            
            theSlider.style.left =  -Math.abs((sliderWidth * sliderItem.length) - sliderWidth) + "px";
        }
        
        // delay 0.5s, then remove the class .shifting when finished checking and styling
        // .shifting {transition: all 0.5s ease;}
        setTimeout(() => {
            theSlider.classList.remove("shifting");
        }, 500);

        // stop moving when mouse button is released:
        document.onmouseup = null;
        document.onmousemove = null;
    }
}
*, *:before, *:after {
    margin: 0;
    padding: 0;
    -webkit-box-sizing: border-box; 
    box-sizing: border-box;
}

.slider-container {
    position: relative;
    height: 80vh;
    overflow-x: scroll;
    overflow-y: hidden;
}

.slider-container::-webkit-scrollbar {
    display: none !important;
}

.slider-all-items {
    position: absolute;
    display: inline-flex;    
}

.slider-item {
    width: calc(100vw - 0px);
    height: 80vh;
    cursor: grab;
    display: block;
}

.slider-item1 {
    background: red;
}

.slider-item2 {
    background-color: blue;
}

.slider-item3 {
    background-color: yellow;
}

.shifting{
    transition: all 0.5s ease;
}
<div data-item="slider-full" class="slider-container">
    <div class="slider-all-items">
        <div class="slider-item slider-item1"></div>
        <div class="slider-item slider-item2"></div>
        <div class="slider-item slider-item3"></div>
    </div>
</div>

Share Improve this question edited Aug 26, 2021 at 15:37 001 asked Jan 1, 2021 at 19:39 001001 2,0597 silver badges23 bronze badges 2
  • could you please provide more specific details about the effect that you try to achive? – noam gaash Commented Jan 17, 2021 at 17:17
  • 2 I would like to help but it is very unclear what you want to improve in the behaviour of the code. You specify that you are aiming for " responsive even on window.resize, always on full screen (items width: 100vw or 100%) and without the infinite mode.". However it already resizes on window resize, always goes 100% of the container width, and does not have infinite scroll. I do not see what is missing. Separately, I do not understand what you mean by refreshing specific content in part of the screen. How do you do that? – ProfDFrancis Commented Jan 17, 2021 at 22:27
Add a ment  | 

3 Answers 3

Reset to default 1

I am not sure if i understand everything correctly, but maybe it helps you. Here is my code example for a full screen slider with your wished "shifting" class.

const slider = document.querySelector('.slider-container')
const items = document.querySelectorAll('.slider-item')

// active item index
let selectedIndex = 0

// move item positions depend on selected index
const applyItemPositions = () => {
  items.forEach((item, index) => {
    // add shifting class
    item.classList.add('shifting')
    item.style.left = `${ (index - selectedIndex) * 100 }%`
  })
  
  // remove shifting class after 500ms
  setTimeout(() => {
    items.forEach((item, index) => {
      item.classList.remove('shifting')
    })
  }, 500)
}

// initial positions
applyItemPositions()

// helpers
const decreaseSelectedIndex = () => {
  if (selectedIndex > 0) {
    selectedIndex -= 1
    applyItemPositions()
  }
}

const increaseSelectedIndex = () => {
  if (selectedIndex < items.length - 1) {
    selectedIndex += 1
    applyItemPositions()
  }
}

// swipe events

startPos = [0, 0]

// swipestart
slider.addEventListener('pointerdown', (e) => {
  startPos = [e.clientX, e.clientY]
})

// swipeend
slider.addEventListener('pointerup', (e) => {
  const endPos = [e.clientX, e.clientY]
  
  // threshold (min 20px swipe distance)
  if (Math.abs(endPos[0] - startPos[0]) < 20) {
    return
  }
  
  if (endPos[0] < startPos[0]) {
    increaseSelectedIndex()
  }
  
  if (endPos[0] > startPos[0]) {
    decreaseSelectedIndex()
  }
})
html, body {
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
}

.slider-container {
  position: relative;
  width: 100%;
  height: 100%;
  background: #eee;
  overflow: hidden;
}
  
.slider-item {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.slider-item1 {
  background: red;
}

.slider-item2 {
  background: blue;
}

.slider-item3 {
  background: yellow;
}

.shifting{
  transition: all 0.5s ease;
}
<div data-item="slider-full" class="slider-container">
  <div class="slider-item slider-item1"></div>
  <div class="slider-item slider-item2"></div>
  <div class="slider-item slider-item3"></div>
</div>

Have you considered using CSS' scroll-snapping? This already natively does what you want (without any need for JS). The only thing you need to add yourself is the ability to drag, but only if you want to enable dragging on non-touch devices.

I've whipped up a quick demo, including dragging support (via JS), below:

//JS for dragging support - wrote this quickly, you could probably fine tune this. 
//Only handles X movement (could be modified to do Y as well).

const ele = document.getElementById('container');
let startPos = 0;

const mouseMoveHandler = function(e) {
  // How far the mouse has been moved
  const dx = e.clientX - startPos;
  // Scroll the element
  ele.scrollLeft = ele.scrollLeft - dx;
  // Store mousepos for next scroll
  startPos = e.clientX;
};

//as soon as user interaction begins, start handling the movement
document.addEventListener('mousedown', (e) => {
  //store the current cursor position as the starting position
  startPos = e.clientX;
  //add the dragging class which temporarily disables the snapping, or else the dragging (scrolling) doesn't work
  ele.classList.add('dragging');
  ele.addEventListener('mousemove', mouseMoveHandler);
});

//when interaction stops, stop handling the movement
document.addEventListener('mouseup', (e) => {
  ele.classList.remove('dragging');
  ele.removeEventListener('mousemove', mouseMoveHandler);
});
/* CSS based on codepen example from https://blog.logrocket./how-to-use-css-scroll-snap/ */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: 'Helvetica', sans-serif;
}

.container {
  cursor: grab;
  width: 60vw;
  height: 70vh;
  margin: 15vh auto;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
  color: white;
  background-color: oldlace;
  display: flex;
  align-items: center;
}

.container.dragging {
  scroll-snap-type: none;
  user-select: none;
  /* prevents accidental text selection */
}

.child {
  margin-left: 0.5rem;
  height: 90%;
  scroll-snap-align: start;
  padding: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.child:nth-child(1n) {
  background-color: darkorchid;
  flex: 0 0 80%;
}

.child:nth-child(2n) {
  background-color: indigo;
  flex: 0 0 60%;
}

.child:nth-child(3n) {
  background-color: navy;
  flex: 0 0 40%;
}

.child:nth-child(4n) {
  background-color: palegreen;
  flex: 0 0 50%;
}

.child:nth-child(5n) {
  background-color: yellow;
  flex: 0 0 80%;
}

.child:nth-child(6n) {
  background-color: orange;
  flex: 0 0 60%;
}

.child:nth-child(7n) {
  background-color: tomato;
  flex: 0 0 80%;
}
<main class="container" id="container">
  <div class="child">
    <h2>Image 1</h2>
  </div>
  <div class="child">
    <h2>Image 2</h2>
  </div>
  <div class="child">
    <h2>Image 3</h2>
  </div>
  <div class="child">
    <h2>Image 4</h2>
  </div>
  <div class="child">
    <h2>Image 5</h2>
  </div>
  <div class="child">
    <h2>Image 6</h2>
  </div>
  <div class="child">
    <h2>Image 7</h2>
  </div>
</main>

I believe we can remove the glitch as well as achieving the div's(slider-item) of equal width as you have already used full screen = width 100%) without reloading.

By simply using ResizeObserver : I believe all we have to do is to call "closeDragElement" function on window resize as this is the only function that calculates the size of the sliders.

CDN for ResizeObserver.

CODE

        // get the slider
        var theSlider = document.querySelector(".slider-all-items");
        // get the items in the slider
        var sliderItem = document.querySelectorAll('.slider-item');

        // variables saved for later
        var sliderWidth;
        var sliderRight;

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            // get the mouse cursor position at startup:
            pos3 = e.clientX;
            document.onmouseup = closeDragElement;
            // call a function whenever the cursor moves:
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            // calculate the new cursor position:
            pos1 = pos3 - e.clientX;
            pos3 = e.clientX;

            // set the element's new position:
            theSlider.style.left = (theSlider.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            // get each item width
            sliderWidth = theSlider.getBoundingClientRect().width / sliderItem.length;
            // get the right side position of the slider
            sliderRight = theSlider.getBoundingClientRect().right;
            // get the left side position of the slider
            sliderLeft = theSlider.getBoundingClientRect().left;

            if (sliderLeft >= 0) {
                theSlider.style.left = "0px";
            }

            if (sliderRight <= sliderWidth) {
                theSlider.style.left = -Math.abs((sliderWidth * sliderItem.length) - sliderWidth) + "px";
            }
            // stop moving when mouse button is released:
            document.onmouseup = null;
            document.onmousemove = null;
        }

        function dragElement(theSlider) {
            var pos1 = 0, pos3 = 0;
            theSlider.onmousedown = dragMouseDown;
            theSlider.addEventListener('resize', closeDragElement);
        }

        dragElement(theSlider);

        /* global ResizeObserver */

        const ro = new ResizeObserver(entries => {
            for (let entry of entries) {
                closeDragElement();
            }
        });

        ro.observe(theSlider); //<-- NOTICE HERE
*,
        *:before,
        *:after {
            margin: 0;
            padding: 0;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
        }

        .slider-container {
            position: relative;
            height: 80vh;
            overflow-x: scroll;
            overflow-y: hidden;
        }

        .slider-container::-webkit-scrollbar {
            display: none !important;
        }

        .slider-all-items {
            position: absolute;
            display: inline-flex;
        }

        .slider-item {
            width: calc(100vw - 0px);
            height: 80vh;
            cursor: grab;
            display: block;
        }

        .slider-item1 {
            background: red;
        }

        .slider-item2 {
            background-color: blue;
        }

        .slider-item3 {
            background-color: yellow;
        }

        .shifting {
            transition: all 0.5s ease;
        }
<div data-item="slider-full" class="slider-container">
        <div class="slider-all-items">
            <div class="slider-item slider-item1"></div>
            <div class="slider-item slider-item2"></div>
            <div class="slider-item slider-item3"></div>
        </div>
    </div>

Before: https://www.dropbox./s/y0oy4rwnlj5la4g/before.gif?dl=0

After: https://www.dropbox./s/jsqba0ef61ibef1/after.gif?dl=0

发布评论

评论列表(0)

  1. 暂无评论