bug持續更新中...
測試瀏覽器
Chrome: 61.0.3163.73
Safari: 10.0(IOS 10.3.3)
Github: webapp-bugs
1. IOS overflow: scroll 全屏滾動出界
1.1 出現場景
滑動到最頂部(最底部)的時候,停下,然后繼續向上滑動(向下滑動)
1.2 解決方案
- 手動設置滑到邊界時的
scrollTop
(scrollFix)
當快滑到上邊界或者下邊界的值時,手動設置scrollTop
與達到邊界時相差一像素(上邊界時:scrollTop = 1
, 下邊界時:scrollTop = elem.scrollHeight - elem.offsetHeight - 1
),這樣就不會觸發出界的極限條件。
具體實現:
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. 有說在全局滾動下和局部滾動下會有差異,但是就我測試的情況來說,差異並不是特別大。可能是版本太高的原因,具體結論還待測試更多機型。
- 頭部由
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%;
}
2. IOS通過腳本使輸入框聚焦,無法彈出鍵盤
2.1 出現場景
看如下代碼:
// 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
上輸入框聚焦確沒有辦法彈出鍵盤
2.2 解決方案
爬牆爬到這么一個issue,3樓eddiemonge
老哥說到了,在IOS
下除非用戶手動觸發了輸入框的focus
事件,才會觸發鍵盤,至於設置定時器也是不管用的;但是,手動點擊一個按鈕,在按鈕的操作中再來執行focus
事件倒是管用的。他還給出了一個http://jsbin.com/inunis/8/edit?html,js,output
// 這樣是可以彈出鍵盤的
submitBtn.onclick = function(e) {
e.preventDefault();
inputEmail3.focus();
}
3. IOS光標不跟隨輸入框移動
3.1 艱辛歷程
我為什么會關注這個問題:那是因為我**(這里省略一萬個草泥馬)也遇到了這個問題呀,容我細細說來。
我有一個登錄頁面,在聚焦之后需要往上彈一下,android
上正常,然后IOS
上還同時引出了一個BUG
:輸入框上去了,但是光標卻在下面閃。怎么辦呢?當然是靠想辦法解決呀,后來我就想在輸入框上貼一層蒙版,點擊了之后消失,同時在點擊操作中,等到動畫結束之后再執行輸入框的focus
,行不行呢?好期待。。。
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
以上,不然,光標重繪不正常,而且光標有明顯的移動過程。所以如果童鞋們如果發現有什么更好的辦法,還望不吝賜教。
另外,如果一個頁面中有輸入框,聚焦之后,滑動過程中在IOS
上可能會出現不流暢的問題,其實可以這么做:監測頁面的touchmove
事件,如果當前頁面存在着輸入框被active
,那么直接讓其blur
,保證滑動過程中沒有輸入框被聚焦。(不過以我的測試情況來看,在chrome
和safari
上滑動的時候輸入框不再被激活,類似在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
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();
另外如果頁面縮放,也會引起頭部定位不正常。詳情可以看這里,關於anroid
上fixed
的支持情況,可以看這里
5. Android彈出的鍵盤遮住輸入框
5.1 出現場景
當輸入框比較靠下時,android
上彈出鍵盤,會將輸入框遮住。
說明:測試了很多機型,發現現在的android
上的瀏覽器都貌似修復了這個問題,就是當鍵盤彈上來的時候,會默認地將輸入框上移。但是我在項目中內嵌的webview
中確實遇到了這種問題。
測試說明:測試的機型包括了現在一些主要的瀏覽器:chrome
、UC
、QQ
、Opera
、360
、百度、獵豹,測試的android
版本包括4.1、4.4、5.1等,測試的瀏覽器版本都有下載最低的歷史版本來測。但是就測試的情況來看,除了獵豹瀏覽器會出現上述的情況之外,其他的基本表現正常。(更多測試量沒做,沒有這么多機器呀。尷尬😓)
逗比時刻:我為了測試較老的Android
版本,特地裝了genymotion
,后來發現根本就沒有鍵盤彈出。
總之,如果遇到了上述的問題,不妨可以試試這樣的辦法。
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);
});
注意:
- 代碼中用到了事件模擬鍵盤的彈出與消失。如果是在混合
APP
的開發中,應該是有相關插件來監聽鍵盤事件的,同時可以獲取鍵盤的高度 - 如果舊版本的瀏覽器不支持
getBoundingClientRect
方法,可以用代碼中提供的getOffsetTop
方法來替代 - 如果在
IOS
中也遇到這樣的問題,此時的鍵盤高度要乘以設備像素比