Conflict between jQuery's .animate(scrollLeft:) and CSS's scroll-snap-type. Fix doesn't work on iOS?

Viewed 422

Please run the code snippet...

I'm building a horizontal scroll menu. I've added left and right buttons (arrow icons) to allow user to move through the menu. So the user can move either by swiping/scrolling or by tapping the arrow icons.

So far so good. Next I added CSS's scroll-snap-type, which works nicely, improving UX when user is moving the scollbar by swiping. But than scroll-snap conflicts with jQuery's .animate( { scrollLeft: "+=200"}, 500), which is triggered when user clicks on either arrow. This makes the control buttons broken.

To fix this, I tried to remove the scroll-snap-type css class while the scrollLeft jQuery animation is running, and add it back once it's done.

This seems to work pretty good in all devices when run inside dev-tools simulation, including iPhone devices. Live testing with Android 10 browsers (Chrome/Firefox) works good too, but doesn't work on iOS (tested on iOS 14.2 with latest Safari, Firefox and Chrome).

On iOS, when clicking on either Right/Left arrow btn, the animation runs, but than the menu scroll position suddenly snaps back to the first item, making the controls broken.

jQuery(document).ready(function($) {
  /* Controls for horizontal scrolling */
  var leftPaddle = $( ".left-paddle" );
  var rightPaddle = $( ".right-paddle" );     
  // scroll to right
  $(rightPaddle).on('click', function(event) {
    event.preventDefault();
    $('#snappylist').removeClass('scroll-snap-type');
    $('#snappylist').animate({
        scrollLeft: "+=250"
      }, 500)
      .promise()
      .done(() => {
        $('#snappylist')
          .addClass("scroll-snap-type")
      });
  });
  // scroll to left
  $(leftPaddle).on('click', function(event) {
    event.preventDefault();
    $('#snappylist').removeClass('scroll-snap-type');
    $('#snappylist').animate({
        scrollLeft: "-=250"
      }, 500)
      .promise()
      .done(() => {
        $('#snappylist')
          .addClass("scroll-snap-type")
      });
  });
});
.list {
  margin-top: 50px;
  background-color: gray;
  display: flex;
  overflow-x: auto;
  gap: 0.25rem;
  -webkit-overflow-scrolling: touch;
  max-width: 300px;
}

.scroll-snap-type {
  scroll-snap-type: x mandatory;
}

.list-item {
  scroll-snap-align: center;
  display: inline-flex;
  min-width: 80px;
  height: 50px;
  justify-content: center;
  align-items: center;
  color: white;
}

.paddle {
  position: absolute;
  top: 50px;
  bottom: 0;
  width: 20px;
  border: none;
  background-color: red;
  padding: 0;
  max-height: 50px;
}

.left-paddle {
  left: 0;
  color: yellow;
  background: red;
}

.right-paddle {
  left: 300px;
  color: yellow;
  background: red;
}
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="module" src="https://unpkg.com/ionicons@5.2.3/dist/ionicons/ionicons.esm.js"></script>
<script nomodule="" src="https://unpkg.com/ionicons@5.2.3/dist/ionicons/ionicons.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"></script>

<ul id="snappylist" class="list scroll-snap-type">
  <li class="list-item">
    <ion-icon size="large" name="construct-outline"></ion-icon>
  </li>
  <li class="list-item">
    <ion-icon size="large" name="flower-outline"></ion-icon>
  </li>
  <li class="list-item">
    <ion-icon size="large" name="game-controller-outline"></ion-icon>
  </li>
  <li class="list-item">
    <ion-icon size="large" name="git-compare-outline"></ion-icon>
  </li>
  <li class="list-item">
    <ion-icon size="large" name="eye-outline"></ion-icon>
  </li>
  <li class="list-item">
    <ion-icon size="large" name="earth-outline"></ion-icon>
  </li>
  <li class="list-item">
    <ion-icon size="large" name="extension-puzzle-outline"></ion-icon>
  </li>
  <li class="list-item">
    <ion-icon size="large" name="bus-outline"></ion-icon>
  </li>
</ul>
<div class="paddles">
  <button class="left-paddle paddle">
    <ion-icon name="arrow-back-outline"></ion-icon>
  </button>
  <button class="right-paddle paddle">
    <ion-icon name="arrow-forward-outline"></ion-icon>
  </button>
</div>

However, if I leave the animation as-is, without removing it onclick:

    // scroll to right
    $(rightPaddle).on('click', function(event) { 
        event.preventDefault();
        $('#parent-div').animate( { scrollLeft: "+=150"}, 500)
    });

    // scroll to left
    $(leftPaddle).on('click', function(event) {
        event.preventDefault();
        $('#parent-div').animate( { scrollLeft: "-=150"}, 500)
    });

This way everything seeme to work fine on iOS mobile, but than I'm back to the broken animation in other os/browsers.

0 Answers
Related