I'm attempting to implement some swiping functionality into my app, but I'm currently struggling to get the correct behaviour I want.
I have a function which is called onTouchStart which saves the current touch objects' clientX and clientY position to the state, like so:
touchStart(e) {
const touchObj = e.touches[0];
this.setState({
startX: touchObj.clientX,
startY: touchObj.clientY,
})
}
After the users begins to move their finger across the screen, I call another function onTouchMove, which saves the current clientX and clientY positions:
touchMove(e) {
const touchObj = e.touches[0];
this.setState({
currentX: touchObj.clientX,
currentY: touchObj.clientY,
})
}
To then figure out whether a swipe was an up or down swipe, I call another function which takes the start positions and takes away the current positions. I also compare it against a threshold of 150 to make sure that it was an actual swipe, and not a long swipe (like a scroll). Here's what that looks like:
touchEnd(e) {
this.setState({
touchStarted: false,
})
if (Math.abs(this.state.startX - this.state.currentX) < this.state.threshold) {
this.setState({
direction: this.state.startY > this.state.currentY ? "top" : "bottom",
})
console.log(this.state.direction);
}
}
The problem
This is detecting up and down swipes fine, but on the very first swipe a user does, the console.log returns the original state of none (as opposed to up or down).
How do I ensure that the initial swipe is always set to the correct direction?
The problem continues further, whereby the user will have to swipe twice in the opposite direction for the state to be saved to the actual direction which is being swiped, like so:
up // none
up // up
down // up
down // down
Apologies if I've not articulated my problem too well. Here's a link to a CodePen where you can see the problem in further detail (you'll need a phone or an emulator to see the touch events).
Here's also a link to a post which I've been following to get this functionality working.
Any pointers would be highly appreciated. Thanks!
Here is the link to the fixed pen.
touchEnd(e)
{
var s = {
touchStarted: false
};
if (Math.abs(this.state.startX - this.state.currentX) < this.state.threshold)
{
s.direction = this.state.startY > this.state.currentY ? "top" : "bottom";
}
this.setState(s, () => console.log(this.state.direction))
}
The only "problem" was that you assumed that setState is synchronous and therefore checked state value immediately after its call, whereas it is not. If you want to check value of the state after setState has finished - use its second argument to pass callback function.
Related
I have a simple unordered list with li elements. I would like to do something similar to the rc-swipeout npm library where if you swipe on a list element you are presented with options.
Unfortunately I am running Meteor/Blaze application and unable to use React. I have found a couple of libraries that are simple jQuery such as swipeTo which has a demo here (works on mobile).
The swipeTo library actually provides some swipe functionality, it gives options when you swipe over.
However, I would like to add functionality where when I swipe all the way, the list item is deleted. Right now when you swipe all the way, the list item just rebounds back to its regular position as you can see in that demo.
What is the best method to add a conditional such as this?
Try checking event.clientX; save the value in swipeStart and compare it to the value in swipeEnd.
(I couldn't figure out how the swipeTo.js script made event coordinates available, so I edited it, lines 40 and 93):
if(typeof swipeStart == 'function') {
swipeStart.call(this, start); //add 'start' as parameter
}
if(typeof swipeEnd== 'function') {
swipeEnd.call(this, absMoveStatus, e.changedTouches[0].clientX); //add amount moved and clientX as parameter
}
In your main script:
$(function() {
$('.item-swipe').swipeTo({
minSwipe: 100,
angle: 10,
binder: true,
swipeStart: function(start) { //now use start value
console.log('start', start);
},
swipeMove: function(e) {
console.log('move');
},
swipeEnd: function(movedAmount, end) {
console.log('end', movedAmount, end);
// if movedAmount value > 200, and end value < 20 (close to left side),
// then delete the item
},
});
deleteItem();
})
I am currently developing a web application for iPad. I have a html canvas, where I am wanting to track the coordinates of the touchmove event.
I can track the touchstart event, which outputs the coordinate I have pressed, but when I try and output my current coordinates as I am moving across the canvas, it is doing nothing, I have the following code which is using angular2, where this.currentArrowPoint is the label I am outputting the values to on the screen:
e.preventDefault();
this.currentArrowPoint = ["a", "b"];
this.currentYPosition = ["e.changedTouches[0].pageY];
this.currentArrowPoint = [this.currentYPosition];
It is definitely entering the mousemove, as I am getting the a and b back, but then when I output anything relating to the event e, I get absolutely no output, not even if I try and output just e. This works on everything but apple devices, so wondering if I am missing something special about apple browsers or devices?
Thanks
As you can read here, only Chrome and Firefox support changedTouches in a TouchEvent, which in turn means that Safari does not support this.
I also guess you did not mean to put e.changedTouches[0].pageY in between quotes?
You should try and track touches based on their index on touchstart (such a waste why they didn't call this touchdown....) and figure out on touchmove if any of these touches have changed. You are using a canvas, so I can imagine you are trying to draw something. You could stop drawing once you count more than 1 touch. This way you are always sure you are tracking the right finger (or toe.. I'm not judging)
I find walk around solution if you want do swipe or something like that on iOS 13 with 3 event listener.
const someFucntion = () =>{
let touchstartX = 0;
let touchendX = 0;
const gesturedZone = this.mainWrapperRef.current; // ref, querySelector or something else
gesturedZone.addEventListener('touchstart', (event) => {
if (event.touches.length === 1) {
touchstartX = event.touches[0].screenX;
}
}, false);
gesturedZone.addEventListener('touchmove', (event) => {
if (event.touches.length === 1) {
touchendX = event.touches[0].screenX;
}
}, false);
gesturedZone.addEventListener('touchend', () => {
this.handleGesture(touchstartX, touchendX);
}, false);
}
const handleGesture = (touchstartX, touchendX) => {
// do some staff
};
I'm facing a strange problem that I think leaves HammerJS internal event loop with a stuck event that ruins subsequent detections.
This only happens on Internet Explorer Edge on a Touch Device with PointerEvents.
Basically, when using HammerJS for a PAN event (panstart -> panmove -> panend), and you cross the current frame boundary (for example, into an IFRAME, or just outside the browser window) AND you release your finger there, then HammerJS never receives the CANCEL event and the session kind of stays stuck.
From then on, all gestures are reported incorrectly, with one more finger ('pointer') than you're using: For example, it will report a PINCH or ROTATE (2 pointers) just tapping (1 pointer) and so on.
I haven't found a way to reset the Hammer Manager once it enters this ghost state. This breaks my app.
I've prepared a Fiddle with a full working example. Please execute it under a Windows/Touch device !
https://jsfiddle.net/28cxrupv/5/
I'd like to know, either how to detect the out-of-bounds event, or just how could I manually reset the Hammer Manager instance if I am able to detect myself by other means that there are stuck events.
UPDATE
I've found in my investigations that the problem is at the lowest level in HammerJS: the PointerEvents handler has an array of detected pointers this.store and there's the stuck event with an old timestamp.
I've found a way to patch Hammer.JS so it can detect stuck pointers. I don't know if this is wrong, but apparently it works!
On HammerJS PointerEvents handler, there's an array this.store that keeps all current pointer events. It's there where, when we pan out of the window and release the touch, a stuck event is kept forever.
Clearing this array causes Hammer to go back to normal again.
I just added a condition where, if we are processing a Primary touch (start of a gesture?), and the store is not empty, it clears the store automatically.
How it works is, on the next interaction with the stuck hammer instance, the internal store gets reset and the gesture is interpreted properly.
On Hammer.js 2.0.6, around line 885
/**
* handle mouse events
* #param {Object} ev
*/
handler: function PEhandler(ev) {
var store = this.store;
var removePointer = false;
var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
var isTouch = (pointerType == INPUT_TYPE_TOUCH);
// get index of the event in the store
var storeIndex = inArray(store, ev.pointerId, 'pointerId');
// start and mouse must be down
if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
// NEW CONDITION: Check the store is empty on a new gesture
// http://stackoverflow.com/questions/35618107/cross-frame-events-on-ie-edge-break-hammerjs-v2
if (ev.isPrimary && store.length) {
window.console.warn ("Store should be 0 on a primary touch! Clearing Stuck Event!");
this.reset();
}
if (storeIndex < 0) {
store.push(ev);
storeIndex = store.length - 1;
}
} else if (eventType & (INPUT_END | INPUT_CANCEL)) {
removePointer = true;
}
// it not found, so the pointer hasn't been down (so it's probably a hover)
if (storeIndex < 0) {
return;
}
// update the event in the store
store[storeIndex] = ev;
this.callback(this.manager, eventType, {
pointers: store,
changedPointers: [ev],
pointerType: pointerType,
srcEvent: ev
});
if (removePointer) {
// remove from the store
store.splice(storeIndex, 1);
}
}
});
and also I define the function "reset":
/**
* Reset internal state
*/
reset: function() {
this.store = (this.manager.session.pointerEvents = []);
},
I am trying to use Mapbox/Leaflet to create a Heatmap. This is the exact example that I have working properly: https://www.mapbox.com/mapbox.js/example/v1.0.0/leaflet-heat/
The operative code is this:
map.on({
movestart: function () { draw = false; },
moveend: function () { draw = true; },
mousemove: function (e) {
if (draw) {
heat.addLatLng(e.latlng);
}
}
})
However, this does not work for touch screens. I have watched this video to get an idea of what I need to change: https://www.youtube.com/watch?v=wwffqMAS8K8#t=100
Being new to JS and webapps in general, I am unsure of how to use the syntax explained at around 36:00 minutes into this video. He provides a function that forks mouse/touch handling differently according to what type of event is detected:
var posX, posY;
function positionHandler(e) {
if ((e.clientX) && (e.clientY)) {
posX = e.clientX; posY = e.clientY;
} else if (e.targetTouches) {
posX = e.targetTouches[0].clientX;
posY = e.targetTouches[0].clientY;
e.preventDefault();
}
}
I understand that here we define a function, positionHandler, to return the position of a mouse or touch event on the screen. But I do not know how to integrate this and make it work with the Leaflet syntax above.
How do I adjust the example above for it to work on both desktops and touch screens? Hopefully I have shown here that I've tried to do the research but I'm stuck.
The answer to this question lies in the API for Leaflet. You should be able to use some of their event handlers in order to add this functionality. While I did not have luck just turning on the iHandler map.tap.enable(); I reconfigured it by replacing mouseend with click and mousemove with dblclick.
See documentation here for further information: http://leafletjs.com/reference.html#map-interaction-handlers
In my testing environment I'm using the unofficial webdriverjs from http://webdriver.io/ for testing my frontend. Even though I took a look in the source https://github.com/camme/webdriverjs I was unable to define mouse movements properly.
Using
client.moveTo(undefined, 200, 200, function () {
client.buttonDown (function () {
//print location of click
});
});
gives me a click at x: 200; y : 200. My problem is that no mouse movement is registered (using $('...').on('mousemove', func {}); )
Is there any way to send mouse movements in such a way that they are recognized as mousemove events?
Regards
Since I already got JQuery on the client side I solved the problem now by sending respective Javascript code which triggers customized events like for example shown here: Is there a way to trigger mousemove and get event.pageX, event.pageY? (third answer)