I. Devicemotion Event

Before implementing the shake function on the mobile terminal, you need to understand the devicemotion event on the mobile terminal and the knowledge of anti-shake throttling, so that you can understand

Monitor horizontal and vertical screen switching detection

Monitor the time of orientationchange and judge whether the current screen belongs to landscape (90, -90) or portrait (0,180) through the window.orientation attribute.

function setOrientation() {
    let mask = document.querySelector("#mask");
    switch (window.orientation) {
        case 90:
        case -90:
            mask.style.display = "flex";
            break;
        default:
            mask.style.display = "none";
            break;
    }
}
setOrientation();
window.addEventListener("orientationchange",setOrientation)
Copy the code

Mobile phone acceleration detection

Basically listening for devicemotion, and the acceleration is the acceleration of the phone

window.addEventListener("devicemotion".(e) = >{
    const motion = e.acceleration; // Cell phone acceleration
    const {x,y,z} = motion;
    box.innerHTML = `
        x:${x.toFixed(0)}<br/>
        y:${y.toFixed(0)}<br/>
        z:${z.toFixed(0)}
        `;
});
Copy the code

Gravity acceleration of mobile phone

Mobile acceleration of gravity is acceleration is similar to the property name is different, it is accelerationIncludingGravity

window.addEventListener("devicemotion".(e) = >{
    const motion = e.accelerationIncludingGravity; // Mobile acceleration + gravity
    const {x,y,z} = motion;
    box.innerHTML = `
        x:${x.toFixed(0)}<br/>
        y:${y.toFixed(0)}<br/>
        z:${z.toFixed(0)}
        `;
});
Copy the code

The gravity received by the phone

In front of us talked about accelerationIncludingGravity for mobile + gravity acceleration, acceleration is the acceleration, the subtraction can get both gravity

window.addEventListener("devicemotion".(e) = >{
    const motion = e.accelerationIncludingGravity; // Mobile acceleration + gravity
    const motion2 = e.acceleration; / / acceleration
    let {x,y,z} = motion;
    let {x:x2,y:y2,z:z2} = motion2;
    x -= x2;
    y -= y2;
    z -= z2;
    box.innerHTML = `
        x:${x.toFixed(0)}<br/>
        y:${y.toFixed(0)}<br/>
        z:${z.toFixed(0)}
        `;
});
Copy the code

Body feeling operation

Use translateX, translateY

let translateX = 0;
let translateY = 0;     
window.addEventListener("devicemotion".(e) = >{
    const motion = e.accelerationIncludingGravity; // Mobile acceleration + gravity
    const motion2 = e.acceleration; / / acceleration
    let {x,y} = motion;
    let {x:x2,y:y2} = motion2;
    x -= x2;
    y -= y2;
    translateX += x;
    translateY -= y;
    box.style.transform = `translate(${translateX}px,${translateY}px)`;
});
Copy the code

Hit the pit

In the detection process of acceleration, there will be some pits:

  • On Android and IOS, the acceleration is in opposite directions

  • In IOS, to use the acceleration API, the current application must use HTTPS

  • In IOS 12.2, users can turn off action and direction access in their phone Settings

  • In IOS 13 and beyond, when you want to use action and direction access in an app, you need to request permission from the user

  • IOS 13.3 and later, users must manually apply for authorization

For the pit mentioned above, let’s solve it step by step

And since the acceleration is in opposite directions on Android and IOS, we can write a way to tell if it’s Android

function isAndroid() {
    const u = window.navigator.userAgent;
    return u.indexOf("Android") > -1||u.indexOf("Adr") > -1;
}
Copy the code

In IOS 12.2, if the user disables action and direction access in the phone’s Settings, we cannot listen for devicemotion events

We can trigger a delay timer at the beginning of the page rendering to prompt the user to set permissions, and then listen for events to know this delay timer

let timer = setTimeout(() = > {
    alert("Please enable action and direction access, otherwise you will not be able to use this application");
}, 200);
window.addEventListener("devicemotion".() = >{
    clearTimeout(timer); }, {once:true});
Copy the code

IOS 13.3 and later, users must manually apply for authorization. The idea here is that we can’t render the accelerometer directly at mount time. It needs to be executed after a DOM event is triggered

btn.addEventListener("touchend".() = >{
    setMotion((e) = > {
        const motion = e.accelerationIncludingGravity; // Mobile acceleration + gravity
        const motion2 = e.acceleration;
        let { x, y } = motion;
        let { x: x2, y: y2 } = motion2;
        x -= x2;
        y -= y2;
        translateX += x;
        translateY -= y;
        box.style.transform = `translate(${translateX}px,${translateY}px)`;
    });
});
Copy the code

Second, anti-shake and throttling

Image stabilization

Basically, I want the function to only execute once, even if I make multiple calls

Here is a simple version of the anti – shake function package:

Fn is the function that needs to be buffeted, delay is the duration of buffeting, start is whether to execute the function at the beginning, and return returns the function that has been buffeted

There are two main points to deal with:

  • To deal withthisPointing to problems
  • Dealing with parameter issues
function debounce(fn,deley = 200,start = false) {
    let timer = 0;
    let isStart = true;
    return function(. arg) { // The function is buffered
        const _this = this;
        clearTimeout(timer);
        if(isStart){
            start && fn.apply(_this,arg);
            isStart = false;
        }
        timer = setTimeout(() = > {
            (!start)&&fn.apply(_this,arg);
            isStart = true; }, deley); }}Copy the code

The throttle

In short, let the function execute at an acceptable fixed frequency

Throttling and anti – shake very similar, but is the judgment of the time processing way is different

function throttle(fn,deley = 200,start = true) {
    let timer = 0;
    return function(. arg) { // The function is buffered
        const _this = this;
        if(timer){
            return;
        }
        start&&fn.apply(_this,arg);
        timer = setTimeout(() = > {
            (!start)&&fn.apply(_this,arg);
            timer = 0; }, deley); }}Copy the code

Third, the implementation process

The essence of achieving a shake is that there is a large difference between the current acceleration and the previous acceleration

Let’s define two properties to determine

const maxRange = 50; // When the difference of acceleration is greater than this value, the user is considered to have shaken
const minRange = 5; // When the difference of acceleration is less than this value, the user has stopped shaking
Copy the code

Get phone acceleration

const { x, y, z } = e.acceleration;
const range = Math.abs(x - lastX) + Math.abs(y - lastY) + Math.abs(z - lastZ);
Copy the code

There is no difficulty in the function behind, if you do not understand the place, the knowledge involved in the beginning of the article has been involved, here is to do compatible with IOS (multiple versions) and android ID processing, coupled with the anti-shake function, so that the shake function is more efficient

The complete code is shown below

<body>
    <button id="btn">Turn it on and shake it</button>
    <button id="stopBtn">Close and shake</button>
    <div id="info"></div>
    <script src="motion.js"></script>
    <script>
        let btn = document.querySelector("#btn");
        let stopBtn  = document.querySelector("#stopBtn");
        /* setShake ops: {start:fn // what to do when shaking shake shake:fn // what to do when shaking end: fn// what to do when shaking end} */
        function setShake(ops) {
            const { start = () = > { }, shake = () = > { }, end = () = > { } } = ops;
            let lastX = 0,
                lastY = 0,
                lastZ = 0;
            const maxRange = 50;
            const minRange = 5;
            let isShake = false;
            const unMotion = setMotion(throttle((e) = > {
                const { x, y, z } = e.acceleration;
                const range = Math.abs(x - lastX) + Math.abs(y - lastY) + Math.abs(z - lastZ);
                if(range > maxRange && (! isShake)) { start(e); isShake =true;
                } else if (range > maxRange && isShake) {
                    shake(e);
                } else if (range < minRange && isShake) {
                    end(e);
                    isShake = false;
                }
                lastX = x;
                lastY = y;
                lastZ = z;
            }));
            return unMotion; // Cancel shake listener
        }
        let unShake;
        btn.addEventListener("touchend".() = > { 
            unShake = setShake({
                start:() = >{
                    info.innerHTML += "Start shaking it 

"
; }, shake:() = >{ info.innerHTML += "Shake it up

"
; }, end: () = >{ info.innerHTML += "Shake it off

"
; }})}); stopBtn.addEventListener("touchend".() = >{ if(unShake){ unShake(); }})
</script> </body> Copy the code

motion.js

// In IOS 12, determine whether the user has disabled action and direction access
{
  let timer = setTimeout(() = > {
    alert("Please enable action and direction access, otherwise you will not be able to use this application");
  }, 200);
  window.addEventListener("devicemotion".() = > {
    clearTimeout(timer);
  }, { once: true });
}
// Check whether the current system is Android
function isAndroid() {
  const u = window.navigator.userAgent;
  return u.indexOf("Android") > -1 || u.indexOf("Adr") > -1;
}
/* setMotion set to listen for acceleration changes to handle cb acceleration changes to do after the return cancel event registration */
function setMotion(cb) {
  let fn = (e) = > {
    if (isAndroid()) { // Handle the android inversion problem
      e.acceleration.x = -e.acceleration.x;
      e.acceleration.y = -e.acceleration.y;
      e.acceleration.z = -e.acceleration.z;
      e.accelerationIncludingGravity.x = -e.accelerationIncludingGravity.x;
      e.accelerationIncludingGravity.y = -e.accelerationIncludingGravity.y;
      e.accelerationIncludingGravity.z = -e.accelerationIncludingGravity.z;
    }
    cb(e);
  };
  // Differentiate between IOS 13 and pre-ios
  if (typeof DeviceMotionEvent.requestPermission === "function") { // IOS 13 and later
    DeviceMotionEvent.requestPermission()
      .then(permissionState= > {
        if (permissionState === 'granted') {
          // Permission allowed
          window.addEventListener("devicemotion", fn);
        }
      }).catch(() = > {
        alert("Please enable authorization or you cannot use this application."); })}else { // Before Android and IOS 13
    window.addEventListener("devicemotion", fn)
  }
  return () = >{
    window.removeEventListener("devicemotion",fn); }}Copy the code

Fourth, concluding remarks

If you find this article helpful, you can reach out your small hands and give this article a thumbs up

I am a new front end the way of new, with the attitude of learning, with meet you companion’s point of view, to share his knowledge, in addition to let oneself more consolidation of knowledge, also hope everyone can learn through the article I wrote a little knowledge, if any error is knowledge content, can be in the comments section below or the public, tell me, I will immediately change

Finally, I also created a “front-end harvester” public account, I hope you can pay attention to a wave of articles are hair loss