title: iOS Disable Page Scrolling with overflow-scrolling: touch
body: <p>Let's say we want to make a webapp feel like a native app with "Add to Home Screen."  One of the first steps is to disable the default scrolling.  Easy, right?</p>
  
  <pre><code>// window or document
  window.addEventListener("touchmove", function(event) {
      // no more scrolling
      event.preventDefault();
  }, false);
  </code></pre>
  
  <p>That's all fine and dandy until you add <code>overflow-scrolling</code> to the mix.  To be precise, on iOS it would be <code>-webkit-overflow-scrolling: touch</code>.</p>
  
  <pre><code>/* #scrollable happens to be a ul */
  #scrollable {
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
  }
  </code></pre>
  
  <p>By adding event prevention, hardware-accelerated scrolling in a container does not function, clearly not the intended effect.</p>
  
  <p>The obvious solution looks something like this:</p>
  
  <pre><code>// you could do this for multiple elements, of course
  var scrollable = document.querySelector("#scrollable");
  scrollable.addEventListener("touchmove", function(event) {
      // no more bubbling :)
      event.stopPropagation();
  }, false);
  </code></pre>
  
  <p>This solution introduces a problem, however, if you try to scroll left or right in <code>#scrollable</code>, it reverts back to the default scroll listener.  Clearly, then, you should monitor the events to see if the <code>touchmove</code> event is tracking left or right, right?  Unfortunately, no, as it will also, under circumstances I don't entirely understand, revert to the default scroll listener when scrolling vertically in the container.</p>
  
  <p>Now what?  To make matters worse, we would ideally be able to handle <code>click</code> or click-like events on the individual <code>li</code>s (read: <code>touchstart</code>):</p>
  
  <pre><code>var items = scrollable.querySelectorAll("#scrollable li");
  for (var item = 0; item &lt; items.length; item++) {
      items[item].addEventListener("touchstart", function() {
          // handle the touch start
      }, false);
  }
  </code></pre>
  
  <p>To fix this problem, we could turn to simply using <code>click</code> events, but that defaults the goal of making the webapp "feel" native due to the delay between tapping and response.  To solve this, we'll add an event listener for <code>touchstart</code> and <code>touchend</code>:</p>
  
  <pre><code>var items = scrollable.querySelectorAll("#scrollable li");
  var activeItem = null, startTouch = null;
  for (var item = 0; item &lt; items.length; item++) {
      items[item].addEventListener("touchstart", function(event) {
          startTouch = event.touches[0];
          activeItem = this;
      }, false);
      items[item].addEventListener("touchend", function(event) {
          var touch = event.changedTouches[0];
          var deltaX = touch.pageX - startTouch.pageX
          var deltaY = touch.pageY - startTouch.pageY;
          // require the touchstart to be within 10 pixels of the touchend
          if (deltaX * deltaX + deltaY * deltaY &lt;= 100)
              // handle "click" event
      }, false);
  }
  </code></pre>
  
  <p>That's all fine and good, but we still haven't solved the problem with default page scrolling taking control of some <code>touchmove</code> events.  Any ideas?</p>