最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

jquery - Controlling vertical scrolling and horizontal swiping on a list in an iOS web app with JavaScript - Stack Overflow

programmeradmin0浏览0评论

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...

  1. If the user touches the list and moves up or down, the list scrolls vertically.
  2. If the user flicks up or down, the list scrolls vertically with natural momentum
  3. If the user touches the list and moves ONLY left - the particular item slides to the left revealing a delete button.
  4. IMPORTANTLY - the list should scroll OR the item should slide, BUT NEVER BOTH.

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)...

  1. Create a touchstart event listener on the list container. In the callback, store the x- and y-coordinates of the user's starting touch position.
  2. Create a 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)
  3. If delta_x AND delta_y are both less than 10 pixels - don't do anything (don't scroll the list or slide the item) - since we haven't yet figured out whether the user plans to move up/down or left/right.
  4. If EITHER delta_x OR delta_y are more than 10 pixels - we can assume the user has moved far enough to express her intention. If delta_y > delta_x, assume she's moving up/down, and allow the list to scroll, but don't slide the item. If delta_x > delta_y, assume she's moving left/right, so we should allow the item to slide, but not allow the list to scroll.

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 acplish 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!

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...

  1. If the user touches the list and moves up or down, the list scrolls vertically.
  2. If the user flicks up or down, the list scrolls vertically with natural momentum
  3. If the user touches the list and moves ONLY left - the particular item slides to the left revealing a delete button.
  4. IMPORTANTLY - the list should scroll OR the item should slide, BUT NEVER BOTH.

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)...

  1. Create a touchstart event listener on the list container. In the callback, store the x- and y-coordinates of the user's starting touch position.
  2. Create a 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)
  3. If delta_x AND delta_y are both less than 10 pixels - don't do anything (don't scroll the list or slide the item) - since we haven't yet figured out whether the user plans to move up/down or left/right.
  4. If EITHER delta_x OR delta_y are more than 10 pixels - we can assume the user has moved far enough to express her intention. If delta_y > delta_x, assume she's moving up/down, and allow the list to scroll, but don't slide the item. If delta_x > delta_y, assume she's moving left/right, so we should allow the item to slide, but not allow the list to scroll.

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 acplish 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!

Share Improve this question edited Nov 21, 2013 at 19:46 mattstuehler asked Nov 21, 2013 at 19:23 mattstuehlermattstuehler 9,33218 gold badges81 silver badges115 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 3 +25

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 :

  • direction: direction in which you want to detect the swipe gesture (more info)
  • threshold: Minimal distance required before recognizing
  • 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/hammerjs/2.0.4/hammer.min.js"></script>
<div id="container">
  <ul id="list">
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</span>
    </li>
    <li class="item"><span class="delete">&#215;</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

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论