How to enable touchmove in a mobile element while disabling scrolling - javascript

I'm building an app using svg-pencil to allow users to draw images. Normally I would use atrament to enable drawing on canvas, but this project requires the output to be an SVG that I can then analyze at the <path> level.
SVG-pencil is great but is not mobile friendly since it was developed some time ago. Using atrament as a guide, I'm trying to make it responsive to touch events as well as mouse events.
The first step was to just add the touch event listeners to the part of svg-pencil's event registration:
this.listeners = {
mousedown: function (ev) { self._onmousedown(ev) },
mouseup: function (ev) { self._onmouseup(ev) },
mousemove: function (ev) { self._onmousemove(ev) },
// added these three lines
touchstart: function (ev) { self._onmousedown(ev) },
touchend: function (ev) { self._onmouseup(ev) },
touchmove: function (ev) { self._onmousemove(ev) }
};
The trouble is that, on mobile, the default behavior for touchmove is to scroll the page. So I added two event listeners to the <svg> element that the module creates to try and prevent this:
var svg = p.element;
svg.addEventListener('touchmove', function(e) {
e.stopImmediatePropagation();
// e.preventDefault();
});
svg.addEventListener('touchstart', function(e) {
e.stopImmediatePropagation();
// e.preventDefault();
});
This successfully fires the app's mousestart and mouseend events in a mobile emulator or a real phone if you just click the drawable area, and DOES successfully disable scrolling within the svg area if you drag inside it, but the mousemove event registered to touchmove above does not fire more than once. As you can see, I tried preventDefault as well with no luck.
I'm not an expert in event bubbling, but somehow it appears that the stopPropogation call on the svg element is also canceling the event inside svg-pencil.
Here's an example. There's a console log on every event. It works fine in regular browswer, but you can see the problem if you select a mobile emulator in Chrome's devtools or try it in Xcode's simulator.
The full, slightly modified code is here.
I also tried adding the preventDefault inside svg-pencil's event listeners with no luck.

Related

How to completely remove mobile browser long-press vibration?

I'm trying to completely remove the vibration that occurs on a long press of an element in a mobile browser.
Specifically, I have an image that I'm adding my own long-press functionality to, but the subtle short-vibrate that happens by default is interfering and I need to disable it.
I've successfully stopped the usual context menu from appearing by overriding it as follows:
window.oncontextmenu = function (event) {
event.preventDefault();
event.stopPropagation();
return false;
};
I've also added CSS to stop highlights and text selection etc - but I haven't been able to figure out what's causing the default vibrate after a few hundred ms.
Is there another lifecycle event that's popped on a long-press in a mobile browser, in or around oncontextmenu?
Full plunker Example here, long-press on the image from a mobile browser (I'm using chrome on Android) to see what I mean: https://plnkr.co/edit/y1KVPltTzEhQeMWGMa1F?p=preview
Disable the text selection when its clicked on.
document.querySelector("#your_target").addEventListener("touchstart", (e) =>
{
e.preventDefault();
e.stopPropagation();
this.style.userSelect = "none";
});
document.querySelector("#your_target").addEventListener("touchend", (e) =>
{
e.preventDefault();
e.stopPropagation();
this.style.userSelect = "default";
});
You could use touch-action: none; in CSS. Then you might be able to handle an interaction event to do what you want.
https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action

Template event selector 'not' isn't working

In this Meteor template code, when the canvas is clicked, it prints out the canvas element to the console but it is expected not to fire the event.
How can it be made so that it fires if the element which is clicked is not a canvas?
Template.myTemp.events({
'click *:not(canvas)': function(e) {
e.stopPropagation();
console.log(e.target);
});
Definitely an interesting problem as the :not selector is supported by Blaze, and works properly with other HTML elements. You might want to open an issue about this in the Blaze repo.
The above being said, there are a few different ways you can work around this. You could add a check in your event handler to make sure you don't do anything with canvas related events:
Template.myTemp.events({
'click *'(event, instance) {
if (event.target.nodeName.toLowerCase() !== 'canvas') {
// Handle non-canvas events ...
}
},
});
Another option involves chaining your event handlers, if you want to be able to filter out canvas events specifically. For example:
Template.myTemp.events({
'click canvas'(event, instance) {
event.stopImmediatePropagation();
// Handle canvas click events only ...
},
'click *'(event, instance) {
// Handle all click events except canvas click events, since they're
// captured and handled above ...
},
});

Using mousedown event on mobile without jQuery mobile?

I've built a webapp, and for a little bit of polish, I wanted to add mousedown and mouseup handlers to swap out images (in this case, to make a button look like it's being pressed).
my code is something like this:
window.onload = function() {
//preload mouse down image here via Image()
$("#button_img").mousedown(function(){$("#button_img").attr("src","button_on.png");});
$("#button_img").mouseup(function(){$("#button_img").attr("src","button_off.png")});
}
This works swimmingly on the desktop, but on mobile (testing in iOS Safari), the mousedown and mouseup events happen at the same time, so effectively nothing happens.
I tried to use the vmousedown and vmouseup events in jQueryMobile, however this code:
//include jquerymobile.js and jquerymobile.css
window.onload = function() {
//preload mouse down image here via Image()
$("#button_img").vmousedown(function(){$("#button_img").attr("src","button_on.png");});
$("#button_img").vmouseup(function(){$("#button_img").attr("src","button_off.png")});
}
Just gave me the errors that vmousedown and vmouseup don't exist. Also, jQueryMobile overrides the CSS I've already written for the page.
So is there a way to get vmousedown and vmouseup to work, and to do so without jQuery Mobile's CSS?
You're looking for touchstart and touchend. They are the events that vmousedown and vmouseup attempt to mimic.
Here's an example:
window.onload = function() {
//preload mouse down image here via Image()
$("#button_img").bind('touchstart', function(){
$("#button_img").attr("src","button_on.png");
}).bind('touchend', function(){
$("#button_img").attr("src","button_off.png");
});
}
This will work without any framework on any device that supports touch events. You could use something like Modernizr to do this test and if the device does not support touch events, bind to the regular desktop events.
When you use touchstart/touchend/touchmove you get some interesting information, for instance how many touches are occurring at once, so you can detect if the user is scrolling or attempting to zoom.
UPDATE
Since the event object inside an event handler differs for touch events and mouse events, if you want to know the coordinates of the event either way, you can do something like this (the example below assumes Modernizr has been loaded):
//determine which events to use
var startEventType = 'mousedown',
endEventType = 'mouseup';
if (Modernizr.touch === true) {
startEventType = 'touchstart';
endEventType = 'touchend';
}
//bind to determined event(s)
$("#button_img").bind(startEventType, function(event) {
//determine where to look for pageX by the event type
var pageX = (startEventType === 'mousedown')
? event.pageX
: event.originalEvent.touches[0].pageX;
...
})...
UPDATE
I was looking this over and it seems like you don't need to detect the event type before binding the event handler:
//bind to determined event(s)
$("#button_img").bind('mousedown touchstart', function(event) {
//determine where to look for pageX by the event type
var pageX = (event.type.toLowerCase() === 'mousedown')
? event.pageX
: event.originalEvent.touches[0].pageX;
...
})...
If you are worried about receiving both events in quick succession you could use a timeout to throttle the event handler:
//create timer
var timer = null;
//bind to determined event(s)
$("#button_img").bind('mousedown touchstart', function(event) {
//clear timer
clearTimeout(timer);
//set timer
timer = setTimeout(function () {
//determine where to look for pageX by the event type
var pageX = (event.type.toLowerCase() === 'mousedown')
? event.pageX
: event.originalEvent.touches[0].pageX;
...
}, 50);
})...
Note: You can force mousedown and touchstart events in quick succession with developer tools but I'm not sure about the real world use case here.
Have you considered styling your buttons using CSS instead? the :active state will be triggered when a user is clicking/touching the element. Here is an example:
/* Default state */
#button_img {
background-image: url('button_off.png');
}
/* Clicked/touched state */
#button_img:active {
background-image: url('button_on.png');
}
CSS will be much more performant and you will also be able to better separate concerns (display vs logic, etc).
JSBin: http://jsbin.com/beyin/1/
There is a way to get the vmouseup, vmousedown, vmousemove, vclick, etc. functionality of jQueryMobile without getting all the rest (and especially the side effects) of jquerymobile (i.e. enhancement, extra css, and the like)
Go to http://jquerymobile.com/download-builder/ (a tool for downloading a custom build of jquerymobile with only the components you need)
select ONLY "Virtual Mouse (vmouse) Bindings"
download it.
The download will contain only a single .js files (in both minimized and uncompressed version). No css.
Link this script in the head of your html after plain jquery, and use it like this:
<head>
<script src="http://code.jquery.com/ui/1.10.4/jquery-ui.min.js"></script>
<script src="whatever/path/jquery.mobile.custom.min.js"></script>
<script>
$(function(){ // or replace this with window.onload for that matter
// Your code here, e.g.
$("#button_img").on("vmousedown", function() {
/*whatever*/
});
// CAUTION: this won't work (see note below):
// $("#button_img").vmousedown(function(){/*whatever*/}); // WON'T WORK
});
</script>
</head>
NOTE: the methods .vmousedown(), .vmouseup(), etc. won't work. You have to bind the event listener with .on("vmousedown", ...).
Not sure why: I guess this is because the part of jquerymobile that creates shortcut methods with the same name as the events is in some other module. Maybe it is possible to figure out which module it is and include it in the download, but I think it would force you to include other undesired dependencies.
Use touchstart or touchend for touch devices.
Most times you want to catch touchstart as well as mousedown. You need to make sure though that the handler is only triggered once. The simplest way to do this is to catch them both and call e.preventDefault().
$("#button_img").on('touchstart mousedown', function(e) {
//your code...
e.preventDefault(); //prevents further events from being dispatched
}
Source: developer.mozilla.org:
If the browser fires both touch and mouse events because of a single user input, the browser must fire a touchstart before any mouse events. Consequently, if an application does not want mouse events fired on a specific touch target element, the element's touch event handlers should call preventDefault() and no additional mouse events will be dispatched.

Stop drag-n-drop event with Raphaeljs

I am trying to simulate the swipe event from the iPhone with Raphaeljs.
To do so, I am using the drag and drop event.
To simulate the event in have a method in the move event that calculate the distance between my object and the mouse position. If the mouse goes after that distance I want to stop the drag and drop event. This is where I'm stuck.
Here is the code:
var start = function (event) {
},
move = function (event) {
inrange = self.inRange (circle.attr("cx"), circle.attr("cy"), event.pageX, event.pageY);
if(inrange == false){
//Stop draging!
}
},
up = function () {
circle.animate({ r: 40, "stroke-width": 1 }, 200);
};
circle.drag(move, start, up);
In the move method I need the stop the drag event or simulate a mouseup. How can I do so?
From the documentation:
To unbind events use the same method names with “un” prefix, i.e. element.unclick(f);
and
To unbind drag use the undrag method.
So I would think circle.undrag(); should work.
#Gregory You can use a JS closure to detect if the circle needs to stop dragging. And #apphacker there is a known issue in Raphael that prevents undrag from working. It is scheduled to be fixed in 2.0, but that doesn't have a release date as of yet (and the bug isn't fixed in the beta code, despite the ticket saying it is.
I recommend manually implementing the mousedown, mouseup and mousemove events using jQuery, as per #floyd's recommendation, and add a JS closure check in your move function to see if the circle needs to stop being dragged yet or not.'
It also occurs to me that your original post was last edited in Nov '10, so you might have moved on since then ;)
If you can include jQuery you can use trigger on "mouseup." If you can't include jQuery maybe just take a look at the source and lift that one function?
UPDATE
After some brief googling I came across this implementation. Only tested in in Chrome though:
function fireEvent(element,event){
if (document.createEventObject){
// dispatch for IE
var evt = document.createEventObject();
return element.fireEvent('on'+event,evt)
} else{
// dispatch for firefox + others
var evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true ); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
}
}

tell pure touch event and pure gesture event apart?

I am doing iphone web development and I've noticed that: gesture events can also be touch events, which means a touch event handler also fires up when there is a gesture event.
Any way that I can add specific event handler to gesture (not touch)? Or how can I know if this is a pure touch event or a pure gesture event?
Edit: This was an answer for your original revision that asked if something is a "pure touch event". It won't help you with your changed question on getting pure gesture events.
Listen for gesture events and have a gesturing boolean that you check during touch events that is set to true by the event handler for the gesture events and set back to false by the event handler for the touch events if it is true.
I have not researched at all about these events, but here's a sample implementation:
var gesturing = false;
document.addEventListener(aTouchEventName, function () {
if (gesturing) {
return gesturing = false;
}
// your touch event handler code here
}, false);
document.addEventListener(aGestureEventName, function () {
gesturing = true;
}, false);
I am not sure the specifics on this but it looks like there are 3 events fired in a gesture.
-touchstart
-touchmove
-touchend
Ultimately your touch end will fire. What you can do is create a threshold amount between your star and end. And trigger a custom event if the (x,y) difference is past a certain amount.

Categories