I'm creating a web application specifically targeted for phones (primarily iPhone, but Android & WP are on the horizon...).
One of the screens contains a scrolling list of items. I would like the list to behave similarly to the built-in iOS Mail app.
In other words...
So - it's important to figure out what the user's intention is, which means I probably need to prevent ANY response until I figure out whether the user is moving her finger vertically or horizontally.
By simply setting these CSS styles on the list container...
overflow-y: auto;
-webkit-overflow-scrolling: touch;
... I get #1 & #2 above. So, I need to figure out how implement #3.
My first thought was to implement something like this (pseudocode)...
touchstart event listener on the list container. In the callback, store the x- and y-coordinates of the user's starting touch position.touchmove event listener on the list container. In the callback, figure out how far the user's finger has moved (e.g., delta_x and delta_y)I expected that I would use event.preventDefault() in either the touchstart or touchmove to control when scrolling should begin. E.g.,
div.addEventListener("touchstart", function(e) {
    touchStart = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
    }
}, false);
div.addEventListener("touchmove", function(e) {
    touchNow = {
        x: e.touches[0].pageX,
        y: e.touches[0].pageY
    }
    var
        dx = touchStart.x - touchNow.x,
        dy = touchStart.y - touchNow.y;
    if ((Math.abs(dx) < 10) && (Math.abs(dy) < 10)) {
        // prevent scrolling
        e.preventDefault();
    } else if (Math.abs(dx) > Math.abs(dy) < 10) {
        // moving right/left - slide item
    } else {
        // moving up/down - allow scrolling
    }
}, false);
However - this doesn't work. Regardless of how far you move, the list NEVER scrolls.
Obviously - I'm misunderstanding what triggers the scrolling, and what event.preventDefault() is supposed to do in this context.
So - is there a way to accomplish what I'm after?
I'm hoping for a pure JavaScript solution (so I understand it better), but a jQuery approach would be fine to. I'm definitely hoping to avoid a jQuery plugin/library/framework if at all possible...
Thanks in advance!
Horizontal scrolling can be achieved by clicking and dragging a horizontal scroll bar, swiping sideways on a desktop trackpad or trackpad mouse, pressing left and right arrow keys, or swiping sideways with one's finger on a touchscreen.
On our container, we want to turn off vertical scrolling (overflow-y) and enable horizontal scrolling (overflow-x). Then with each card, we want to set it to display with inline-block so they all display in a row. The line of CSS you probably are unfamiliar with is white-space: nowrap.
To hide the horizontal scrollbar and prevent horizontal scrolling, use overflow-x: hidden: HTML.
A horizontal scroll bar enables the user to scroll the content of a window to the left or right. A vertical scroll bar enables the user to scroll the content up or down.
I don't suggest reinventing the wheel. There are a number of libraries out there that supports gesture detection. Out of which, I suggest using Hammer.js to detect the touch events.
It doesn't have any dependencies, and it's small, only 3.96 kB minified + gzipped!
And it is all about handling touch events, nothing else.
In your case, Hammer has inbuilt swipe detection.
You can customize the default swipe gesture by specifying :
velocity : Minimal velocity required before recognizing (unit is in px per ms)
and more.
Following is a simple example (Stack Snippet seems to have some issue emulating touch events, fiddle works fine):
var myElement = document.getElementById("container");
var hammertime = new Hammer(myElement, {});
hammertime.get('swipe').set({
  direction: 2 // 2 stands for left
})
hammertime.on('swipe', function(event) {
  event.target.querySelector(".delete").classList.add("show");
});
* {
  margin: 0;
  padding: 0;
}
#container {
  width: 250px;
  height: 300px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: dodgerblue;
}
#list {
  height: 100%;
  list-style: none;
}
#list li {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  height: 50px;
  border: 2px solid #fff;
}
#list li span.delete {
  display: inline-block;
  position: absolute;
  left: 250px;
  width: 50px;
  height: 40px;
  line-height: 40px;
  margin: 3px;
  text-align: center;
  background: #fff;
  transition: left 0.5s ease-in;
}
#list li span.delete.show {
  left: 170px;
}
<script src="http://cdn.jsdelivr.net/hammerjs/2.0.4/hammer.min.js"></script>
<div id="container">
  <ul id="list">
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
    <li class="item"><span class="delete">×</span>
    </li>
  </ul>
</div>
If you are more interested in learning how the touch event works rather than getting the job done , then I suggest looking under the hood of Hammer.
There is a little Hammer.js jQuery plugin as well, for those who can't part with jQuery.
I had a similar problem, when I was implementing a slideshow. That's the solution that worked for me, hope it will help you:
function touchStart(event){
   if (!event) event = window.event;
   if(!touched){ 
      touched = true;
      var touchObj = event.changedTouches[0];
      touchXPos  = parseInt(touchObj.clientX);
      touchYPos  = parseInt(touchObj.clientY);
   }    
   return false;
}
When the user touches the screen, the cooridnate positions are stored in touchXPos, touchYPos and the boolean variable "touched" is set to true.
function touchMove(event){
   if (!event) event = window.event;
   if(touched){
       var touchObj = event.changedTouches[0];
       var distanceX = touchXPos - parseInt(touchObj.clientX);
       var distanceY = touchYPos - parseInt(touchObj.clientY);
   if(!touchDirection) {
       if(Math.abs(distanceX) > Math.abs(distanceY)){
          if (distanceX > 0)  touchDirection = "right";
          else if (distanceX < 0) touchDirection = "left";
       }
       else{
          if (distanceY > 0) { touchDirection = "up"; }
          else if (distanceY < 0) {touchDirection = "down"; }
       }
   } 
   if (((touchDirection == "right") )|| ((touchDirection == "left"))){
       //update touchDirection
       if(Math.abs(distanceX) > Math.abs(distanceY)){
          if (distanceX > 0)  touchDirection = "right";
          else if (distanceX < 0) touchDirection = "left";
       }
       touchXPos = parseInt(touchObj.clientX);
       slideshow_mask.scrollLeft += distanceX;
   }
   else if (((touchDirection == "up") ) || ((touchDirection == "down") )){
       return false;
   }
   event.preventDefault();
   return false;
}
Similar to your approach, I determine the delta-x and delta-y whenever the touch moves, which I named distanceX and distanceY. When this is the first "move" I determine the touchdirection, that can be right, left, up or down. The next movement that gets registered, can only stay within this initial drection, i.e. up/down OR left/right. So, if the initial direction was for example "right", the user can only slide horizontally (left or right) but never up/down.
Since my code was designed to move a slideshow horizontally, I was just interested in the horizontal movement (-> in the code snippet you can see, that I updated the direction (left, right), stored the new xPosition and moved the slideshow in this direction), but it shouldn't be too hard to adapt it to vertical scrolling.
I had the same task, and wrote a pure Javascript lib swiped.js for a horizontal swiping on a list
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With