移动webapp的那些令你头疼的事

bug持续更新中...

测试浏览器

Chrome: 61.0.3163.73javascript

Safari: 10.0(IOS 10.3.3)css

Github: webapp-bugshtml

1. IOS overflow: scroll 全屏滚动出界

1.1 出现场景

滑动到最顶部(最底部)的时候,停下,而后继续向上滑动(向下滑动)java

这里写图片描述

1.2 解决方案

  • 手动设置滑到边界时的scrollTopscrollFix

当快滑到上边界或者下边界的值时,手动设置scrollTop与达到边界时相差一像素(上边界时:scrollTop = 1, 下边界时:scrollTop = elem.scrollHeight - elem.offsetHeight - 1),这样就不会触发出界的极限条件。node

具体实现:jquery

var ScrollFix = function(elem) {
    // Variables to track inputs
    var startY, startTopScroll;
    
    elem = elem || document.querySelector(elem);
    
    // If there is no element, then do nothing  
    if(!elem)
        return;

    // Handle the start of interactions
    elem.addEventListener('touchstart', function(event){
        startY = event.touches[0].pageY;
        startTopScroll = elem.scrollTop;
        
        if(startTopScroll <= 0)
            elem.scrollTop = 1;

        if(startTopScroll + elem.offsetHeight >= elem.scrollHeight)
            elem.scrollTop = elem.scrollHeight - elem.offsetHeight - 1;
    }, false);
};

注:1. 这个方法只能部分防止,在某些时候仍是会触发出界。2. 有说在全局滚动下和局部滚动下会有差别,可是就我测试的状况来讲,差别并非特别大。多是版本过高的缘由,具体结论还待测试更多机型。android

  • 头部由static变为fixed(测试效果貌似更好)
.toolbar {
    -webkit-box-sizing: border-box;
    padding: 1em;
    background: #222;
    color: #fff;
    font-weight: bold;
    text-align: center;
    height: 50px;
  
    /* 添加fixed头部 */
    position: fixed;
    top: 0;
    z-index: 1;
    width: 100%;
}

r-bounce

2. IOS经过脚本使输入框聚焦,没法弹出键盘

2.1 出现场景

看以下代码:ios

// html
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
<button id="submitBtn" class="btn btn-default">Sign in</button>
  
// script
var inputEmail3 = document.querySelector('#inputEmail3');
var submitBtn = document.querySelector('#submitBtn');

// way1
setTimeout(() => {
    inputEmail3.focus();
}, 2000);

这种方式下:在IOS上输入框聚焦确没有办法弹出键盘git

focus

2.2 解决方案

爬墙爬到这么一个issue,3楼eddiemonge老哥说到了,在IOS下除非用户手动触发了输入框的focus事件,才会触发键盘,至于设置定时器也是无论用的;可是,手动点击一个按钮,在按钮的操做中再来执行focus事件却是管用的。他还给出了一个http://jsbin.com/inunis/8/edit?html,js,outputgithub

// 这样是能够弹出键盘的
submitBtn.onclick = function(e) {
    e.preventDefault();
    inputEmail3.focus();
}

r-focus

3. IOS光标不跟随输入框移动

3.1 艰辛历程

我为何会关注这个问题:那是由于我**(这里省略一万个草泥马)也遇到了这个问题呀,容我细细说来。

我有一个登陆页面,在聚焦以后须要往上弹一下,android上正常,而后IOS上还同时引出了一个BUG:输入框上去了,可是光标却在下面闪。怎么办呢?固然是靠想办法解决呀,后来我就想在输入框上贴一层蒙版,点击了以后消失,同时在点击操做中,等到动画结束以后再执行输入框的focus,行不行呢?好期待。。。

caret

html代码是这样的:

// ... 这里省略若干
<div class="col-sm-10">
    <input type="email" class="form-control" id="inputEmail3" placeholder="Email" />
    <div class="input-overlay" id="overlay"></div>
</div>

样式:

.input-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(255, 0, 0, 0.3);
    z-index: 2;
}

脚本:

overlay.addEventListener('touchstart', function(e) {
    e.preventDefault();
    testForm.style.transform = 'translate3d(0, 0, 0)';
            
    overlay.style.display = 'none';
    overlay.style.zIndex = -1;

    setTimeout(function() {
        inputEmail3.focus();
    }, 200);
});

正所谓想象是好样的,可是实际行使起来却不是那么使人满意。是的,毫无效果。后来我想,是否是能够模拟一个事件,再触发一次点击,而后代码是这样的:

function mockEvent(fn) {
    var createDiv = document.createElement('div');
    createDiv.style.display = 'none';
    document.body.appendChild(createDiv);

    // 未作兼容
    var e = document.createEvent('MouseEvent'); 
    e.initEvent('xx', true, true); 

    createDiv.addEventListener('xx', function() {
      fn && fn();
      createDiv.remove();
    });

    createDiv.dispatchEvent(e); 
}

overlay.addEventListener('click', function(e) {
    // ...

    setTimeout(function() {
        mockEvent(function() {
            inputEmail3.focus();
        });
    }, 200);
});

答案依然是:不行。而后我想,是否是setTimeout的缘由,只要存在延迟的状况下就不行。结果我去这么试了一下,将以前的按钮直接点击方式改成200ms以后执行focus

submitBtn.onclick = function(e) {
    e.preventDefault();
    setTimeout(function() {
        inputEmail3.focus();
    }, 200); 
}

果真,只要设置延迟就不起效果了。顿时忽然想到移动端点透事件貌似有个300ms延迟执行。虽然点透事件在移动端会被处理掉,然而我只是想验证一下个人猜测。而后我又这么写:

// html
<div class="col-sm-10">
    <input type="email" class="form-control" id="inputEmail3" placeholder="Email" />
    <div class="input-overlay" id="overlay"></div>
    <a class="input-link" href="javascript:;" id="link">link</a>
</div>

overlay下面放一个link,而后在overlay上绑定touchstart事件,在link上绑定click事件。这样在上层的遮罩去掉以后,就能够300ms后执行下面的link层中的事情,那么也算是用户真正地触发的点击行为,美滋滋。结果我在代码中加了这个东西:

overlay.addEventListener('touchstart', function(e) {
    // ...
});

link.addEventListener('click', function() {
    link.style.display = 'none';
    link.style.zIndex = -1;

    inputEmail3.focus();
});

尼玛呀,仍是不行,绝望了。

然而。。。

天生不死心,又去爬墙呀。输入inupt move while cursor to stay where it was。下面讲解决方案。

3.2 解决方案

我找到了这样的一个issue。在其中的描述是:他的内容中有一输入框,而后focus,当滑动内容时,光标不跟随移动,而在此输入的时候,光标又会回到输入框中。状况应该和我相似。

robby says

I also have this problem.
It is apparently related to the use of css transforms.

I have fixed it with this hack workaround that forces redrawing as you scroll to eliminte this issue:
CSS:

input {
  text-shadow: rgba(0,0,0,0) 0px 0px 0px;
}
input.force-redraw {
  text-shadow: none;
}

JS:

myScroll = new iScroll('wrapper', { 
  onScrollMove: function() {
    $('input').toggleClass('force-redraw');
  }
});

是的,有木有很激动。因而我这样写:

// css
input {
    text-shadow: rgba(0,0,0,0) 0px 0px 0px;
}
input.force-redraw {
    text-shadow: none;
}

// javascript
inputEmail3.addEventListener('focus', function() {
    testForm.style.transform = 'translate3d(0, 0, 0)';
    setTimeout(() => {
      inputEmail3.className = 'form-control force-redraw';
    }, 300);
});

效果大致上实现了,可是仍然有瑕疵。就是必须设置延迟300ms以上,否则,光标重绘不正常,并且光标有明显的移动过程。因此若是童鞋们若是发现有什么更好的办法,还望不吝赐教。

r-caret

另外,若是一个页面中有输入框,聚焦以后,滑动过程当中在IOS上可能会出现不流畅的问题,其实能够这么作:监测页面的touchmove事件,若是当前页面存在着输入框被active,那么直接让其blur,保证滑动过程当中没有输入框被聚焦。(不过以个人测试状况来看,在chromesafari上滑动的时候输入框再也不被激活,相似在PC端滑动的时候采用了蒙版或者points-event: none;的效果)

var thisFocus;
var content = document.querySelector('#content');

var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
    var input = inputs[i];
    input.onfocus = focused;
}

function focused(e) {
    thisFocus = e.target;
}

content.addEventListener('touchmove', function() {
    if (thisFocus) {
        thisFocus.blur();
        thisFocus = null;
    }
});

4. IOS输入框聚焦后页面总体上移,头部顶出

4.1 出现场景

页面中有fixed头部,输入框,而且输入框靠下时,当输入框focus的时候,会将整个页面上移,致使头部被顶出去。fixed position div freezes on page

header

4.2 解决方案

缘由大体是:ios自带的输入居中效果,而带有fixed头部在页面被顶上去的同时没有从新计算位置,致使显示错误。那么能够具体分这几步来解决:

  • 没有focus的时候采用fixed固定头部
  • 不要让用户进行缩放
  • 当输入框focus时,采用绝对定位头部,同时使用window.pageYOffset来计算滑动的距离,设置头部的top
  • 滑动的时候,监听scroll方法,动态设置头部top
  • 失去焦点的时候,从新将头部恢复至fixed定位
  • 滑动时,若是头部结构太复杂,可能会引发固定不流畅(会跟着滚动)

代码请往这里看:

var isFocused, focusedResizing, ticking = false;

window.tryfix = function() {
    var inputs = document.getElementsByTagName('input');
    for (var i = 0; i < inputs.length; i++) {
        input = inputs[i];
        input.onfocus = focused;
        input.onblur = blured;
    }
    window.onscroll = onScroll;
}

function focused(event) {
    isFocused = true;
    scrolled();
}

function blured(event) {
    isFocused = false;
    var headStyle = document.getElementById('header').style;
    var footStyle = document.getElementById('footer').style;
    if (focusedResizing) {
        focusedResizing = false;
        headStyle.position = 'fixed';
        headStyle.top = 0;
        footStyle.display = 'block';
    }
}

function onScroll() {
    if (!ticking) {
        requestAnimationFrame(scrolled);
        ticking = true;
    }
}

function scrolled() {
    var headStyle = document.getElementById('header').style;
    var foot = document.getElementById('footer');
    var footStyle = foot.style;
    if (isFocused) {
        if (!focusedResizing) {
            focusedResizing = true;
            headStyle.position = 'absolute';
            footStyle.display = 'none';
        } 
        headStyle.top = window.pageYOffset + 'px';
        // window.innerHeight wrong
        //var footTop = window.pageYOffset + window.innerHeight - foot.offsetHeight;
        //footStyle.bottom = (document.body.offsetHeight - footTop) + 'px';
    }
    ticking = false;
}

tryfix();

r-header

另外若是页面缩放,也会引发头部定位不正常。详情能够看这里,关于anroidfixed的支持状况,能够看这里

5. Android弹出的键盘遮住输入框

5.1 出现场景

当输入框比较靠下时,android上弹出键盘,会将输入框遮住。

说明:测试了不少机型,发现如今的android上的浏览器都貌似修复了这个问题,就是当键盘弹上来的时候,会默认地将输入框上移。可是我在项目中内嵌的webview中确实遇到了这种问题。

测试说明:测试的机型包括了如今一些主要的浏览器:chromeUCQQOpera360、百度、猎豹,测试的android版本包括4.一、4.四、5.1等,测试的浏览器版本都有下载最低的历史版原本测。可是就测试的状况来看,除了猎豹浏览器会出现上述的状况以外,其余的基本表现正常。(更多测试量没作,没有这么多机器呀。尴尬😓)

逗比时刻:我为了测试较老的Android版本,特意装了genymotion,后来发现根本就没有键盘弹出。

总之,若是遇到了上述的问题,不妨能够试试这样的办法。

keyboard

5.2 解决方案

弹出键盘的时候,计算可视区域的高度以及输入框距离视口的高度加上自己的高度(可视区域、自身距离视口高度 + 自身高度)。若是可视区域的高度大于后者,说明此时的输入框须要上移,那么就将body向上平移,不然不平移。在键盘消失的时候回归到原来的位置就好。具体能够看以下代码,另外能够看这个例子:https://jsbin.com/ganiqus

var availHeight = Math.max(document.documentElement.clientHeight || document.body.clientHeight);
var inputs = document.getElementsByTagName('input');
var textareas = document.getElementsByTagName('textarea');
var footer = document.querySelector('#footer');
var ftStyle = footer.style;
var body = document.body;
var keyboardHeight;

// 绑定事件
for (var i = 0; i < inputs.length; i++) {
    var input = inputs[i];
    input.onfocus = focused;
    input.onblur = blured;
}

for (var i = 0; i < textareas.length; i++) {
    var textarea = textareas[i];
    textarea.onfocus = focused;
    textarea.onblur = blured;
}

// 模拟事件
function mockEvent(type, fn) {
    var createDiv = document.createElement('div');
    createDiv.style.display = 'none';
    document.body.appendChild(createDiv);

    var e = document.createEvent('MouseEvent'); 
    e.initEvent(type, true, true); 

    createDiv.addEventListener(type, function() {
        fn && fn();
        createDiv.remove();
    });

    createDiv.dispatchEvent(e); 
}

function focused() {
    // 事件模拟
    mockEvent('native.keyboardshow');
}

function blured() {
    mockEvent('native.keyboardhide');
}

function getOffsetTop(el) { 
    var mOffsetTop = el.offsetTop; 
    var mOffsetParent = el.offsetParent; 
    while(mOffsetParent) { 
        mOffsetTop += mOffsetParent.offsetTop + mOffsetParent.clientTop; 
        mOffsetParent = mOffsetParent.offsetParent; 
    } 
    return mOffsetTop; 
}

// 是否须要上移输入框
function needPullUpScreen(target, top, height) {
    var keyboardShow = false;
    var nodeName = target.nodeName;
    var leftHeight;
    var isAndroid = true;
    if (isAndroid) {
        leftHeight = availHeight - keyboardHeight;
    } else {
        leftHeight = availHeight - keyboardHeight * window.devicePixelRatio;
    }

    if (nodeName) {
        if ((top + height + 5) >= leftHeight) {
            switch (nodeName.toLowerCase()) {
                case 'input':
                    keyboardShow = target.type !== 'button' && target.type !== 'file';
                    break;
                case 'textarea':
                    keyboardShow = true;
                    break;
                default:
                    keyboardShow = false;
                    break;
            }
        }
    }
    return keyboardShow;
};

// 监听键盘弹出事件
window.addEventListener('native.keyboardshow', function(e) {
    ftStyle.display = 'none';

    // 此处获取keyboard的高度,由插件提供,这里写死
    // keyboardHeight = e.keyboardHeight;
    keyboardHeight = 290;
    var activeEle = document.activeElement;

    // getBoundingClientRect 只在android 4.4以上才有用
    // top和height可用getOffsetTop(el)和el.offsetHeight替代
    var boundingClientRect = activeEle.getBoundingClientRect();
    var top = boundingClientRect.top;
    var height = boundingClientRect.height;

    // 移到居中位置
    // 这个高度能够根据本身的状况来写
    var moveOffset = top + height  - availHeight / 2;
    if (activeEle && needPullUpScreen(activeEle, top, height)) {
        body.style.webkitTransform = `translate3d(0, ${-moveOffset}px, 0)`;
        body.style.transform = `translate3d(0, ${-moveOffset}px, 0)`;
        body.style.webkitTransition = 'transform 200ms';
        body.style.transition = 'transform 200ms';
    }
});

// 监听键盘消失事件
window.addEventListener('native.keyboardhide', function() {
    body.style.webkitTransform = '';
    body.style.transform = '';
    body.style.webkitTransition = 'transform 200ms';
    body.style.transition = 'transform 200ms';

    setTimeout(function() {
        ftStyle.display = '';
    }, 200);
});

r-keyboard

注意:

  • 代码中用到了事件模拟键盘的弹出与消失。若是是在混合APP的开发中,应该是有相关插件来监听键盘事件的,同时能够获取键盘的高度
  • 若是旧版本的浏览器不支持getBoundingClientRect方法,能够用代码中提供的getOffsetTop方法来替代
  • 若是在IOS中也遇到这样的问题,此时的键盘高度要乘以设备像素比
相关文章
相关标签/搜索