Canvas get touch position on mobile web - javascript

I have a code which drags a line from (x,y) co-ordinate to new mouse (x,y) co-ordinate. This works fine in desktop browsers, but for some reason it doesn't work in mobile browsers. I have added touch event listeners but I guess the co-ordinates are some how getting incorrect. Heres my code:
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
mx = (e.pageX - offsetX) - LINE_WIDTH;
my =( e.pageY - offsetY )- LINE_WIDTH;
}
function mouseDown(e){
getMouse(e);
clear(fctx);
var l = lines.length;
for (var i = l-1; i >= 0; i--) {
draw(fctx,lines[i]);
var imageData = fctx.getImageData(mx, my, 1, 1);
if (imageData.data[3] > 0) {
selectedObject = lines[i];
isDrag = true;
canvas.onmousemove = drag;
clear(fctx);
}
}
}
function mouseUp(){
isDrag = false;
}
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
canvas.addEventListener('touchstart', mouseDown, false);
canvas.addEventListener('touchend', mouseUp, false);
you can see the working part here: http://codepen.io/nirajmchauhan/pen/yYdMJR

Generating mouse events from touch events
OK seen this question up here for a while and no one is coming forward with an answer I will give one.
Touch events unlike mouse events involve many points of contact with the UI. To accommodate this the touch events supply an array of touchpoints. As a mouse can not be in two places at once the two interaction methods should really be handled separately for the best user experience. OP as you do not ask about detecting if the device is touch or mouse driven, I have left that for another person to ask.
Handling Both
Mouse and Touch events can coexist. Adding listeners for mouse or touch events on a device that does no have one or the other is not an issue. The missing input interface simply does not generate any events. This makes it easy to implements a transparent solution for your page.
It comes down to which interface you prefer and emulating that interface when the hardware for it is unavailable. In this case I will emulate the mouse from any touch events that are created.
Creating events programmatically.
The code uses the MouseEvent object to create and dispatch events. It is simple to use and the events are indistinguishable from real mouse events. For a detailed description of MouseEvents goto MDN MouseEvent
At its most basic.
Create a mouse click event and dispatch it to the document
var event = new MouseEvent( "click", {'view': window, 'bubbles': true,'cancelable': true});
document.dispatchEvent(event);
You can also dispatch the event to individual elements.
document.getElementById("someButton").dispatchEvent(event);
To listen to the event it is just the same as listening to the actual mouse.
document.getElementById("someButton").addEventListener(function(event){
// your code
));
The second argument in the MouseEvent function is where you can add extra information about the event. Say for example clientX and clientY the position of the mouse, or which or buttons for which button/s is being pressed.
If you have ever looked at the mouseEvent you will know there are a lot of properties. So exactly what you send in the mouse event will depend on what your event listener is using.
Touch events.
Touch events are similar to the mouse. There is touchstart, touchmove, and touchend. They differ in that they supply a array of locations, one item for each point of contact. Not sure what the max is but for this answer we are only interested in one. See MDN touchEvent for full details.
What we need to do is for touch events that involve only one contact point we want to generate corresponding mouse events at the same location. If the touch event returns more than one contact point, we can not know which their intended focus is on so we will simply ignore them.
function touchEventHandler(event){
if (event.touches.length > 1){ // Ignor multi touch events
return;
}
}
So now we know that the touch a single contact we can go about creating the mouse events based on the information in the touch events.
At its most basic
touch = event.changedTouches[0]; // get the position information
if(type === "touchmove"){
mouseEventType = "mousemove"; // get the name of the mouse event
// this touch will emulate
}else
if(type === "touchstart"){
mouseEventType = "mousedown"; // mouse event to create
}else
if(type === "touchend"){
mouseEventType = "mouseup"; // ignore mouse up if click only
}
var mouseEvent = new MouseEvent( // create event
mouseEventType, // type of event
{
'view': event.target.ownerDocument.defaultView,
'bubbles': true,
'cancelable': true,
'screenX':touch.screenX, // get the touch coords
'screenY':touch.screenY, // and add them to the
'clientX':touch.clientX, // mouse event
'clientY':touch.clientY,
});
// send it to the same target as the touch event contact point.
touch.target.dispatchEvent(mouseEvent);
Now your mouse listeners will receive mousedown, mousemove, mouseup events when a user touches the device at only one location.
Missing the click
All good so far but there is one mouse event missing, that is needed as well. "onClick" I am not sure if there is a equivilant touch event and just as an exercise I saw there is enough information in what we have to decide if a set of touch events could be considered a click.
It will depend on how far apart the start and end touch events are, more than a few pixels and its a drag. It will also depend on how long. (Though not the same as a mouse) I find that people tend to tap for click, while a mouse can be held, in lieu of conformation on the release, or drag away to cancel, this is not how people use the touch interface.
So I record the time the touchStart event happens. event.timeStamp and where it started. Then at the touchEnd event I find the distance it has moved and the time since. If they are both under the limits I have set I also generate a mouse click event along with the mouse up event.
So that is the basics way to convert touch events into mouse events.
Some CODE
Below is a tiny API called mouseTouch that does what I have just explained. It covers the most basic mouse interactions required in a simple drawing app.
// _______ _
// |__ __| | |
// _ __ ___ ___ _ _ ___ ___| | ___ _ _ ___| |__
// | '_ ` _ \ / _ \| | | / __|/ _ \ |/ _ \| | | |/ __| '_ \
// | | | | | | (_) | |_| \__ \ __/ | (_) | |_| | (__| | | |
// |_| |_| |_|\___/ \__,_|___/\___|_|\___/ \__,_|\___|_| |_|
//
//
// Demonstration of a simple mouse emulation API using touch events.
// Using touch to simulate a mouse.
// Keeping it clean with touchMouse the only pubic reference.
// See Usage instructions at bottom.
var touchMouse = (function(){
"use strict";
var timeStart, touchStart, mouseTouch, listeningElement, hypot;
mouseTouch = {}; // the public object
// public properties.
mouseTouch.clickRadius = 3; // if touch start and end within 3 pixels then may be a click
mouseTouch.clickTime = 200; // if touch start and end in under this time in ms then may be a click
mouseTouch.generateClick = true; // if true simulates onClick event
// if false only generate mousedown, mousemove, and mouseup
mouseTouch.clickOnly = false; // if true on generate click events
mouseTouch.status = "Started."; // just for debugging
// ES6 new math function
// not sure the extent of support for Math.hypot so hav simple poly fill
if(typeof Math.hypot === 'function'){
hypot = Math.hypot;
}else{
hypot = function(x,y){ // Untested
return Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
};
}
// Use the new API and MouseEvent object
function triggerMouseEvemt(type,fromTouch,fromEvent){
var mouseEvent = new MouseEvent(
type,
{
'view': fromEvent.target.ownerDocument.defaultView,
'bubbles': true,
'cancelable': true,
'screenX':fromTouch.screenX,
'screenY':fromTouch.screenY,
'clientX':fromTouch.clientX,
'clientY':fromTouch.clientY,
'offsetX':fromTouch.clientX, // this is for old Chrome
'offsetY':fromTouch.clientY,
'ctrlKey':fromEvent.ctrlKey,
'altKey':fromEvent.altKey,
'shiftKey':fromEvent.shiftKey,
'metaKey':fromEvent.metaKey,
'button':0,
'buttons':1,
});
// to do.
// dispatch returns cancelled you will have to
// add code here if needed
fromTouch.target.dispatchEvent(mouseEvent);
}
// touch listener. Listens to Touch start, move and end.
// dispatches mouse events as needed. Also sends a click event
// if click falls within supplied thresholds and conditions
function emulateMouse(event) {
var type, time, touch, isClick, mouseEventType, x, y, dx, dy, dist;
event.preventDefault(); // stop any default happenings interfering
type = event.type ; // the type.
// ignore multi touch input
if (event.touches.length > 1){
if(touchStart !== undefined){ // don't leave the mouse down
triggerMouseEvent("mouseup",event.changedTouches[0],event);
}
touchStart = undefined;
return;
}
mouseEventType = "";
isClick = false; // default no click
// check for each event type I have the most numorus move event first, Good practice to always think about the efficancy for conditional coding.
if(type === "touchmove" && !mouseTouch.clickOnly){ // touchMove
touch = event.changedTouches[0];
mouseEventType = "mousemove"; // not much to do just move the mouse
}else
if(type === "touchstart"){
touch = touchStart = event.changedTouches[0]; // save the touch start for dist check
timeStart = event.timeStamp; // save the start time
mouseEventType = !mouseTouch.clickOnly?"mousedown":""; // mouse event to create
}else
if(type === "touchend"){ // end check time and distance
touch = event.changedTouches[0];
mouseEventType = !mouseTouch.clickOnly?"mouseup":""; // ignore mouse up if click only
// if click generator active
if(touchStart !== undefined && mouseTouch.generateClick){
time = event.timeStamp - timeStart; // how long since touch start
// if time is right
if(time < mouseTouch.clickTime){
// get the distance from the start touch
dx = touchStart.clientX-touch.clientX;
dy = touchStart.clientY-touch.clientY;
dist = hypot(dx,dy);
if(dist < mouseTouch.clickRadius){
isClick = true;
}
}
}
}
// send mouse basic events if any
if(mouseEventType !== ""){
// send the event
triggerMouseEvent(mouseEventType,touch,event);
}
// if a click also generates a mouse click event
if(isClick){
// generate mouse click
triggerMouseEvent("click",touch,event);
}
}
// remove events
function removeTouchEvents(){
listeningElement.removeEventListener("touchstart", emulateMouse);
listeningElement.removeEventListener("touchend", emulateMouse);
listeningElement.removeEventListener("touchmove", emulateMouse);
listeningElement = undefined;
}
// start adds listeners and makes it all happen.
// element is optional and will default to document.
// or will Listen to element.
function startTouchEvents(element){
if(listeningElement !== undefined){ // untested
// throws to stop cut and past useage of this example code.
// Overwriting the listeningElement can result in a memory leak.
// You can remove this condition block and it will work
// BUT IT IS NOT RECOGMENDED
throw new ReferanceError("touchMouse says!!!! API limits functionality to one element.");
}
if(element === undefined){
element = document;
}
listeningElement = element;
listeningElement.addEventListener("touchstart", emulateMouse);
listeningElement.addEventListener("touchend", emulateMouse);
listeningElement.addEventListener("touchmove", emulateMouse);
}
// add the start event to public object.
mouseTouch.start = startTouchEvents;
// stops event listeners and remove them from the DOM
mouseTouch.stop = removeTouchEvents;
return mouseTouch;
})();
// How to use
touchMouse.start(); // done using defaults will emulate mouse on the entier page
// For one element and only clicks
// HTML
<input value="touch click me" id="touchButton" type="button"></input>
// Script
var el = document.getElementById("touchButton");
if(el !== null){
touchMouse.clickOnly = true;
touchMouse.start(el);
}
// For drawing on a canvas
<canvas id="touchCanvas"></canvas>
// script
var el = document.getElementById("touchButton");
if(el !== null){
touchMouse.generateClick = false; // no mouse clicks please
touchMouse.start(el);
}
// For switching elements call stop then call start on the new element
// warning touchMouse retained a reference to the element you
// pass it with start. Dereferencing touchMouse will not delete it.
// Once you have called start you must call stop in order to delete it.
// API
//---------------------------------------------------------------
// To dereference call the stop method if you have called start . Then dereference touchMouse
// Example
touchMouse.stop();
touchMouse = undefined;
// Methods.
//---------------------------------------------------------------
// touchMouse.start(element); // element optional. Element is the element to attach listeners to.
// Calling start a second time without calling stop will
// throw a reference error. This is to stop memory leaks.
// YOU Have been warned...
// touchMouse.stop(); // removes listeners and dereferences any DOM objects held
//---------------------------------------------------------------
// Properties
// mouseTouch.clickRadius = 3; // Number. Default 3. If touch start and end within 3 pixels then may be a click
// mouseTouch.clickTime = 200; // Number. Default 200. If touch start and end in under this time in ms then may be a click
// mouseTouch.generateClick; // Boolean. Default true. If true simulates onClick event
// // if false only generate mousedown, mousemove, and mouseup
// mouseTouch.clickOnly; // Boolean. Default false. If true only generate click events Default false
// mouseTouch.status; // String. Just for debugging kinda pointless really.
So hope that helps you with your code.

Related

How to Avoid continuous triggering of HTML5 DeviceOrientationEvent

I am trying to tilt an image based on HTML5 DeviceOrientation event. However, I am seeing that the event is getting continuously fired even when the device is stable i.e non rotating/non moving. In the following code snippet, the console log is printed continuously. What is the possible reason, how can I stop it.
I tried both capturing and bubbling,
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', function(eventData) {
var tiltLR = eventData.gamma;
console.log("tiltLR..........",tiltLR);
}, false);
}
I havent needed to use this type of event listener before so I am not familiar with the output.
However, I believe you would need to compare the old tilt with the new tilt. If the new tilt is substantially greater or less then... execute code.
if (window.DeviceOrientationEvent) {
var originalTilt = undefined,
tolerance = 5;
window.addEventListener('deviceorientation', function(eventData) {
if (eventData.gamma > originalTilt + tolerance ||
eventData.gamma < originalTilt - tolerance){
var tiltLR = eventData.gamma;
console.log("tiltLR..........",tiltLR);
originalTilt = tiltLR;
}
}, false);
}

EaselJS: add child with mousedown triggered

The basic functionality I'm going for is...
Tap (mousedown) on the stage to create and add a child at that location.
*EDIT: I'm also trying to solve for multitouch, so multiple balls can be created at the same time.
As you hold down you can drag (pressmove) that child around and it grows (using regX/regY/scaleX/scaleY) until you release (pressup), at which point it falls (using a tick cycle).
I have everything sort of working, but I've hit a snag wherein I can't add a child and have it immediately register mousedown without releasing and pressing again.
Is there a way to manually trigger mousedown after adding, or some other workaround that makes more sense? dispatchEvent doesn't seem to be working.
Here's my stage event listener and touch bits:
stage.enableMouseOver(10);
createjs.Touch.enable(stage, false, false);
stage.preventSelection = false;
stage.addEventListener("stagemousedown", spawnsnowball);
And here are my functions. The spawnsnowball one includes displayObject event listeners verging on desperate, but the only way I've been able to get the pressmove and pressup working is to click on the same snowball again. releasesnowball right now just releases all instances of them (using a 'stagemouseup' listener), but if I can get it triggering off of pressup then I'll rewrite it to target just the event target.
function spawnsnowball(evt){
var ball = new createjs.Bitmap(loader.getResult("snowball"));
ball.crossOrigin="Anonymous";
ball.name="ball";
ball.scaleX = 0.5;
ball.scaleY = ball.scaleX;
ball.regX = ball.image.width/2;
ball.regY = ball.image.height/2;
ball.x = evt.stageX;
ball.y = evt.stageY;
ball.type = balltype;
ball.holding = 1;
ball.velX = 0;
ball.velY = 0;
ball.addEventListener("pressup",releasesnowball);
ball.addEventListener("pressmove",dragsnowball);
ball.onPress = function(mouseEvent) {};
stage.addChild(ball);
ball.dispatchEvent("mousedown");
ball.dispatchEvent("pressdown");
}
function dragsnowball(evt){
evt.target.x = evt.stageX;
evt.target.y = evt.stageY;
}
function releasesnowball(evt){
for(var i=0;i<stage.getNumChildren();i++){
var shape = stage.getChildAt(i);
if(shape.type == balltype){
if(shape.holding){
shape.holding = 0;
var dX = shape.x - shape.oldX;
var dY = shape.y - shape.oldY;
if(Math.abs(dY)>8)
dY = 8*dY/Math.abs(dY);
if(Math.abs(dX)>3)
dX = 3*dX/Math.abs(dX);
}
}
}
}
The pressmove event is special because it basically stores off the target of the last mousedown event, and then remembers it for pressmove and pressup events.
This means you can't really fake the event by forcing mouse events. Dispatching a mouse event from the target will not do the trick.
Instead, consider just handling the initial drag manually. You already know what you want to be the target of the pressmove, so you can listen for the stagemousemove event, and handle it yourself:
// Listen to the stagemousemove and manually call the event.
var initialDrag = stage.on("stagemousemove", function(event) {
event.target = ball; // Re-target the event so your other method works fine.
dragsnowball(event);
});
// When done, remove the move listener.
// The off() method supports a "once" parameter so you don't have to unsubscribe that listener.
stage.on("stagemouseup", function(event) {
stage.off("stagemousemove", initialDrag);
}, null, true); // Fires one time
Here is a quick sample using your code as the base: http://jsfiddle.net/3qhmur82/
I also added some comments in the demo which might be useful.
Hope that helps!

.setCapture and .releaseCapture in Chrome

I have an HTML5 canvas based Javascript component that needs to capture and release mouse events. In the control the user clicks an area inside it and drags to affect a change. On PC I would like the user to be able to continue dragging outside of the browser and for the canvas to receive the mouse up event if the button is released outside of the window.
However, according to my reading setCapture and releaseCapture aren't supported on Chrome.
Is there a workaround?
An article written in 2009 details how you can implement cross-browser dragging which will continue to fire mousemove events even if the user's cursor leaves the window.
http://news.qooxdoo.org/mouse-capturing
Here's the essential code from the article:
function draggable(element) {
var dragging = null;
addListener(element, "mousedown", function(e) {
var e = window.event || e;
dragging = {
mouseX: e.clientX,
mouseY: e.clientY,
startX: parseInt(element.style.left),
startY: parseInt(element.style.top)
};
if (element.setCapture) element.setCapture();
});
addListener(element, "losecapture", function() {
dragging = null;
});
addListener(document, "mouseup", function() {
dragging = null;
}, true);
var dragTarget = element.setCapture ? element : document;
addListener(dragTarget, "mousemove", function(e) {
if (!dragging) return;
var e = window.event || e;
var top = dragging.startY + (e.clientY - dragging.mouseY);
var left = dragging.startX + (e.clientX - dragging.mouseX);
element.style.top = (Math.max(0, top)) + "px";
element.style.left = (Math.max(0, left)) + "px";
}, true);
};
draggable(document.getElementById("drag"));
The article contains a pretty good explanation of what's going on, but there are a few gaps where knowledge is assumed. Basically (I think), in Chrome and Safari, if you handle mousemove on the document then, if the user clicks down and holds the mouse, the document will continue receiving mousemove events even if the cursor leaves the window. These events will not propagate to child nodes of the document, so you have to handle it at the document level.
Chrome supports setPointerCapture, which is part of the W3C Pointer events recommendation. Thus an alternative would be to use pointer events and these methods.
You might want to use the jquery Pointer Events Polyfill to support other browsers.

d3.behavior.zoom disable double tap

I am not sure if this is possible, but I would like to disable the zoom behavior on double tap, and keep the pinch-zoom behavior when using on tablet. I would like to use this event for other functionality.
If I disable "touchstart.zoom" event I am loosing the whole zooming functionality.
D3 controls the touchstart.zoom event on the element you've selected to zoom with d3.behavior.zoom()(). You can't simply replace this handler and conditionally call the original D3 handler because part of it's algorithm adds and removes this handler, so your override would be overwritten.
However, you can catch this event further upstream and conditionally allow it to propagate to the element with the zoom behavior. For this to work, you need to add your handler to a child element(s) so that it will bubble up to the zooming element. For example:
<g class="zoom_area"> <-- Element you called D3 zoom behaviour on
<rect width=... height=... style="visibility:hidden; pointer-events:all" class="background">
// Background rect that will catch all touch events missed by your elements
</rect>
<g class="content"> <-- Container for your elements
... <-- Your SVG content
</g>
</g>
Setup the normal D3 zoom behaviour:
var zoomer = d3.behavior.zoom();
zoomer(d3.select('g.zoom_area'));
Then setup your double touch override:
var last_touch_time = undefined;
var touchstart = function() {
var touch_time = d3.eent.timeStamp;
if (touch_time-last_touch_time < 500 and d3.event.touches.length===1) {
d3.event.stopPropagation();
last_touch_time = undefined;
}
last_touch_time = touch_time;
};
d3.select('.background_rect').on('touchstart.zoom', touchstart);
d3.select('.content').on('touchstart.zoom', touchstart);
Here is an alternative version that will only detect rapid touches where the tap occurs in a similar location. The downside is that rapid taps in different locations will still zoom-in. The upside is that rapid legitimate panning/zooming gestures will still work.
var last_touch_event = undefined;
var touchstart = function() {
if (last_touch_event && d3.event.touches.length===1 &&
d3.event.timeStamp - last_touch_event.timeStamp < 500 &&
Math.abs(d3.event.touches[0].screenX-last_touch_event.touches[0].screenX) < 10 &&
Math.abs(d3.event.touches[0].screenY-last_touch_event.touches[0].screenY) < 10) {
d3.event.stopPropagation();
last_touch_time = undefined;
}
last_touch_event = d3.event;
};
d3.select('.background_rect').on('touchstart.zoom', touchstart);
d3.select('.content').on('touchstart.zoom', touchstart);

how to trigger a drag event on click of a button jquery ui

Im attempted to integrate an image cropping tool into my website.
Im using this http://www.trepmag.ch/z/jrac/example/
While it works perfectly on the example, unfortunately Im my own site it is not so smooth.
When I drag the box across the screen, it will not always update the co-ordinates unless I drag it a tiny bit more at the end.
My plan was to simply take the co-ordinates and send them to the server, but this is holding me up as its taking incorrect co-ordinates. Even if I drag the cropping area by another pixel it at the end it updates them correctly.
My thinking was to simply trigger an automatic drag of an insignificant amount (like .1px) to get the correct co-ordinates. However I have no idea how to trigger a drag event.
I was thinking of using mousedown but I still do not know how to tell it to drag.
Here is the plugin code itself
http://www.trepmag.ch/z/jrac/jrac/jquery.jrac.js
EDIT: Here is the code of the event handling...
$('#output img').jrac({
'crop_width': 250,
'crop_height': 170,
'crop_x': 100,
'crop_y': 100,
'image_width': 400,
'viewport_onload': function() {
alert('viewport loaded');
var $viewport = this;
var inputs = $('#coordinates').find('.coords input:text');
var events = ['jrac_crop_x','jrac_crop_y','jrac_crop_width','jrac_crop_height','jrac_image_width','jrac_image_height'];
for (var i = 0; i < events.length; i++) {
var event_name = events[i];
// Register an event with an element.
$viewport.observator.register(event_name, inputs.eq(i));
// Attach a handler to that event for the element.
inputs.eq(i).bind(event_name, function(event, $viewport, value) {
$(this).val(value);
})
// Attach a handler for the built-in jQuery change event, handler
// which read user input and apply it to relevent viewport object.
.change(event_name, function(event) {
var event_name = event.data;
$viewport.$image.scale_proportion_locked = $('#coordinates').find('.coords input:checkbox').is(':checked');
$viewport.observator.set_property(event_name,$(this).val());
});
}
$viewport.$container.append('<div>Image natual size: '
+$viewport.$image.originalWidth+' x '
+$viewport.$image.originalHeight+'</div>')
}
})
// React on all viewport events.
.bind('jrac_events', function(event, $viewport) {
var inputs = $('#coordinates').find('.coords input');
inputs.css('background-color',($viewport.observator.crop_consistent())?'chartreuse':'salmon');
});

Categories

Resources