Can I use JQuery to query the Trackpad?
So I can do something like this:
Pseudo Javascript (JQuery)
$(document).keyInput().trackpadTwoFingersLeft(function() {
$('#div ul').animate({left: "=+1"},1);
});
Is there a plugIn or another framework where I can do this?
Thank you for every response and idea. :)
I've looked around a bit on the web, and so far see that both Chrome and Safari do not expose these events in the browser.
https://superuser.com/questions/27627/three-finger-page-up-page-down-in-safari-chrome
Touch events available in Safari?
Firefox does support something:
https://developer.mozilla.org/En/DOM/Mouse_gesture_events
But I don't see a lot of references to this.
I guess when only one browser supports it, it is a bit useless to use these kind of events.
There is a wheel event which you can use to detect two-finger swipe on Mac.
Your code could look something like this:
$('element').on('wheel', function(e){
var eo = e.originalEvent;
if(Math.abs(eo.wheelDeltaY) < 10 && Math.abs(eo.wheelDeltaX) > 2){
e.preventDefault();
if(eo.wheelDeltaX < -100 && !scope.item.swipedLeft){
// swipe left
}
if(eo.wheelDeltaX > 100 && scope.item.swipedLeft){
// swipe right
}
}
});
This could possibly not work in some older browser and/or Mozilla (as it fires some different event for the wheel movement), but as long as you implement this as an additional/helper feature, this code should suffice.
You could probably use mouse wheel tracking to get the desired effect: http://www.switchonthecode.com/tutorials/javascript-tutorial-the-scroll-wheel
If you want to use jQuery, this mousewheel plugin should help: http://brandonaaron.net/code/mousewheel/docs
We manage to determine trackpad usage by first assuming an OSX machine has it and then working to detect if that user has a mouse plugged in. Excuse me, because we use typescript, but it will work in javascript.
First, just look at the user agent properties, and then if OSX, assume it has one. I just use some basic string expressions on the user agent to determine what the OS is.
if (!props.isMobile && props.operatingSystem === OSType.Darwin) {
props.hasTouchpad = true
props.hasGestureSupport = props.browser === BrowserType.Safari | props.isMobile
}
Now work to eliminate that device by listening to the wheel event. A trackpad device will create really small deltaY values that are non-integer. I have tried this and it does a very good job.
const detectMouseType = useCallback((e:WheelEvent) => {
if (e.ctrlKey || !browserProperties.hasTouchpad) {
//Didn't detect originally, or pinch zoom OSX and surface, ignore
return
}
let isMouse = e.deltaX === 0 && !Number.isInteger(e.deltaY)
isMouseCounts.push(isMouse ? 1 : 0)
if (isMouseCounts.length > 5) isMouseCounts.shift()
let sum = isMouseCounts.reduce(function(a, b) { return a + b; });
if (sum > 3 && e.type === "wheel") {
console.log("Touchpad disabled")
//detected a mouse not a touchpad, maybe a mouse plugged into a mac
document.removeEventListener('wheel', detectMouseType);
props.hasTouchpad = false
props.hasGestureSupport = false
}
}, [])
Now onto using trackpad events and detecting pinch in browsers that normally don't support it (HammerJS for example):
On OSX Safari and iOS Safari the "gesturestart" and "gesturechange" events are fired which are sufficient for detecting pinch and rotate. There are scale and rotate parameters you can use to get the data you need.
Here's some code to get you started:
https://medium.com/#auchenberg/detecting-multi-touch-trackpad-gestures-in-javascript-a2505babb10e
On OSX Chrome, you can use the "wheel" event to detect pinch, scroll, and two finger tap. Most solutions you'll see will not support pinch or two finiger tap on OSX Chrome, but this will. Here's how two finger tap and pinch are done for Chrome and also mice on all other browsers:
let scale = 1.0;
let posX = 0.0;
let posY = 0.0;
element.addEventListener('wheel', (e) => {
e.preventDefault();
if (e.ctrlKey) {
//using two fingers
if (e.deltaY === 0 && e.deltaX === 0) {
console.log("Chrome two finger tap detected")
} else {
console.log("pinch zoom")
scale = scale - e.deltaY * 0.01
}
} else {
console.log('scrolling')
posX = posX - e.deltaX * 2
posY = posY - e.deltaY * 2
}
});
There's some info here but I wasn't able to test it
function setupForceClickBehavior(someElement)
{
// Attach event listeners in preparation for responding to force clicks
someElement.addEventListener("webkitmouseforcewillbegin", prepareForForceClick, false);
someElement.addEventListener("webkitmouseforcedown", enterForceClick, false);
someElement.addEventListener("webkitmouseforceup", endForceClick, false);
someElement.addEventListener("webkitmouseforcechanged", forceChanged, false);
}
https://developer.apple.com/library/archive/documentation/AppleApplications/Conceptual/SafariJSProgTopics/RespondingtoForceTouchEventsfromJavaScript.html#//apple_ref/doc/uid/TP40016162-SW6
Related
I am using a leap motion sensor with my node app to detect gestures. With my current setup, the gesture detection is working but not as accurately as I need it to be. When I perform a swipe gesture the leap motion does not always send a swipe gesture object and when it does detect a swipe it often will send multiple gestures. For example, when swiping left, it will return a left swipe as well as an up swipe. I am wondering if this is an issue with my code or my leap motion sensor.
Here is my leap controls code:
var controller = Leap.loop({ inNode: true, enableGestures: true }, function(frame) {
// finger location tracking
if(frame.hands.length > 0) {
var iBox = frame.interactionBox;
var pointable = frame.pointables[0];
if (pointable) {
var leapPoint = pointable.stabilizedTipPosition;
var normalizedPoint = iBox.normalizePoint(leapPoint, true);
let mouseControlY = (1 - normalizedPoint[1]) * appHeight; // with leap facing up
// invert directions for mirror
let invertMouseControlX = (1 - normalizedPoint[0]) * appWidth;
updateMouseCords(invertMouseControlX, mouseControlY)
}
}
// finger gesture tracking
if(frame.valid && frame.gestures.length > 0){
frame.gestures.forEach(function(gesture){
switch (gesture.type){
case "screenTap":
console.log("Click");
break;
case "swipe":
handleSwipe(gesture);
break;
}
});
}
});
function handleSwipe (swipe) {
let io = getIO();
if(swipe.state === 'stop'){
if (swipe.direction[0] > 0){
// user swiped right
swipeDirection = 'right'
} else {
// user swiped left
swipeDirection = 'left'
}
if(swipe.direction[1] > 0) {
// user swiped up
swipeDirection = 'up'
}
// send swipe direction to frontend
io.emit('swipeData', swipeDirection);
}
}
I only need to detect a left, right, or up swipes and possibly the screen tap gesture. Is there anything I can do to optimize my code or is the issue likely my sensor?
I am not an expert myself, but I have prevented multiple detections by using the setTimeout() function, which is set to a few milliseconds where no gesture detection can occur.
The JavaScript documentation also mentions the attributes Gesture.duration and Gesture.type. Perhaps you can achieve better results by using them.
It is also worth noting, that Gestures are unfortunately deprecated in version 3.0. However, I am not sure if this is due to the problems you described or if they will be fundamentally revised in later versions. I would also be interested in an update on this topic.
Is there any way to detect if the client is using a touchpad vs. a mouse with Javascript?
Or at least to get some reasonable estimate of the number of users that use touchpads as opposed to mice?
Compare e.wheelDeltaY and e.deltaY (or e.deltaMode in Firefox) to detect touchpad mouse device
function handler(e) {
var isTouchPad = e.wheelDeltaY ? e.wheelDeltaY === -3 * e.deltaY : e.deltaMode === 0
// your code
document.body.textContent = isTouchPad ? "isTouchPad" : "isMouse"
}
document.addEventListener("mousewheel", handler, false);
document.addEventListener("DOMMouseScroll", handler, false);
The answer above by Lauri seems to work, but it took me a while to understand why it works. So here I'll provide a slightly more human readable version, along with a conceptual explanation. First, that same code written out in a human readable manner:
function detectTrackPad(e) {
var isTrackpad = false;
if (e.wheelDeltaY) {
if (e.wheelDeltaY === (e.deltaY * -3)) {
isTrackpad = true;
}
}
else if (e.deltaMode === 0) {
isTrackpad = true;
}
console.log(isTrackpad ? "Trackpad detected" : "Mousewheel detected");
}
document.addEventListener("mousewheel", detectTrackPad, false);
document.addEventListener("DOMMouseScroll", detectTrackPad, false);
This works because wheelDeltaY measures the physical distance that the actual hardware mouse wheel has travelled, while deltaY measures the amount of scrolling produced on screen. A conventional mouse typically has a much lower "scroll resolution" than a trackpad. That is to say, with a trackpad you can make a tiny motion and a get a tiny scroll on screen. A conventional mouse scrolls in chunkier, low resolution clicks. To complete a full rotation of the mouse wheel, it might make 10 clicks. There is no such thing as a half click or quarter click.
For a conventional mouse, a single wheel click is reported as 120 wheelDeltaY "units" and results in about ~100px worth of scrolling. The physical wheelDeltaY unit is a completely arbitrary number, it is not measuring inches or degrees or anything like that. The number 120 was selected simply because it has a lot of useful factors. The amount of scrolling on screen is represented by deltaY, and it varies significantly by browser. (Sidenote, deltaY is generally measured in "lines" not pixels, though it's complicated, see previous link).
Interacting with a trackpad is different in two ways. First of all, you can get wheelDeltaY values much smaller than 120, because very subtle finger gestures are detectable. Second, the wheelDeltaY is exactly 3x the deltaY value (at least in every browser I've managed to test). So, for instance, if you make a physical finger gesture equal to 12 click units, it will generally result in 4 pixels worth of scrolling. Lauri's code uses this second property (Y1 = Y2 * 3) to detect the existence of a trackpad, but you could probably also be successful simply by checking if abs(wheelDeltaY) equals 120
I haven't tested this, but I think it would also work:
function detectTrackPad(e) {
var isTrackpad = false;
if (e.wheelDeltaY) {
if (Math.abs(e.wheelDeltaY) !== 120) {
isTrackpad = true;
}
}
else if (e.deltaMode === 0) {
isTrackpad = true;
}
console.log(isTrackpad ? "Trackpad detected" : "Mousewheel detected");
}
document.addEventListener("mousewheel", detectTrackPad, false);
document.addEventListener("DOMMouseScroll", detectTrackPad, false);
This topic may be already solved, but the answer was there is no way to detect it. Well I needed to get a solution, it was very important. 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 > 100) {
if (eventCount > 10) {
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);
It may need some optimization and is maybe less than perfect, but it works! At least it can detect a macbook trackpad. But due to the design i'd say it should work anywhere where the pad introduces a lot of event calls.
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.
Edit 2 I changed the numbers for the time check and numbers of events called from 50 to 100 and 5 to 10 respectively. This should yield in a more accurate detection.
You could detect JS events.
A touch device will fire touch events such as touchstart in addition to mouse events.
A non-touch device will only fire the mouse events.
In the general case, there is no way to do what you want. ActiveX might allow you to see and examine USB devices, but in the best case, even if that is somehow possible, that limits you to IE users. Beyond that, there is no way to know.
You might be able to discern patterns in how (or how often) a touchpad user moves the cursor versus how a mouse user might move the cursor. Differentiating between physical input devices in this way is an absurdly difficult prospect, and may be wholly impossible, so I include here it for completeness only.
Wheel event triggered by touchpad will give much smaller event.deltaY,1 or 2,but trigger by mouse wheel will give like 100,200.
Trust me. This is the easiest and only solution that also works for Safari (as far as I know).
isTrackPad(e) {
const { deltaY } = e;
if (deltaY && !Number.isInteger(deltaY)) {
return false;
}
return true;
}
From testing plugging in a mouse to a mac which does have a touchpad and also a windows machine with one, I can summarize how I got this working.
Detect if the navigator user agent contains "Mobile" or "Mac OS" If either
of these are true, it is likely a touch based system, but work to
eliminate that. Set boolean hasTouchPad to true
If the above is true, detect "mouse" events and run a test like high
numbers, or a frequency of non integers or Kalman filtering.
Keep these in a queue and if that queue's sum passes a threshold,
disable the hasTouchpad variable and disconnect the event.
let isMouseCounts: Array<number> = []
if (Client.hasTouchpad) {
document.addEventListener('wheel', detectMouseType);
}
function detectMouseType(e:WheelEvent) {
if (!Client.hasTouchpad) return
let isMouse = e.deltaX === 0 && !Number.isInteger(e.deltaY)
isMouseCounts.push(isMouse ? 1 : 0)
if (isMouseCounts.length > 5) isMouseCounts.shift()
let sum = isMouseCounts.reduce(function(a, b) { return a + b; });
if (sum > 3 && e.type === "wheel") {
console.log("Touchpad disabled")
document.removeEventListener('wheel', detectMouseType);
Client.hasTouchpad = false;
}
}
You could just check for the device driver softwares installed into the local package as functioning. Like in windows synaptics, elan hardware, as for UNIX(Linux) you could just check for the package installed during the basic installed onto. A lot of packages come in different formats in different versions of Linux and Linux like systems(Not linux entirely) but they use the same package name for all. Just got to know the code to pull it. Still working on it.
I want to know whether the user is physically holding their screen in portrait or landscape mode, even if they have locked their screen.
I cannot use orientationchange events, as these won't fire if the rotation is locked. I want to follow the actual, real-world rotation of the device, not the logical orientation of the viewport.
I am aware that we should normally never force a display orientation on our users, and that we should respect users' preferences when they lock the orientation - but I really do have a reason to do this.
Is there ever any way?
Yes - if the phone is being held upright. Then we can use DeviceMotionEvents to read the pull of gravity on the phone's X and Y axes:
function watchOrientation(subscriber) {
if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', eventData => {
const hg = eventData.accelerationIncludingGravity.x;
const vg = eventData.accelerationIncludingGravity.y;
if (vg > 5) {
subscriber('up');
} else if (vg <= -5) {
subscriber('down');
} else if (hg > 0) {
subscriber('left');
} else if (hg <= 0) {
subscriber('right');
}
}, true);
} else {
console.warn('Device does not support "devicemotion" events');
}
}
Obviously, this won't work if the phone is held flat or laid on a table. In that case your only chance is to use the compass data in a DeviceOrientationEvent (but only if you already know whether your user is facing North!)
I bought some files through codecanyon.net and they've been working fine on all browsers. Just recently I noticed they weren't working in Chrome.
The code is really big and I've tried to change some things through trial and error on the js file but was unsuccessful. You can see the slider at http://miguelsart.com/scroller-test.
As you can see, the captions are supposed to be hidden and once you hover they have slide up. But with Chrome, the captions appear automatically and nothing happens when you hover.
I think something is wrong in this part of the code:
//init captions
Scroller.prototype.initCaptions = function() {
var $captions = this._$slides.find(">p:first");
if (this._displayCaption) {
var padding = $captions.outerWidth() - $captions.width();
$captions.css({width:this._slideWidth - padding, display:"inline-block"}).click(preventDefault);
if (this._captionPos == OUTSIDE) {
var heights = $captions.map(function() { return $(this).height(); }).get();
var maxHeight = Math.max.apply(Math, heights);
$captions.css({top:this._captionAlign == TOP ? 0 : this._slideHeight, height:maxHeight});
this._extHeight = $captions.outerHeight();
if (this._captionAlign == TOP) {
this._extOffset = this._extHeight;
}
$captions.addClass("outside");
}
else {
if (jQuery.browser.msie && parseInt(jQuery.browser.version) > 6 && parseInt(jQuery.browser.version) < 9) {
$captions.addClass("ie-inside");
}
else {
$captions.addClass("inside");
}
}
}
else {
$captions.hide();
}
}
I've tried messing around replacing display for opacity or for visibility but nothing worked. Does anyone have any clue what might be wrong?
Thanks in advance!
I believe I've figured out what's wrong with the author's implementation, and you are correct, it has to do with the latest version of Chrome.
On line 43 of jquery.wt.scroller.js
this._mouseoverCaption = window.Touch ? false : opts.mouseover_caption;
The author of the plugin is testing for native touch capabilities (by determining if window.Touch is defined). Chrome must've recently added native touch API capabilities in a recent version.
So what the author was going for, was saying that 'you can't hover on a touch device, so we can't show the captions on hover on a touch device so we'll just always show them' - which is logically.
However, just because touch capabilities exist, however, doesn't mean that touch input is the default (as in this case). Modernizr solves this issue (for now) by performing the following conditional:
if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
Something tells me this will also soon be broken. (https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js#L42)
So, long story short (too late, I know) add this to the plugin code:
Add this to line 7 (push all lines down one):
var TOUCH = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
Find and replace all occurrences of window.Touch with TOUCH in the plugin code.
Tell the author of the plugin I'll send him a bill. :)
I have recently come across a few websites that seems to access the accelerometer or gyroscope on my laptop, detecting changes in orientation or movement.
How is this done? Must I subscribe to some kind of event on the window object?
On which devices (laptops, mobile phones, tablets) is this known to work?
NB: I actually already know (part of) the answer to this question, and I am going to post it right away. The reason that I am posting the question here, is to let everyone else know that accelerometer data is available in Javascript (on certain devices) and to challenge the community to post new findings on the subject. Currently, there seems to be almost no documentation of these features.
There are currently three distinct events which may or may not be triggered when the client devices moves. Two of them are focused around orientation and the last on motion:
ondeviceorientation is known to work on the desktop version of Chrome, and most Apple laptops seems to have the hardware required for this to work. It also works on Mobile Safari on the iPhone 4 with iOS 4.2. In the event handler function, you can access alpha, beta, gamma values on the event data supplied as the only argument to the function.
onmozorientation is supported on Firefox 3.6 and newer. Again, this is known to work on most Apple laptops, but might work on Windows or Linux machines with accelerometer as well. In the event handler function, look for x, y, z fields on the event data supplied as first argument.
ondevicemotion is known to work on iPhone 3GS + 4 and iPad (both with iOS 4.2), and provides data related to the current acceleration of the client device. The event data passed to the handler function has acceleration and accelerationIncludingGravity, which both have three fields for each axis: x, y, z
The "earthquake detecting" sample website uses a series of if statements to figure out which event to attach to (in a somewhat prioritized order) and passes the data received to a common tilt function:
if (window.DeviceOrientationEvent) {
window.addEventListener("deviceorientation", function () {
tilt([event.beta, event.gamma]);
}, true);
} else if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', function () {
tilt([event.acceleration.x * 2, event.acceleration.y * 2]);
}, true);
} else {
window.addEventListener("MozOrientation", function () {
tilt([orientation.x * 50, orientation.y * 50]);
}, true);
}
The constant factors 2 and 50 are used to "align" the readings from the two latter events with those from the first, but these are by no means precise representations. For this simple "toy" project it works just fine, but if you need to use the data for something slightly more serious, you will have to get familiar with the units of the values provided in the different events and treat them with respect :)
Can't add a comment to the excellent explanation in the other post but wanted to mention that a great documentation source can be found here.
It is enough to register an event function for accelerometer like so:
if(window.DeviceMotionEvent){
window.addEventListener("devicemotion", motion, false);
}else{
console.log("DeviceMotionEvent is not supported");
}
with the handler:
function motion(event){
console.log("Accelerometer: "
+ event.accelerationIncludingGravity.x + ", "
+ event.accelerationIncludingGravity.y + ", "
+ event.accelerationIncludingGravity.z
);
}
And for magnetometer a following event handler has to be registered:
if(window.DeviceOrientationEvent){
window.addEventListener("deviceorientation", orientation, false);
}else{
console.log("DeviceOrientationEvent is not supported");
}
with a handler:
function orientation(event){
console.log("Magnetometer: "
+ event.alpha + ", "
+ event.beta + ", "
+ event.gamma
);
}
There are also fields specified in the motion event for a gyroscope but that does not seem to be universally supported (e.g. it didn't work on a Samsung Galaxy Note).
There is a simple working code on GitHub
The way to do this in 2019+ is to use DeviceOrientation API. This works in most modern browsers on desktop and mobile.
window.addEventListener("deviceorientation", handleOrientation, true);
After registering your event listener (in this case, a JavaScript
function called handleOrientation()), your listener function
periodically gets called with updated orientation data.
The orientation event contains four values:
DeviceOrientationEvent.absolute
DeviceOrientationEvent.alpha
DeviceOrientationEvent.beta
DeviceOrientationEvent.gamma
The event handler function can look something like this:
function handleOrientation(event) {
var absolute = event.absolute;
var alpha = event.alpha;
var beta = event.beta;
var gamma = event.gamma;
// Do stuff with the new orientation data
}
Usefull fallback here: https://developer.mozilla.org/en-US/docs/Web/Events/MozOrientation
function orientationhandler(evt){
// For FF3.6+
if (!evt.gamma && !evt.beta) {
evt.gamma = -(evt.x * (180 / Math.PI));
evt.beta = -(evt.y * (180 / Math.PI));
}
// use evt.gamma, evt.beta, and evt.alpha
// according to dev.w3.org/geo/api/spec-source-orientation
}
window.addEventListener('deviceorientation', orientationhandler, false);
window.addEventListener('MozOrientation', orientationhandler, false);