移動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中也遇到這樣的問題,此時的鍵盤高度要乘以設備像素比
相關文章
相關標籤/搜索