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