A few years ago, I needed to use the function of shake in a program. Suddenly, I found that the devicemotion event did not work in IOS. After checking a lot of data, I finally solved the problem. What is DeviceMotion? How to realize the function of shake, and what kind of problems need to be dealt with in each device, we will elaborate on this matter through an article.

What exactly is DeviceMotion?

There is a special event in the Window object, Devicemotion, which can be used to listen for information such as changes in the acceleration of the device. In the Event object (DeviceMotionEvent), there is a Acceleration object, which can be used to obtain the acceleration information of the device. The demo code is as follows:

<script> window.addEventListener("devicemotion",(e)=>{ let motion = e.acceleration; let {x,y,z} = motion; // x,y,z are the accelerations in the three directions}); </script>Copy the code

Acceleration in the acceleration object, x, y and z represent acceleration in three directions, namely length, width and thickness, respectively, as shown in the figure below:

Now that we know how to use Acceleration, can we start using it? Not so fast. This is our trip to the pits. Moving on, let’s talk more about what’s in store for Acceleration when it comes to using the phone.

DeviceMotion A pit trip

So let’s start with the simplest thing, and with Android, acceleration, when you take the value in the X,Y and Z directions, it’s the opposite of IOS. What does that mean? For example, now I’m using an Android phone, and if I move to the left, I get a speed of 10 on the x axis, then I get a speed of -10 on the corresponding IOS axis. The same is true for other Y and Z axes.

< script > / / judge whether IOS devices, on behalf of IOS, true or false represents the IOS function getIos () {let u = window. The navigator. UserAgent; return !! u.match(/\(i[^;] +; ( U;) ? CPU.+Mac OS X/); } window.addEventListener("devicemotion",(e)=>{ let motion = e.acceleration; let {x,y,z} = motion; if(! getIos()){ x = -x; y = -y; z = -z; }}); </script>Copy the code

After climbing the first pit, we continue to explore the pit, the above code using IOS friends may not see the effect, this is why, version by version.

HTTPS requirements in IOS

In the first version of IOS, it worked just as well as Android, but in some version after IOS 10 (forgive me if I can’t remember), IOS has a security restriction that requires you to use HTTPS to use devicemotion events. Okay, well, if it’s official, there’s nothing we can do about it, right? Go ahead.

Finally in IOS protocol configuration, the problem should be able to solve it, young you think this problem is too simple.

Security restrictions after IOS 12.2

After IOS 12.2, Apple’s development team added a new twist: users can turn off “Action and Direction Access” in the phone’s Settings, as shown below. If the user turns off, then we have to “hehe”. By the way, there is no effective way for developers to find out directly whether the user has turned off action and Direction access.

How to deal with this problem? I have made a trick to share with you, the code is as follows:

<sccript> if(! Window.devicemotionevent){alert(" Your device does not support DeviceMotion"); } else {let timer = setTimeout(function(){alert(' Please enable action and direction permissions on your device, otherwise you will not be able to use this app '); }, 100); window.addEventListener("devicemotion",(e)=>{ clearTimeout(timer); },{once:true}); } </script>Copy the code

In the first step, we use window.DeviceMotionEvent to exclude devices that do not support acceleration, such as computers, which will have window.DeviceMotionEvent if they do.

Step 2, if the device supports window.DeviceMotionEvent, start an event monitoring (note that acceleration acquisition is particularly sensitive even if we think our phone is still, there will be some acceleration for it, so devicemotion will be triggered every now and then with very small intervals). If the event is not executed, the user has disabled action and direction permissions.

Now that we’ve fixed the issue of users being able to turn off permissions after IOS 12.2, the issue of getting acceleration should be resolved. But will apple’s development team just let us go? Don’t be so naive!

IOS 13 user permission request

With IOS 13, the Apple dev team started working on it again, especially now with 13.3.

What’s going on? Let’s look at the following code:

<script>
    DeviceMotionEvent.requestPermission()
            .then(permissionState => {
                if (permissionState === 'granted') {
                    window.addEventListener('devicemotion', () => {})
                }
            })
            .catch((err)=>{

            });
</script>
Copy the code

This is after IOS 13, apple added a method for me to get user permissions, and once we get user permissions, we can detect acceleration, but notice that this method didn’t exist until after IOS 13, so if I wrote this before IOS 13, DeviceMotionEvent does not generate an error without a requestPermission method. Note also that in the latest IOS 13.3, this method requires the user to trigger the retrieval, so let’s see what the final version will look like.

< script > / / judge whether ios devices function getIos () {var u = window. The navigator. UserAgent. return !! u.match(/\(i[^;] +; ( U;) ? CPU.+Mac OS X/); } /* setDeviceMotion(cb,errCb){/ function setDeviceMotion(cb,errCb){  if(! Window.devicemotionevent){errCb(" DeviceMotion not supported "); return; } if (typeof DeviceMotionEvent.requestPermission === 'function') { // IOS 13 DeviceMotionEvent.requestPermission() .then(permissionState => { if (permissionState === 'granted') { window.addEventListener('devicemotion',cb); }}). Catch ((err)=>{errCb(" user does not have permission "); }); } else {let timer = setTimeout(function(){errCb(" user has not enabled permission "); }, 1000); window.addEventListener("devicemotion",(e)=>{ clearTimeout(timer); },{once:true}); window.addEventListener("devicemotion",cb); } } document.ontouchstart=function(){ setDeviceMotion((e)=>{ let motion = e.acceleration; let {x,y,z} = motion; if(! getIos()){ x = -x; y = -y; z = -z; } },(errMessage)=>{ alert(errMessage); }); } </script>Copy the code

At this point, we finally have a compatible acceleration listening process, and then we can look at the implementation of the related functions of the shake. This step is relatively easy.

Shake to achieve

Once you can get the acceleration of the phone, the shake function is relatively simple. When we shake the phone, the phone itself generates an acceleration. We can intercept the comparison of the two accelerations of the mobile phone, and there will be a difference in the middle. When the difference is greater than a certain range, it can be considered that the user has shaken the operation.

There is a problem to be noted here. When detecting the user shaking the phone, we cannot predict which axis the user is shaking, so we will detect all three axes, and the specific code is as follows:

<script> function shake(cb){ const maxRange = 60; // Let lastX=0, lastY =0, lastZ =0; window.addEventListener("devicemotion",(e)=>{ let motion = e.acceleration; let {x,y,z} = motion; let range = Math.abs(x - lastX) + Math.abs(y - lastY) + Math.abs(z - lastZ); If (range > maxRange){// the user shook alert(" you shook "); } lastX = x; lastY = y; lastZ = z; }); } </script>Copy the code

Here we have a basic shake function, but there are still some incomplete functions: 1. 2. Devicemotion Execution interval is very small, which can reach several millimeters. However, in terms of performance, we do not need such a small execution interval. Basically no one can shake our phones within a few millimeters. 3. The compatibility processing of Devicemotion mentioned above has not been added. Let’s deal with these problems as a whole, and the code is as follows:

<button ID ="startBtn"> </button> <button ID ="closeBtn"> */ function setDeviceMotion(cb,errCb){if(! Window.devicemotionevent){errCb(" DeviceMotion not supported "); return; } if (typeof DeviceMotionEvent.requestPermission === 'function') { // IOS 13 DeviceMotionEvent.requestPermission() .then(permissionState => { if (permissionState === 'granted') { window.addEventListener('devicemotion',cb); }}). Catch ((err)=>{errCb(" user does not have permission "); }); } else {let timer = setTimeout(function(){errCb(" user has not enabled permission "); }, 1000); window.addEventListener("devicemotion",(e)=>{ clearTimeout(timer); },{once:true}); window.addEventListener("devicemotion",cb); }} /* Throttling function parameters: fn to throttling function interval throttling interval start whether to execute at the start of throttling (true at the start, false at the end) */ function throttle(fn,interval=200,start = true){if(typeof fn! == "function"){return console.error(" please pass a function"); } let timer = 0; return function(... arg){ let _this = this; if(timer){ return ; } start&&fn.apply(_this,arg); timer = setTimeout(() => { (! start)&&fn.apply(_this,arg); timer = 0; }, interval); }} /* addShake addShake function parameter: cbShake type fn what to do after the user has done a shake return value: */ function addShake(cbShake){const maxRange = 60; Const minRange = 10; // When the difference between the user's accelerations is greater than this const minRange = 10; // When the user's acceleration difference is less than this range, determine the user to stop shaking the phone let isShake = false; Let lastX=0, lastY =0, lastZ =0; function toShake(e){ let motion = e.acceleration; let {x,y,z} = motion; let range = Math.abs(x - lastX) + Math.abs(y - lastY) + Math.abs(z - lastZ); If (range > maxRange){// isShake = true; } if(range < minRange&&isShake){// Stop to shake cbShake(e); isShake = false; } lastX = x; lastY = y; lastZ = z; } if(! ShakeEvent = []; // Create a shakeEvent that stores all shakeEvent handlers, so that you can cancel window.shakeEvent = []; } toShake = throttle(toShake); window.shakeEvent.push(toShake); setDeviceMotion(toShake,(errMessage)=>{ alert(errMessage) }) return window.shakeEvent.length - 1; */ function remveShake(shakeIndex){function remveShake(shakeIndex){ window.removeEventListener("devicemotion",window.shakeEvent[shakeIndex]); } let startBtn = document.querySelector("#startBtn"); let closeBtn = document.querySelector("#closeBtn"); let isStartShake = false; let shakeIndex; / / again IOS 13.3 requires the user to trigger, can open again shake startBtn. AddEventListener (" touchend ", () = > {the if (isStartShake) return; isStartShake = true; ShakeIndex = addShake (() = > {alert (" you have to shake ")})}); closeBtn.addEventListener("touchend",()=>{ if(! isStartShake)return; isStartShake = false; remveShake(shakeIndex); }); } </script>Copy the code

At this point, we finally have a fairly complete shake function handler. Of course, the code can continue to handle, for example, we can define shake as an event and so on, but this is not relevant to today’s topic, SO I won’t repeat it.


Previous recommendations:

Want to use Vuejs to break the 20K must-have hot interview questions

Use Vue +node to build a front-end anomaly monitoring system


Move a small hand to point a praise, with xiaobian together brave front circle, welcome to leave a comment, discuss yo ~