Analysis and solutions to the problem of Android soft keyboard blocking the input box

This article participated in the 

1024 Programmers Day event. You who are reading are welcome to join.

After reviewing the information many times, I found that this problem cannot be solved perfectly through the front end alone. The main reasons are:

  1. js does not have a relevant interface that can accurately obtain the height of the soft keyboard of an Android phone, so it cannot process that when the soft keyboard pops up, the input box can be pushed up and fit perfectly with the soft keyboard, such as the scene where the input box is at the bottom. At this time, the scroll height of the page is not high enough, so it cannot be scrolled to the end accurately, so that the soft keyboard pushes the input box up and fits it perfectly.
  2. js cannot monitor the soft keyboard closing event. For example, click the fold button to close the soft keyboard. At this time, the focus of the input box is still there, and the blur event of the input box cannot be triggered. Some people say that the resize event will be triggered, but after my testing, the resize event does not Not triggered.

Below is my solution. First, encapsulate a tool function to obtain the width and height of the page view. The code is as follows:

 const getViewSize = ():{ width : number ; height : number } {
     if ( window . innerWidth ) {
       return {
         width : window . innerWidth ,
         height : window . innerHeight
      };
    } else  if ( document . compatMode === 'CSS1Compat' ) {
       return {
         width : document . documentElement . clientWidth ,
         height : document . documentElement . clientHeight
      };
    } else {
       return {
         width : document . body . clientWidth ,
         height : document . body . clientHeight
      };
    }
 }

Secondly, listen to the page resize event, and also listen to the focus and blur events of the input box. as follows:

input. addEventListener ( 'focus' , this . onSetScrollHandler . bind ( this , true ));
input. addEventListener ( 'blur' , this . onSetScrollHandler . bind ( this , false ));
 window . addEventListener ( 'resize' , this . onSetScrollHandler . bind ( this , false ));

Next is the processing of the onSetScrollHandler function. First, we have to consider two situations. The first is that if the scroll height of the page is enough, when scrolling to the input box, the remaining scroll height of the page can be just greater than or equal to the height of the soft keyboard. At this time, it can Fit the input box perfectly. If the scroll height is not enough, we need to increase the height of the root element of the page, and we increase the height of the root element by setting the height of the style. Therefore, when the blur event or resize event is triggered and the height changes, the height of the root element needs to be restored to the original value. Therefore, we need to cache the height of the page first and whether there is a height setting. as follows:

const originHeight = getViewSize (). height ;
 const originBodyHeight = document . body . style . height ;

We can see that onSetScrollHandler adds a Boolean parameter for judgment. Of course, because the resize event will be triggered at this time, we also need to calculate the status separately.

In addition, since this problem occurs on Android phones, in order to prevent the code we added from affecting iOS phones or other devices that implement the soft keyboard pop-up function by default, we need to add environmental judgment. as follows:

type envReturnType = {
     isBrowser : boolean ;
     isServer : boolean ;
     isMobile : boolean ;
     isAndriod : boolean ;
     isIos : boolean ;
     canUseWorkers : boolean ;
     canUseEventListeners : boolean ;
     canUseViewport : boolean ;
}
const getEnv = (): envReturnType => {
     const inBrowser = Boolean ( typeof  window !== 'undefined' && window . document && window . document . createElement );
     const isMobileVailable = ( reg : string | RegExp ): boolean =>  Boolean (navigator. userAgent . match (reg));
     const getEnvObject = [
         isBrowser : inBrowser
         isMobile : isMobileVailable ( /(iPhoneliPod]Androidlios)/i ) Test  Regex ...
         isAndriod : isMobileVailable ( /(android)/i ),
         isIos : isMobileVailable ( /(iPhoneliPodlios)/i ),
         isServer : !inBrowser,
         canUseWorkers : typeof  Worker !==   undefined !
         canUseEventListeners : inBrowser && Boolean ( window . addEventListener )
         canUseViewport : inBrowser && Boolean ( window . screen )
    ];
    return  Object . assign (0bject . values ​​(getEnvObject), getEnvObject);
}

Therefore, the first thing to do inside the onSetScrollHandler function is to determine whether it is an Android phone. as follows:

const  onSetScrollHandler = ( status: boolean ) => {
     const { isAndriod } = getEnv (); 
     if (!isAndriod){
         return ;
    }
    //Following code 
}

Then we get the scrollTop of the body element, and then get the scrollTop of the input box that needs to be pushed up plus its height, which is the distance we want to scroll. Then to ensure compatibility, we need to set document.body.scrollTop and document.documentElement .scrollTop. Of course, on some Android phones, these two settings may not take effect. In this case, you need to call the scrollIntoView method of the element. At the same time, we also need to modify the height of the body element. Since the exact height of the soft keyboard cannot be obtained, at this time we need to modify the height of the body element to 2 screen heights, so that the remaining scroll height can be larger than the height of the soft keyboard. The final version code is as follows:

const  onSetScrollHandler = ( status: boolean ) => {
     const { isAndriod } = getEnv (); 
     if (!isAndriod){
         return ;
    }
    const { height : resizeHeight } = getViewSize (). height ;
     // This is mainly to restore 
    const { scrollTop } = document . body || document . documentElement ;
     if (status || resizeHeight < originHeight){
         // Increase the height of the root element , to facilitate scrolling 
        document . body . style . height = ` ${originHeight + resizeHeight} px` ;
         const top = input. offsetHeight + input. scrollTop ;
         document . body . scrollTop = document . documentElement . scrollTop = top;
         // Ensure compatibility property, you have to call the scrollIntoView method to restore 
           input. scrollIntoView ();
    } else {
          // If the original height does not exist, remove the height property, otherwise reset the height 
        if (!originBodyHeight){
             document . body . style . removeProperty ( 'height' );
        } else {
             document . body . style . height = ` ${originBodyHeight} px` ;
        }
        //Restore scrollTop 
        document . body . scrollTop = document . documentElement . scrollTop = scrollTop;
        // To ensure compatibility, you must call the scrollIntoView method to restore 
       document . body . scrollIntoView ();
    }
}

A small soft keyboard occlusion problem requires writing so much compatibility code. Compatibility is really painful.