I want to detect scroll/touch direction on phones. For desktop, I use .on('DOMMouseScroll') and .('mousewheel') but this does not work on phones. Body of page is overflow: hidden; Anybody know how to detect this?
It seems I fell into the same case of yours.
Since I needed a lot of research to put all things together, I'm posting it all here:
if (Modernizr.touch) {
// Determining swipe direction
// http://stackoverflow.com/a/22257774/1064325
var touchStartY;
document.addEventListener('touchstart', function (e){
touchStartY = e.touches[0].clientY;
}, false);
// Preventing iOS end of page bounce effect
// http://stackoverflow.com/a/7771215/1064325
document.addEventListener('touchmove', function (e){
e.preventDefault();
}, false);
document.addEventListener('touchend', function (e){
var touchEndY = e.changedTouches[0].clientY;
if (touchStartY > touchEndY + 5) {
// NEXT
} else if (touchStartY < touchEndY - 5) {
// PREV
}
}, false);
} else {
// Handling wheeling properly
// http://stackoverflow.com/a/3515490/1064325
var deltaWheel = 0;
var wheelTimeout = 0;
document.addEventListener('wheel', function(event) {
deltaWheel += event.deltaY;
clearTimeout(wheelTimeout);
wheelTimeout = setTimeout(function() {
if (deltaWheel < -5) {
// PREV
}
if (deltaWheel > +5) {
// NEXT
}
deltaWheel = 0;
}, 50);
});
}
Related
Can you recommend a JS library that actually provides edge swipe functionality when working with bare-bones HTML & CSS?
I've searched all over and haven't found a source of truth for that problem.
I've seen lots and lots of libraries enabling swipe gestures but not edge swipe.
My last attempt was using Hammer.js which I've tried implementing as:
var swipe = new Hammer(document);
// detect swipe and call to a function
swipe.on('swiperight swipeleft', function (e) {
e.preventDefault();
var endPoint = e.pointers[0].pageX;
var distance = e.distance;
var origin = endPoint - distance;
//swipe right to open nav
if (origin <= 15 && e.type == 'swiperight') {
// open main menu
$('#navigation-menu').animate({
left: '0'
});
} else {
// close/hide menu(s)
$('#navigation-menu').animate({
left: '-100%'
});
}
});
Further, if not using any library, how can I implement a mobile edge swipe to show and hide content, (in my case it'd be a navigation menu) with vanilla JS?
At this point I'm open to either solution/direction.
Here is a solution, you can set thresholdStart, End, Milliseconds. You may want to tidy up the code, and port it for touch events (I used mouse events for testing in my browser more easily).
Use:
swipeEdgeFromLeft function and swipeEdgeFromRight function.
var div = document.body;
var mouse = {
isDown: false,
inLeft: false,
inRight: false,
downTimestamp: null
};
var width, thresholdStart, thresholdEnd, thresholdMilliseconds;
function resize(){
width = window.innerWidth;
thresholdStart = 0.1*width;//within 10% of screen width
thresholdEnd = 0.13*width;//beyond 13% of screen width
thresholdMilliseconds = 500;//must be done in 500 milliseconds
}
document.addEventListener("resize", resize, false);
resize();//initialize
div.addEventListener('mousedown'/*'touchstart'*/, function(e){
var x = e./*touches[0].*/pageX;
mouse.isDown = true;
mouse.downTimestamp = performance.now();
if(x < thresholdStart){
mouse.inLeft = true;
} else if(x > width-thresholdStart){
mouse.inRight = true;
}
});
div.addEventListener('mousemove'/*'touchmove'*/, function(e){
var x = e./*touches[0].*/pageX;
if(mouse.inLeft && x > thresholdEnd){
mouse.inLeft = false;
if(performance.now() - mouse.downTimestamp < thresholdMilliseconds){
swipeEdgeFromLeft();
}
} else if(mouse.inRight && x < width-thresholdEnd){
mouse.inRight = false;
if(performance.now() - mouse.downTimestamp < thresholdMilliseconds){
swipeEdgeFromRight();
}
}
});
div.addEventListener('mouseup'/*'touchend'*/, function(e){
//var x = e./*changedTouches[0].*/pageX;
mouse.isDown = false;
mouse.inLeft = false;
mouse.inRight = false;
mouse.downTimestamp = null;
});
function swipeEdgeFromLeft(){
console.log("edge swipe from left");
}
function swipeEdgeFromRight(){
console.log("edge swipe from right");
}
body {
max-width: 100vw;
height: 100vh;
}
.bar {
height: 100vh;
background-color: rgba(0,0,0,0.4);
position: fixed;
pointer-events: none;
}
#left-inner-threshold {
width: calc(0.1 * 100vw);
left: 0;
}
#right-inner-threshold {
width: calc(0.1 * 100vw);
right: 0;
}
#left-outer-threshold {
width: calc(0.13 * 100vw);
left: 0;
}
#right-outer-threshold {
width: calc(0.13 * 100vw);
right: 0;
}
<div id="left-inner-threshold" class="bar"></div>
<div id="left-outer-threshold" class="bar"></div>
<div id="right-inner-threshold" class="bar"></div>
<div id="right-outer-threshold" class="bar"></div>
Here's a solution to your existing code using Hammer.js v2.0.8
The explanation for how to achieve the edge swipe can be found here answered by #jovinbm.
$(document).ready(function () {
const swipe = new Hammer(document);
function getStartPosition(e) {
const delta_x = e.deltaX;
const delta_y = e.deltaY;
const final_x = e.srcEvent.pageX || e.srcEvent.screenX || 0;
const final_y = e.srcEvent.pageY || e.srcEvent.screenY || 0;
return {
x: final_x - delta_x,
y: final_y - delta_y
}
};
swipe.on('swiperight swipeleft', function (e) {
e.preventDefault();
const { x } = getStartPosition(e);
console.log(x);
//swipe right to open nav /* note the condition here */
if (e.type == 'swiperight' && x >= 0 && x <= 50) {
// open menu
$('#navigation').animate({
left: '0'
});
//swiping left should slide out nav and/or sub-nav
} else {
// close/hide menu
$('#navigation, #task-menu').animate({
left: '-100%'
});
}
});
});
Here's a pen showing it in action:
For swipes, only the final pointerup event is included as the srcEvent in the event object passed to your handler (see http://hammerjs.github.io/api/). The initial pointerdown event that carries the details of the initial position of where the swipe event started is not provided in the hammer event object. Fortunately, you can use the srcEvent in the event object to get the starting position of the event initial pointerdown event.
const getStartPosition = (e) => {
const delta_x = e.deltaX;
const delta_y = e.deltaY;
const final_x = e.srcEvent.pageX || e.srcEvent.screenX || 0;
const final_y = e.srcEvent.pageY || e.srcEvent.screenY || 0;
return {
x: final_x - delta_x,
y: final_y - delta_y
};
};
const handleSwipe = (e) => {
const {x} = getStartPosition(e);
if (x >= 0 && x <= 50) {
// handle swipe from left edge e.t.c
}
else {
// handle other case
}
};
The srcEvent is just a normal javascript event that inherits properties from UIEvent hence the pageX/pageY api above. This will probably not work in other browsers since some of them are not standardized
So I'm using mousewheel.js to handle mousewheel scrolling in any part of the document so I can scroll a custom scroller made by Nicescroll.
You can check a fiddle of it working here
Here's part of the code that handles the scrolling:
function activate_mousewheel()
{
$(document).bind('mousewheel', function(event, delta, deltaX, deltaY)
{
if(delta < 0)
{
console.log(1);
$('#postscroller').scrollTop($('#postscroller').scrollTop() + 60);
}
else
{
console.log(2);
$('#postscroller').scrollTop($('#postscroller').scrollTop() - 60);
}
});
}
Now my problem is that when this is used in a computer with a touchpad with horizontal scrolling enabled the movement is all jittery rendering it unusable. So this problem will affect people using any kind of laptop with horizontal scrolling like a chromebook or a macbook.
I've tried doing various fixes, playing with the deltas but to no avail.
I was hoping someone here could find a solution.
Thanks.
I had to find a solution for the problem myself, and after hours of trying and beeing creative here is what i came up with. Of course you have to modify it to integrate it to work smoothly with nicescroll etc. as this is plain JS:
Well I needed to get a solution. So I found a acceptable solution for this problem:
var scrolling = false;
var oldTime = 0;
var newTime = 0;
var isTouchPad;
var eventCount = 0;
var eventCountStart;
var mouseHandle = function (evt) {
var isTouchPadDefined = isTouchPad || typeof isTouchPad !== "undefined";
console.log(isTouchPadDefined);
if (!isTouchPadDefined) {
if (eventCount === 0) {
eventCountStart = new Date().getTime();
}
eventCount++;
if (new Date().getTime() - eventCountStart > 50) {
if (eventCount > 5) {
isTouchPad = true;
} else {
isTouchPad = false;
}
isTouchPadDefined = true;
}
}
if (isTouchPadDefined) {
// here you can do what you want
// i just wanted the direction, for swiping, so i have to prevent
// the multiple event calls to trigger multiple unwanted actions (trackpad)
if (!evt) evt = event;
var direction = (evt.detail<0 || evt.wheelDelta>0) ? 1 : -1;
if (isTouchPad) {
newTime = new Date().getTime();
if (!scrolling && newTime-oldTime > 550 ) {
scrolling = true;
if (direction < 0) {
// swipe down
} else {
// swipe up
}
setTimeout(function() {oldTime = new Date().getTime();scrolling = false}, 500);
}
} else {
if (direction < 0) {
// swipe down
} else {
// swipe up
}
}
}
}
And registering the events:
document.addEventListener("mousewheel", mouseHandle, false);
document.addEventListener("DOMMouseScroll", mouseHandle, false);
Here is how it works:
When the user first scrolls, it will detect and check that in 50ms not more than 5 events got triggered, which is pretty unusual for a normal mouse, but not for a trackpad.
Then there is the else part, which is not for importance for the detection, but rather a trick to call a function once like when a user swipes. Please come at me if I wasn't clear enough, it was very tricky to get this working, and is of course a less than ideal workaround.
Edit: I optimized the code now as much as I can. It detects the mouseroll on the second time and swipe on trackpad instantly. Removed also a lot of repeating and unnecessary code.
The mousewheel event fires a lot of times. So to regulate the event, so it doesn't fire as much, use a throttle function.
https://lodash.com/docs#throttle
Because the event will not fire as often, the performance should be better.
function onMouseWheel(event, delta, deltaX, deltaY)
{
if(delta < 0)
{
console.log(1);
$('#postscroller').scrollTop($('#postscroller').scrollTop() + 60);
}
else
{
console.log(2);
$('#postscroller').scrollTop($('#postscroller').scrollTop() - 60);
}
});
function activate_mousewheel()
{
$(document).bind('mousewheel', _.throttle(onMouseWheel, 100);
}
I am building simple slider with dragging ability. For now, I use pageX to determine the position, like so:
var dragdir = 0,
drag = null;
$('article.small-section').on('mousedown', function(ev) {
drag = 1;
dragdir = ev.pageX;
if(drag) {
$(this).css('cursor','move');
}
}).on('mouseup', function(e){
drag = null
if(!drag) {
$(this).css('cursor','initial');
$(this).css('left','0')
}
});
$('article.small-section').on('mousemove', function(e) {
if(drag) {
if(dragdir > e.pageX) {
//console.log(dragdir+'|'+e.pageX)
if(dragdir > e.pageX+100) {
$(this).parent('section').cycle('prev');
}
$(this).css('right','');
$(this).css('left','-'+(parseInt(e.pageX/10))+'px');
} else {
if(dragdir < e.pageX-100) {
$(this).parent('section').cycle('next');
}
$(this).css('left','');
$(this).css('right','-'+(parseInt(e.pageX/10))+'px');
}
}
});
It works great dragging to the right. To the left - it does, then it backs (or doesn't go at all). I noticed that pageX is going down - that's the problem. How can I fix it?
Working fiddle: http://jsfiddle.net/2jEJH/1/
When the mousewheel is scrolled on the body of a page this event can be captured. I'd like this event to trigger a target element to scroll.
#target is a scrollable element that is never the height of the page. I'd like to capture the mousescroll event anywhere on the page so even if the cursor is not over the element the element still scrolls.
$( 'body' ).on( 'DOMMouseScroll mousewheel', function () {
// Scroll #target instead of body
});
Thanks to this post for showing me how to capture scroll wheel events: Capturing Scroll Wheel Events
You can have a look at this.
http://jsfiddle.net/ZtGva/7/
JS
$(function () {
var myCounter = 0,
myOtherCounter = 0;
var scroll = 0;
$("#target").scroll(function () {
myCounter = myCounter + 1;
$("#log").html("<div>Handler for .scroll() called " + myCounter + " times.</div>");
});
//Firefox
// $(document).bind(...) this works as well
$('#body').bind('DOMMouseScroll', function (e) {
if (e.originalEvent.detail > 0) {
scrollDown();
} else {
scrollUp();
}
//prevent page fom scrolling
return false;
});
//IE, Opera, Safari
$('#body').bind('mousewheel', function (e) {
if (e.originalEvent.wheelDelta < 0) {
scrollDown();
} else {
scrollUp();
}
//prevent page fom scrolling
return false;
});
function scrollDown() {
//scroll down
console.log('Down ' + scroll);
if (scroll < $('#target').find('div').height() - $('#target').height() + 20) {
scroll = $('#target').scrollTop() + 5;
$('#target').scrollTop(scroll);
}
};
function scrollUp() {
//scroll up
console.log('Up ' + scroll);
if (scroll > 0) {
scroll = $('#target').scrollTop() - 5;
$('#target').scrollTop(scroll);
}
};
});
Note I added a div for height calculation
<div id="target"><div>.... </div></div>
You can clean up this code a bit by caching some jquery variables but the idea is there
I was looking for the solution to find out swipe left and right on mobile browsers and created the lib (below). My questions is: is there a simple way of checking for swipe on android and iOS? Also: can I use mouse events instead?
(function (lib, img, cjs) {
lib.touches = new Array();
(lib.setTouchListeners = function(gFilename) {
document.addEventListener("touchstart", function(event) {
// index to the last touch (always the last in the list)
var index = event.touches.length - 1;
// identifier - unique id of the touch (always the least free number on android but quite large number on iOS)
var iden = event.touches[index].identifier;
// save this touch for later comparison (checking if it moved etc.)
var t = {pageX:event.touches[index].pageX, pageY:event.touches[index].pageY, identifier:event.touches[index].identifier};
lib.touches.push(t);
},false);
document.addEventListener("touchmove", function(event) {
// prevent browser from using touch (move page up and down, resize by pinch)
event.preventDefault();
},false);
document.addEventListener("touchend", function(event) {
var len = event.changedTouches.length;
var iden = 0;
// check all changes although it's always one (but just in case something changes in future)
for(var i = 0; i < len; i++) {
iden = event.changedTouches[i].identifier;
// find that identifier in the saved list
for(var j = 0; j < lib.touches.length; j++) {
if (iden == lib.touches[j].identifier) {
var horizontalMove = event.changedTouches[i].pageX - lib.touches[j].pageX;
var verticalMove = event.changedTouches[i].pageY - lib.touches[j].pageY;
if (Math.abs(horizontalMove) > 50) {
if (horizontalMove > 0) {
console.log('right');
} else {
console.log('left');
}
}
if (Math.abs(verticalMove) > 50) {
if (verticalMove > 0) {
console.log('down');
} else {
console.log('up');
}
}
lib.touches.splice(j, 1);
break;
}
}
}
// event.preventDefault();
},false);
});
})(lib = lib||{}, images = images||{}, createjs = createjs||{});
var lib, images, createjs;
You have something called a GestureOverlay or a GestureDetector which you can use to check swipes.
Here are the sites for the GestureOverlay and GestureDetector:
http://developer.android.com/reference/android/gesture/GestureOverlayView.html
http://developer.android.com/reference/android/view/GestureDetector.html
See my other answer:
https://stackoverflow.com/a/18737199/2767703