Search code examples
javascriptjquerytwitter-bootstrapmousewheel

JavaScript application: trying to make the "wheel" event listener discontinuous fails


I am working on a small "Picture browser" application in Bootstrap 4 and JavaScript.

As can be seen below, there is a "Prev" and a "Next" button that help navigate through the pictures.

I have bean trying to find a reliable method to make mouse well scrolling function the same as these buttons. Scrolling down would be the equivalent of clicking the "Next" button. Scrolling up would be the equivalent of clicking the "Prev" button.

var picArr = $('#pictures_list li');
// The index of the last element of the array is "maxIndex"
var maxIndex = picArr.length - 1;
// Initialize counter
var counter = 0;
var updateCounter = function(btn) {
  var direction = $(btn).data("direction")
  if (direction === "left") {
    if (counter > 0) {
      counter--;
    } else {
      counter = maxIndex;
    }
  } else {
    if (counter < maxIndex) {
      counter++;
    } else {
      counter = 0;
    }
  }
}

var showCount = function(container_id) {
  var pageCount = counter + 1;
  document.getElementById(container_id).innerHTML = "Picture " + pageCount + " of " + picArr.length;
}

var showSlide = function() {
  var mTop = (200 * counter) * (-1) + 'px';
  $('#pictures_list').animate({
    'marginTop': mTop
  }, 400);
}

showCount('show_count');

$('#controls button').on('click', function() {
  updateCounter(this);
  showSlide();
  showCount('show_count');
});

document.getElementById("picture_frame").addEventListener("wheel", function() {
  updateCounter(this);
  showSlide();
  showCount('show_count')
});
#picture_frame {
  width: 200px;
  height: 200px;
  margin: 5px auto;
  border: 1px solid #ccc;
  border-radius: 2px;
  overflow: hidden;
}

.controls>div {
  display: inline-block;
}

#picture_frame {
  height: 200px;
  overflow: hidden;
}

#pictures_list {
  flex-flow: row wrap;
  align-items: stretch;
  width: 200px;
}

#pictures_list li {
  display: flex;
  height: 200px;
  flex: 1 100%;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
  <div id="picture_frame">
    <ul id="pictures_list" class="list-unstyled d-flex">
      <li><img src="https://picsum.photos/200/200
" alt="First picture"></li>
      <li><img src="https://picsum.photos/200/200?gravity=east
" alt="Second picture"></li>
      <li><img src="https://picsum.photos/200/200?gravity=west
" alt="Third picture"></li>
      <li><img src="https://picsum.photos/200/200?gravity=north
" alt="Fourth picture"></li>
    </ul>
  </div>
  <div class="text-center mb-2">
    <span class="badge badge-primary" id="show_count"></span>
  </div>
  <div class="controls text-center">
    <div class="btn-group" id="controls">
      <button type="button" class="btn btn-primary btn-sm" data-direction="left">Prev</button>
      <button type="button" class="btn btn-primary btn-sm" data-direction="right">Next</button>
    </div>
  </div>
</div>

Firing the showSlide() function on mouse wheel does work, but the transition is too... continuous. I wish it would be identical to the transition triggered by the buttons.

What am I missing?


Solution

  • Here are several solutions:

    -- To prevent the scroll from scrolling multiple images, you can use this function:

    // Returns a function, that, as long as it continues to be invoked, will not
    // be triggered. The function will be called after it stops being called for
    // N milliseconds. If `immediate` is passed, trigger the function on the
    // leading edge, instead of the trailing.
    function debounce(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };
    

    See https://davidwalsh.name/javascript-debounce-function

    -- You should change the parameter of your function updateCounter so that it takes the direction of scrolling images. (example: boolean: true = next, false = previous)

    --To recover the direction of the scroll, refer to this answer : https://stackoverflow.com/a/45719399/4864628

    var picArr = $('#pictures_list li');
    // The index of the last element of the array is "maxIndex"
    var maxIndex = picArr.length - 1;
    // Initialize counter
    var counter = 0;
    var updateCounter = function(direction) {
      if (direction) {
        if (counter > 0) {
          counter--;
        } else {
          counter = maxIndex;
        }
      } else {
        if (counter < maxIndex) {
          counter++;
        } else {
          counter = 0;
        }
      }
    }
    
    var showCount = function(container_id) {
      var pageCount = counter + 1;
      document.getElementById(container_id).innerHTML = "Picture " + pageCount + " of " + picArr.length;
    }
    
    var showSlide = function() {
      var mTop = (200 * counter) * (-1) + 'px';
      $('#pictures_list').animate({
        'marginTop': mTop
      }, 400);
    }
    
    // Returns a function, that, as long as it continues to be invoked, will not
        // be triggered. The function will be called after it stops being called for
        // N milliseconds. If `immediate` is passed, trigger the function on the
        // leading edge, instead of the trailing.
        function debounce(func, wait, immediate) {
        	var timeout;
        	return function() {
        		var context = this, args = arguments;
        		var later = function() {
        			timeout = null;
        			if (!immediate) func.apply(context, args);
        		};
        		var callNow = immediate && !timeout;
        		clearTimeout(timeout);
        		timeout = setTimeout(later, wait);
        		if (callNow) func.apply(context, args);
        	};
        };
        
    showCount('show_count');
    
    $('#controls button').on('click', function() {
      updateCounter($(this).attr('data-direction') == 'left');
      showSlide();
      showCount('show_count');
    });
    
    var pictureFrame = document.getElementById("picture_frame");
    var onScroll = debounce(function(direction) {
      updateCounter(direction);
      showSlide();
      showCount('show_count')
    }, 100, true);
    
    pictureFrame.addEventListener("wheel", function(e) {
      e.preventDefault();
      var delta;
      if (event.wheelDelta){
          delta = event.wheelDelta;
      }else{
          delta = -1 * event.deltaY;
      }
      
      onScroll(delta >= 0);
    });
    #picture_frame {
      width: 200px;
      height: 200px;
      margin: 5px auto;
      border: 1px solid #ccc;
      border-radius: 2px;
      overflow: hidden;
    }
    
    .controls>div {
      display: inline-block;
    }
    
    #picture_frame {
      height: 200px;
      overflow: hidden;
    }
    
    #pictures_list {
      flex-flow: row wrap;
      align-items: stretch;
      width: 200px;
    }
    
    #pictures_list li {
      display: flex;
      height: 200px;
      flex: 1 100%;
    }
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="container">
      <div id="picture_frame">
        <ul id="pictures_list" class="list-unstyled d-flex">
          <li><img src="https://picsum.photos/200/200
    " alt="First picture"></li>
          <li><img src="https://picsum.photos/200/200?gravity=east
    " alt="Second picture"></li>
          <li><img src="https://picsum.photos/200/200?gravity=west
    " alt="Third picture"></li>
          <li><img src="https://picsum.photos/200/200?gravity=north
    " alt="Fourth picture"></li>
        </ul>
      </div>
      <div class="text-center mb-2">
        <span class="badge badge-primary" id="show_count"></span>
      </div>
      <div class="controls text-center">
        <div class="btn-group" id="controls">
          <button type="button" class="btn btn-primary btn-sm" data-direction="left">Prev</button>
          <button type="button" class="btn btn-primary btn-sm" data-direction="right">Next</button>
        </div>
      </div>
    </div>