Chrome: 61.0.3163.73
Safari: 10.0 (IOS 10.3.3)
滑动到最顶部(最底部)的时候,停下,然后继续向上滑动(向下滑动)
- 手动设置滑到边界时的
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%;
}
看如下代码:
// 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
上输入框聚焦确没有办法弹出键盘
爬墙爬到这么一个issue,3楼eddiemonge
老哥说到了,在IOS
下除非用户手动触发了输入框的focus
事件,才会触发键盘,至于设置定时器也是不管用的;但是,手动点击一个按钮,在按钮的操作中再来执行focus
事件倒是管用的。他还给出了一个http://jsbin.com/inunis/8/edit?html,js,output
// 这样是可以弹出键盘的
submitBtn.onclick = function(e) {
e.preventDefault();
inputEmail3.focus();
}
我为什么会关注这个问题:那是因为我**(这里省略一万个草泥马)也遇到了这个问题呀,容我细细说来。
我有一个登录页面,在聚焦之后需要往上弹一下,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
。下面讲解决方案。
我找到了这样的一个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;
}
});
页面中有fixed
头部,输入框,并且输入框靠下时,当输入框focus
的时候,会将整个页面上移,导致头部被顶出去。fixed position div freezes on page
原因大致是: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
的支持情况,可以看这里
当输入框比较靠下时,android
上弹出键盘,会将输入框遮住。
**说明:**测试了很多机型,发现现在的android
上的浏览器都貌似修复了这个问题,就是当键盘弹上来的时候,会默认地将输入框上移。但是我在项目中内嵌的webview
中确实遇到了这种问题。
**测试说明:**测试的机型包括了现在一些主要的浏览器:chrome
、UC
、QQ
、Opera
、360
、百度、猎豹,测试的android
版本包括4.1、4.4、5.1等,测试的浏览器版本都有下载最低的历史版本来测。但是就测试的情况来看,除了猎豹浏览器会出现上述的情况之外,其他的基本表现正常。(更多测试量没做,没有这么多机器呀。尴尬😓)
**逗比时刻:**我为了测试较老的Android
版本,特地装了genymotion
,后来发现根本就没有键盘弹出。
总之,如果遇到了上述的问题,不妨可以试试这样的办法。
弹出键盘的时候,计算可视区域的高度以及输入框距离视口的高度加上本身的高度(可视区域、自身距离视口高度 + 自身高度)。如果可视区域的高度大于后者,说明此时的输入框需要上移,那么就将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
中也遇到这样的问题,此时的键盘高度要乘以设备像素比