移动端H5页面关于软键盘的一些踩坑记录

最近一个项目需要做一个留言的功能,需求很简单,点击留言按钮底部弹出留言框,然后发送留言给后台就行了,还有一个就是页面要实现复制功能。讨论需求的时候我预想到了手机上软键盘的弹出可能会有一些bug,不过需求比较急,就想着先做完再来改呗。很快做完之后,在电脑上测了没毛病,提测之后,bug刷刷的就来了:

1、手机ios或者Android微信,点击留言弹出输入框,点击完成或者收起按钮,或者安卓手机点击自带返回键,需要完整隐藏输入框;

2、手机ios或者Android微信,点击留言弹出输入框,滑动屏幕都要完整隐藏输入框;

3、IOS手机的复制按钮,点击后屏幕底部有弹框闪出;

4、手机ios或者Android微信,PC微信H5页面在页面下半部分点击复制,页面会跳转到顶部;

5、手机ios,点击留言,输入框会抖动;

6、手机ios点击留言弹出输入框后,此时点击屏幕关闭了输入框,再次点击留言按钮没有影响,直接界面卡死;

可以看到除了3、4是复制相关的,其他都是软键盘相关的,而这些还只是众多bug中有代表性的。。。开始改吧。

先看3、4的复制bug,一说到复制第一反应就是document.execCommand("Copy"),毕竟不需要任何其他组件,简单粗暴,可是在移动端ios出现了弹框瞬间闪出的bug,代码如下:

const btn = document.querySelector('#btn');
btn.addEventListener('click',() => {
    const input = document.createElement('input');
    document.body.appendChild(input);
    input.setAttribute('value', '听说你想复制我');
    input.select();
    if (document.execCommand('copy')) {
        document.execCommand('copy');
        console.log('复制成功');
    }
 document.body.removeChild(input);
})

在Chrome下调试的时候,这个方法时完美运行的。然后到了移动端调试的时候,坑就出来了。

对,没错,就是你,ios。。。

1、点击复制时屏幕下方会出现白屏抖动,仔细看是拉起键盘又瞬间收起

知道了抖动是由于什么产生的就比较好解决了。既然是拉起键盘,那就是聚焦到了输入域,那只要让输入域不可输入就好了,在代码中添加 input.setAttribute('readonly', 'readonly'); 使这个 是只读的,就不会拉起键盘了。

2、无法复制

这个问题是由于 input.select() 在ios下并没有选中全部内容,我们需要使用另一个方法来选中内容,这个方法就是 input.setSelectionRange(0, input.value.length);。

修改后的代码如下:

const btn = document.querySelector('#btn');
btn.addEventListener('click',() => {
    const input = document.createElement('input');
 input.setAttribute('readonly', 'readonly');
 input.setAttribute('value', 'hello world');
 document.body.appendChild(input);
    input.setSelectionRange(0, 9999);
    if (document.execCommand('copy')) {
        document.execCommand('copy');
        console.log('复制成功');
    }
 document.body.removeChild(input);
})

问题又来了,用了setSelectionRange这个方法,在pc端都不能复制了,然后我还是把它改成了input.select(),pc和移动端都能复制,不过ios里面的那个键盘突然闪出问题并不会因为设置了readonly而解决。而且关键是:手机ios或者Android微信,PC微信H5页面在页面下半部分点击复制,页面会跳转到顶部,所以这个execCommand方法并不好用,既然这样那我还是用个组件呗,直接就选择了clipboard.js,官网直接选择了通过属性复制,完美解决一切复制的bug,还是组件真香。

好了,接着看看关于软键盘的几个bug:

1、手机ios或者Android微信,点击留言弹出输入框,点击完成或者收起按钮,或者安卓手机点击自带返回键,需要完整隐藏输入框;

2、手机ios或者Android微信,点击留言弹出输入框,滑动屏幕都要完整隐藏输入框;

5、手机ios,点击留言,输入框会抖动;

6、手机ios点击留言弹出输入框后,此时点击屏幕关闭了输入框,再次点击留言按钮没有影响,直接界面卡死;

由于前面用了组件真香,本来一个下面固定的弹出框也准备自己写的,后来想到fixed在ios的各种坑想了还是用弹出框界用的比较多的layer吧,页面的一些toast提示也可以用layer.msg(),瞬间写好弹框:

    //点击留言
    $(document).on('click','.leaveMsgBtn',function(){
      layer.open({
        type: 1, //页面层
        content: $('#leaveMsgPop'),
        area: ['100%','auto'],
        title: false,//不显示标题
        closeBtn: 0,//不显示右上角关闭按钮
        scrollbar: false,//页面滚动条隐藏
        shadeClose:true,//点击遮罩关闭弹出层
        offset: 'b',//从底部弹出
        success: function(layero, index){
          //内容显示
          $('#leaveMsgPop').css('opacity','1');
          //隐藏底部按钮
          $('.footer').hide();
          $('#msgTextarea',layero).focus(); 
          $('#msgTextarea',layero).on('input',function(){
            if($(this).val()!=''){
              $(this).parent().find('.sendMsg').removeClass('disable');
              $(this).parent().find('.sendMsg').removeAttr('disabled'); 
            }else{
              $(this).parent().find('.sendMsg').addClass('disable');
              $(this).parent().find('.sendMsg').attr("disabled","disabled"); 
            }
          });
        },
        end:function(){ 
          //内容隐藏
          $('#leaveMsgPop').css('opacity','0');
          //显示底部按钮
          $('.footer').show();
        } 
      });
    });

html代码:


    

效果图:

移动端H5页面关于软键盘的一些踩坑记录_第1张图片

可以看到就是一个极简单的弹出框,textarea自动focus,在移动端就可以弹出软键盘。

关于这个:1、手机ios或者Android微信,点击留言弹出输入框,点击完成或者收起按钮,或者安卓手机点击自带返回键,需要完整隐藏输入框;

移动端H5页面关于软键盘的一些踩坑记录_第2张图片

要求点击键盘右上角的那个完成可以收起弹出框,键盘上按键能通过keycode监听到,这个完成按钮都越出键盘外不在五行中了,咋监听呢,通过其他办法咯,发现点击完成键盘就会收起来,那么就去监听键盘收起的状态吧,嗯,ios可以通过focusin和focusout来监听,focusin和focusout支持冒泡,对应focus和blur, 使用focusin和focusout的原因是focusin和focusout可以冒泡,focus和blur不会冒泡,这样就可以使用事件代理,处理多个输入框存在的情况。

在android中,点击键盘上的收起按钮,键盘虽然收起了,输入框仍然处于焦点状态,并没有触发focusout事件。经实践,发现一种变通的方法。通过比较window resize后的clientHeight与最初进来页面时的clientHeight进行对比,如果小于最初的值,那么就可以认为是键盘弹出,否则,认为键盘收起。在android中键盘弹出和收起会改变window的高度,因此监听window的resize。

那么修改后的代码就是:

success: function(layero, index){
          //内容显示
          $('#leaveMsgPop').css('opacity','1');
          //隐藏底部按钮
          $('.footer').hide();
          $('#msgTextarea',layero).focus(); 
          $('#msgTextarea',layero).on('input',function(){
            if($(this).val()!=''){
              $(this).parent().find('.sendMsg').removeClass('disable');
              $(this).parent().find('.sendMsg').removeAttr('disabled'); 
            }else{
              $(this).parent().find('.sendMsg').addClass('disable');
              $(this).parent().find('.sendMsg').attr("disabled","disabled"); 
            }
          });
          //ios
          if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
            $(document).on('focusin', function () {
             //软键盘弹出的事件处理
            });
            $(document).on('focusout', function () {
             //软键盘收起的事件处理
              if($('#msgTextarea',layero).val()==''){
                layer.close(index);
              }
            });
          }else if (/(Android)/i.test(navigator.userAgent)) {//安卓
            var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
            $(window).on('resize', function () {
                var nowClientHeight = document.documentElement.clientHeight || document.body.clientHeight;
                if (clientHeight > nowClientHeight) {
                  //键盘弹出的事件处理
                }
                else {
                  //键盘收起的事件处理
                  if($('#msgTextarea',layero).val()==''){
                    layer.close(index);
                  }
                }
            });
          }
        },

这里还加了个判断,如果你没输入任何内容点击完成那么弹出框就关闭,如果有内容的话就只是收起键盘了,这样第一个bug算是解决了。

2、手机ios或者Android微信,点击留言弹出输入框,滑动屏幕都要完整隐藏输入框;

这个滑动屏幕关闭输入框主要是因为滚动穿透引起的,弹出框出来body就不让它滚动,很容易就想到了给body一个fixed,不过有个问题,就是原来页面滑到了下面,然后出现弹框,如果只是给body一个fixed的话,可以看到遮罩后面的页面一下子滚动到页头,这样的用户体验肯定是不允许的,那就需要保留之前滚到页面某个部分的位置咯。代码如下:

    //禁止滚动
    var ModalHelper = (function(bodyCls) {
       var scrollTop; // 在闭包中定义一个用来保存滚动位置的变量
        return {
          afterOpen: function() { //弹出之后记录保存滚动位置,并且给body添加.modal-open
            scrollTop = document.scrollingElement.scrollTop;
            document.body.classList.add(bodyCls);
            document.body.style.top = -scrollTop + 'px';
          },
          beforeClose: function() { //关闭时将.modal-open移除并还原之前保存滚动位置
            document.body.classList.remove(bodyCls);
            document.scrollingElement.scrollTop = scrollTop;
          }
        };
      })('modal-open');

然后,css的代码是:

body.modal-open {
    position: fixed;
    width: 100%;
}

这样就保证遮罩下面的内容不让用户去穿透滚动了。

5、手机ios,点击留言,输入框会抖动;

这个抖动的话完全是我之前自己为了有个从下滑入动画效果,而layer里面正好有anim参数,结果设置了之后,ios键盘出来直接把弹出框往上顶了两下,就出现了所谓的抖动,得,动画去掉吧。

6、手机ios点击留言弹出输入框后,此时点击屏幕关闭了输入框,再次点击留言按钮没有影响,直接界面卡死;

最后这个问题就找了很久,主要是没找到卡死的原因,状况就是我输入框输入了内容,然后点击完成按钮,键盘会收起来,这时候我再点输入框,会发现完全没反应,点击发送按钮也没反应,这就造成了卡死的假象,然后我在测的时候在页面上随便点,发现有时候键盘会出来,有时候会关闭弹出框。多试几次之后,发现点击页面中间,键盘会出来,突然想到一开始键盘出来的时候貌似就是把弹出框顶到了页面中间的位置,问题找到了就好解决了。

这就是ios的一个bug,在弹出框有输入域聚焦的时候,键盘弹出来,然后弹出框会在页面中间位置,这个时候收起了键盘,其实弹出框的输入域的光标还在之前的但是看不见的位置,也就是页面中间,那么就需要在键盘收起来让页面的scrollTop也发生变化了。解决代码如下:

//键盘收起时需要body页面scrollTop改变
              setTimeout(function () {
                var scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0;
                window.scrollTo(0, Math.max(scrollHeight - 1, 0));
              },100);

scrollHeight记载的就是键盘出来前弹出框的scrollTop,键盘出来之后会把弹出框顶上去,那么scrollTop就会发生变化,在收起键盘的时候我们需要scrollTop回到原来的值。

可以看出来这些bug主要都是集中在ios中,安卓对fixed的体验会好很多,好了,最后贴上这段看似简单需求弹出框的完整js代码以做记录:

    //禁止滚动
    var ModalHelper = (function(bodyCls) {
       var scrollTop; // 在闭包中定义一个用来保存滚动位置的变量
        return {
          afterOpen: function() { //弹出之后记录保存滚动位置,并且给body添加.modal-open
            scrollTop = document.scrollingElement.scrollTop;
            document.body.classList.add(bodyCls);
            document.body.style.top = -scrollTop + 'px';
          },
          beforeClose: function() { //关闭时将.modal-open移除并还原之前保存滚动位置
            document.body.classList.remove(bodyCls);
            document.scrollingElement.scrollTop = scrollTop;
          }
        };
    })('modal-open');

    //点击留言
    $(document).on('click','.leaveMsgBtn',function(){
      layer.open({
        type: 1, //页面层
        content: $('#leaveMsgPop'),
        area: ['100%','auto'],
        title: false,//不显示标题
        closeBtn: 0,//不显示右上角关闭按钮
        scrollbar: false,//页面滚动条隐藏
        shadeClose:true,//点击遮罩关闭弹出层
        offset: 'b',//从底部弹出
        success: function(layero, index){
          //内容显示
          $('#leaveMsgPop').css('opacity','1');
          //隐藏底部按钮
          $('.footer').hide();
          $('#msgTextarea',layero).focus(); 
          $('#msgTextarea',layero).on('input',function(){
            if($(this).val()!=''){
              $(this).parent().find('.sendMsg').removeClass('disable');
              $(this).parent().find('.sendMsg').removeAttr('disabled'); 
            }else{
              $(this).parent().find('.sendMsg').addClass('disable');
              $(this).parent().find('.sendMsg').attr("disabled","disabled"); 
            }
          });
          //ios
          if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
            $(document).on('focusin', function () {
             //软键盘弹出的事件处理
              //禁止滚动
              ModalHelper.afterOpen();
            });
            $(document).on('focusout', function () {
             //软键盘收起的事件处理
              if($('#msgTextarea',layero).val()==''){
                layer.close(index);
                //可以滚动
                ModalHelper.beforeClose();
              }
              //键盘收起时需要body页面scrollTop改变
              setTimeout(function () {
                var scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0;
                window.scrollTo(0, Math.max(scrollHeight - 1, 0));
              },100);
            });
          }else if (/(Android)/i.test(navigator.userAgent)) {//安卓
            var clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
            $(window).on('resize', function () {
                var nowClientHeight = document.documentElement.clientHeight || document.body.clientHeight;
                if (clientHeight > nowClientHeight) {
                  //键盘弹出的事件处理
                  //禁止滚动
                  ModalHelper.afterOpen();
                }
                else {
                  //键盘收起的事件处理
                  if($('#msgTextarea',layero).val()==''){
                    layer.close(index);
                    //可以滚动
                    ModalHelper.beforeClose();
                  }
                }
            });
          }
          //禁止滚动
          ModalHelper.afterOpen();
        },
        end:function(){ 
          //内容隐藏
          $('#leaveMsgPop').css('opacity','0');
          //显示底部按钮
          $('.footer').show();
          //可以滚动
          ModalHelper.beforeClose();
        } 
      });
    });

 

你可能感兴趣的:(原创)