Jerky horizontal scroll on hover

7.2k views Asked by At

I'm using the following JS code to build a horizontal image carousel that scrolls on hover. The mousemove event detects the position of the mouse over the container and scrolls to the left or to the right accordingly. Everything works as I expect but if I move the mouse over the container while the animation is running, it becomes a bit jerky.

Is there any solution for this?

Thanks

JS:

$( '.carousel-frame ul' ).mousemove( function(e) {
    var container = $(this).parent();
    if ((e.pageX - container.offset().left) < container.width() / 2) {
        var direction = function() {
            container.animate({scrollLeft: '-=600'}, 1000, 'linear', direction);
        }
        container.stop().animate({scrollLeft: '-=600'}, 1000, 'linear', direction);
    } else {
        var direction = function() {
            container.animate({scrollLeft: '+=600'}, 1000, 'linear', direction);
        }
        container.stop().animate({scrollLeft: '+=600'}, 1000, 'linear', direction);
    }
}).mouseleave( function() {
    var container = $(this).parent();
    container.stop();
});

CSS:

.carousel-frame {
    width: 100%;
    margin-bottom: 0.5em;
    padding-bottom: 1em;
    position: relative;
    overflow-x: scroll;
    white-space: nowrap;
}
.carousel-frame ul {
    margin: 0;
    padding: 0;
    height: 100%;
    list-style: none;
}
.carousel-frame li.carousel-item {
    cursor: pointer;
    display: inline-block;
    margin: 0 5px 0 0;
    padding: 0;
}

HTML:

<div class="carousel-frame">
  <ul>
    <li class="carousel-item">
      <img src="http://placehold.it/200x150" />
    </li>
    <li class="carousel-item">
      <img src="http://placehold.it/200x150" />
    </li>
    <li class="carousel-item">
      <img src="http://placehold.it/200x150" />
    </li>
    <li class="carousel-item">
      <img src="http://placehold.it/200x150" />
    </li>
    <li class="carousel-item">
      <img src="http://placehold.it/200x150" />
    </li>
  </ul>
</div>

FIDDLE:

https://jsfiddle.net/dk6f3snf/

2

There are 2 answers

2
Mikk3lRo On BEST ANSWER

There are two major changes required to get a smooth effect:

One: When trying to create smooth animations always go for window.requestAnimationFrame...

Two: In your example you are detecting mouse events on the ul, which means that the animation is interrupted every time the cursor passes a "gap" between the li elements.

Updated fiddle: https://jsfiddle.net/dk6f3snf/6/

var speed = 0;
var scroll = 0;
var container = $('.carousel-frame');
var container_w = container.width();
var max_scroll = container[0].scrollWidth - container.outerWidth();

container.on('mousemove', function(e) {
    var mouse_x = e.pageX - container.offset().left;
    var mouseperc = 100 * mouse_x / container_w;
    speed = mouseperc - 50;
}).on ( 'mouseleave', function() {
    speed = 0;
});

function updatescroll() {
  if (speed !== 0) {
      scroll += speed / 5;
        if (scroll < 0) scroll = 0;
        if (scroll > max_scroll) scroll = max_scroll;
      $('.carousel-frame').scrollLeft(scroll);
    }
    $("#speed").html('Speed: ' + speed);
    $("#scroll").html('Scroll: ' + scroll);
    window.requestAnimationFrame(updatescroll);
}
window.requestAnimationFrame(updatescroll);
.carousel-frame {
    width: 100%;
    margin-bottom: 0.5em;
    padding-bottom: 1em;
    position: relative;
    overflow-x: scroll;
    white-space: nowrap;
 }
 .carousel-frame ul {
    margin: 0;
    padding: 0;
    height: 100%;
    list-style: none;
}
.carousel-frame li.carousel-item {
    cursor: pointer;
    display: inline-block;
    margin: 0 5px 0 0;
    padding: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="carousel-frame">
      <ul>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
        <li class="carousel-item">
          <img src="http://placehold.it/200x150" />
        </li>
      </ul>
    </div>
    <div id="speed"></div>
    <div id="scroll"></div>

Note that I also made the speed depend on how far the cursor is from the middle, rather than only which half it is over and slowed it way down... mostly to demonstrate how smooth using the window.requestAnimationFrame method makes things.

Update

Actually to make the speed consistent on different devices and regardless of other stuff "stealing" resources, I guess you need to consider the time elapsed between frames as well. I've updated the fiddle demonstrating this: https://jsfiddle.net/dk6f3snf/7/

Update

I think your question about multiple carousels on a single page is an entirely different subject than your original question... but one way would be to just wrap it in a simple plugin - example: https://jsfiddle.net/dk6f3snf/8/

0
leemon On

I made some changes in my JS code and now the scroll animation is not so choppy when the mouse is moving inside the container. If anyone has a better solution, feel free to post it here.

JS:

var b = null;
$( '.carousel-frame ul' ).on( 'mousemove', function(e) {
    var container = $(this).parent();
    if ((e.pageX - container.offset().left) < container.width() / 2) {
        var direction = function() {
            container.animate( {scrollLeft: '-=600'}, 1000, 'linear', direction );
        }
        if ((b == false) || (b == null)) {
            b = true;
            container.stop( true ).animate( {scrollLeft: '-=600'}, 1000, 'linear', direction );
        }
    } else {
        var direction = function() {
            container.animate( {scrollLeft: '+=600'}, 1000, 'linear', direction );
        }
        if ((b == true) || (b == null)) {
            b = false;
            container.stop( true ).animate( {scrollLeft: '+=600'}, 1000, 'linear', direction );
        }
    }
}).on ( 'mouseleave', function() {
    var container = $(this).parent();
    container.stop( true );
    b = null;
});

FIDDLE:

https://jsfiddle.net/dk6f3snf/1/