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);
Related
I'm currently in the process of creating a webapp that detects whether the user is sitting or standing. I have had great success with detecting the tilt of the device, changing a boolean variable and sending the correct data to a graph.
The following code runs perfectly in the background, updating the data every second.
var trackInterval = setInterval(function(){
if(isSitting){
addData(myPieChart, "Sitting");
} else{
addData(myPieChart, "Standing");
}
}, 1000)
My issue is that the listening function that changes the variable 'isSitting' does not continue once the browser is closed. This means that the last value of 'isSitting' gets data added to it, even though the device might be tilted otherwise.
This is the code that creates the accelerometer updates:
window.addEventListener("devicemotion", accelerometerUpdate, true);
var isSitting = true;
function accelerometerUpdate(event) {
var aX = event.accelerationIncludingGravity.x * 100 ;
var aY = event.accelerationIncludingGravity.y * 100 ;
var aZ = event.accelerationIncludingGravity.z * 100 ;
if (aY > 600 || aY < -900 ){
isSitting = false;
} else{
isSitting = true;
}
}
I have tried Chrome, Opera and Firefox on my Android device.
Any help or tips to work around this would be greatly appreciated
This sounds like regular behaviour as your javascript code, to be executed, should run in your browser.
To achieve your goal, a possible guess would be to look into a way to use this api in service workers, which is currently not possible with the devicemotion api but should be (at least with chrome) with the new Generic Sensors API described here : https://developers.google.com/web/updates/2017/09/sensors-for-the-web
Does window.onload not work consistently with some browsers..?
I've put a script together purposely utilizing native JavaScript (no library or framework used). I know there may be some issues with cross-browser compatibility. However, there is something different occurring with the Edge browser at least.
The entire page works as expected in webkit(Chrome, not yet in Safari), and it seems to work perfect in the latest Firefox as well.
In Microsoft's Edge browser it seems to have a starter issue, or, it's not booting well every time on the onload trigger. It works perfectly sometimes, if I refresh the page enough.
What can possibly explain this? Edge may be better in this case as I'm not sure if it's working in Safari at all.
Codepen link!
window.onload = function() {
// if you're savvy enough for it... please check codepen for the full code
/* automation from input */
wght_input.oninput = function() {
x = this.value;
var bodyweight = x;
// when puts into form input
this.onchange = function() {
frmFeed[0].innerHTML = "";
recc_wght();
getadd();
whatsTotal();
resetButton.click() ;
};
var kgs = curr_wght_set_amt * kg; //convert lbs lifted to kgs
logs("The highest recorded lift was " + curr_wght_set_amt.toFixed(0) + "lbs or "+ kgs +"kgs");
}; // end active oninput
}; // end onload function
use window.addEventListener instead:
window.addEventListener('load', yourFunc, false);
You'd have to wrap everything else in a named function (yourFunc)...
I have a problem with performance optimization. Currently writing an application for copy machine on ANT Galio browser. The problem is that this does not allow to use the mousemove event, but an event mouseover works just fine. I had to write an invisible grid that movement senses. Since the browser does not know how to work with alpha channel had to use background with a picture in one transparent pixel. Browser only supports CSS2 and ecmaScript1.6. The problem is that the grid works fine but if it is serious loaded then the application begins to slow down, but if the same manipulations on the mousemove that all works well on the emulator but not working on the unit it self.
mouseover event handler:
function _onMouseOverSubTile (e) {
var tempSubTile = e.target,
myData = getData(tempSubTile),
myGrid = myData.grid,
myCallBack = myGrid.callBack;
if (myCallBack) {
var tempSubTile = e.target,
mySubTile = myData.tile;
subTileOffsetLeft = mySubTile.left,
subTileOffsetTop = mySubTile.top;
myCallBack(
//x
myGrid.viewPortOffsetLeft +
myGrid.layerLeft +
myGrid.currentTileOffsetLeft + .
subTileOffsetLeft +
myGrid.subTileWidth / 2,
//y
myGrid.viewPortOffsetTop +
myGrid.layerTop +
myGrid.currentTileOffsetTop +
subTileOffsetTop + myGrid.subTileHeight / 2);
};
};
Here is the application:
http://jsfiddle.net/Greck_geek/xfg85Lzn/
The application is running slow due to the transparent pixel!!! If do not use it then all works fine.
We have a web application shown in WebView on Android and we have performance issue on older Android tablets that we really need to deal with
We are using iScroll-lite for javascript dragging
We set hardware acceleration to true, set rendering priority to high and the WebView
lets through only one MOVE touch event in 80ms interval
Is there anything more we can do?
Is there maybe some faster alternative to iScroll-lite?
We don't know what exactly makes it so slow. It for example runs on phone Sony Erricson with 1 GHz, 512 MB RAM with Android 2.3 smoothly but on tablet Qualcomm 1GHz, 512 RAM with Android 4.0 you make a drag and you actually have to wait to see any outcome. The only difference I see in Android version and screen resolution.
It might not be the Javascript after all. Android WebViews tend to have quite unexpected performance issues.
Try to deactivate all unnecessary CSS and see if performance improves. If so, you can re-add the CSS styles iteratively to locate the culprit.
And do not use any kind of animated GIFs. Our experience is that this (strangely enough) drags down performance drastically.
My suggestion is that you write the drag by yourself. It's not difficult. The key part of code looks like below:
var yourDomElement = document.getElementById("demoDrag");
yourDomElement.addEventListener("touchstart", function (e) {
e.preventDefault();
//todo
});
yourDomElement.addEventListener("touchmove", function (e) {
//todo:
});
yourDomElement.addEventListener("touchend", function (e) {
//todo
});
You can find many sample code here (e.g. /assets/www/scripts/novas/framwork/nova.Carousel.js, also the scroller.js). You should read the sample code to learn how to write your own "drag".
If you still have no idea, you can contact our team to finish your task for you. We make phonegap apps with very good performance.
What about using div with overflow set to auto and then applying this fix so that elements can be scrolled
http://chris-barr.com/index.php/entry/scrolling_a_overflowauto_element_on_a_touch_screen_device/
Short extract:
function touchScroll(id){
var el=document.getElementById(id);
var scrollStartPos=0;
document.getElementById(id).addEventListener("touchstart", function(event) {
scrollStartPos=this.scrollTop+event.touches[0].pageY;
event.preventDefault();
},false);
document.getElementById(id).addEventListener("touchmove", function(event) {
this.scrollTop=scrollStartPos-event.touches[0].pageY;
event.preventDefault();
},false);
}
We have tried to get iScroll to work smoothly in a phonegap-app on the iPad(2/3), but we were not able to make that happen. Get anywhere near an acceptable in performance even though all stand-alone examples ran super smooth. We finally ended up using -webkit-overflow-scrolling:touch have you looked into that one yet? I know you need this for Android so I cannot say if it is as good there as on iOS6, but on the iPad it worked like a charm.
If you want to REALLY make a difference, I'd suggest you my approach:
I did a JavaScript-callable function for creating another WebView ("child webview") that I would use like an Iframe and fill with content, so from the JS application I could do:
insertWebView (x,y,w,h,"<div>.......</div>").
You have to do some one-time work to stabilish a way to comunicate both webviews, but you get the idea. Please find below attached the source of my insertWebView function for inspiration.
The improvement was awesome, as those tiny iframe-webviews not only performed awesome, but stuff like the glowing overscroll, multitouch (now they are different webviews!) etc provided a near-native experience.
I also did an extension to use native drag and drop between webviews.
Probably it was not very efficient memory-wise, but in terms of user experience believe me it was worth the effort, thousand times.
good luck!
public void insertWebView(int code, int x0, int y0, int w, int h, String url, int vertical, String initContent, String params, int alpha, int rot, int screenId) {
PlasmaWebView webview1 = getWebView(code);
if (webview1 != null) {
if (Conf.LOG_ON) Log.d(TAG, "WEBVIEW: Reusing webview " + code + " at " + x0 + "," + y0 + "," + w + "," + h + " vertical " + vertical+", rot="+rot);
webview1.setVerticalScrollBarEnabled(vertical == 1);
webview1.setHorizontalScrollBarEnabled(vertical != 1);
webview1.move(x0, y0, w, h, Funq.this);
if ((alpha>0) && (alpha<100)) webview1.setAlpha((float)alpha/100);
webview1.setRotation((float)rot/10);
webview1.handleTemplateLoad(url, initContent);
return;
}
if (Conf.LOG_ON) Log.d(TAG, "WEBVIEW: Insert webview " + code + " at " + x0 + "," + y0 + "," + w + "," + h + " vertical " + vertical + " url " + url+" alpha "+alpha+", rot="+rot);
webview1 = new PlasmaWebView(Funq.this.getBaseContext(), code, vertical==1, useHardware, jvs, screenId);
if ((alpha>0) && (alpha<100)) webview1.setAlpha((float)alpha/100);
webview1.setRotation((float)rot/10);
RelativeLayout.LayoutParams p=webview1.createLayout(x0, y0, w, h, Funq.this);
layerContainer.addView(webview1, p);
webview1.handleTemplateLoad(url, initContent);
}
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