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...
- If the user touches the list and moves up or down, the list scrolls vertically.
- If the user flicks up or down, the list scrolls vertically with natural momentum
- If the user touches the list and moves ONLY left - the particular item slides to the left revealing a delete button.
- 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)...
- 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. - 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) - 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.
- 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...
- If the user touches the list and moves up or down, the list scrolls vertically.
- If the user flicks up or down, the list scrolls vertically with natural momentum
- If the user touches the list and moves ONLY left - the particular item slides to the left revealing a delete button.
- 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)...
- 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. - 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) - 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.
- 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 badges3 Answers
Reset to default 3 +25I 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">×</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