前言
IOS 的兼容永远是前端开发的一个伤心地,虽然是这样,但你也没有办法,毕竟果粉还不是一般的少,当 IOS 键盘遇到 fixed 的时候,你除了感到无助外,更多的是绝望。不过现在曙光就在眼前,只要你把这篇文章仔细阅读完。
说走就走的旅程
首先我们得知道一个事实,那就是在绝大部安卓机中,只要你按常理出牌(常用布局),一般情况下点击输入框弹出手机键盘都不会有什么问题。这里我们不妨假设一个常用的场景,页面分为上中下布局,上(header)、中(main)、下(footer),其中 header 和 footer 分别固定在页面的顶部和底部,如果无法想象得出来,那么可以看下面这段代码:
在线 DEMO1 :http://yunkus.com/demo/mobile-input-pop-keyboard-solution/index-1.html
在安卓手机下点击输入框弹出键盘后是没问题的,但在 IOS 下就有点尴尬吧,虽然第一次点击输入框后一切貌似没问题,但当你在回滚下页面时,你就会发现 input 输入框竟然会随着页面的滚动而滚动,并且可以滚出可见区域,fixed 的效果竟然没有。至于为什么,我相信你已经早就知道了,在这里我就不多累赘了。这个问题要怎么解决呢?不急,我们一步一步来。首先我们已经确定了一点:ios 对 fixed 属性的支持不如安卓(最明显的就是在弹出键盘时),这是无用质疑的。
网上查阅了很多资料,但很不幸的是基本都差不多,更重要的是这些方案还无法解决问题,比如:使用 overflow:hidden;(禁止页面滚动) ,但也不能否认它们的存在。因为至少它们可以给我们提供一个思路:通过禁止页面滚动来解决在 IOS 下弹出键盘后 fixed 失效的问题,这可谓是曲线救国。
在线 DEMO2 :http://yunkus.com/demo/mobile-input-pop-keyboard-solution/index-2.html
通过禁止页面滚动的方法来解决这个问题的大致过程:当input 获取焦点后,绑定一个事件及处理函数,处理函数主要代码为 preventDefault() 用于禁止页面默认行为(包括页面滚动),当input 失去焦点后,解除绑定的事件,就可以恢复了页面滚动了。
但是上面这个还不是完美的解决方案,因为要是如果页面中存在 a 链接或者其它一些有关跳转。那么当用户点击 input 框,弹出键盘后,再去点击了页面中的一个跳转链接,那么很有可能就会没有成功解除事件绑定,所在在这里我们有必要作一些特殊处理,处理也很简单,我们可以给页面添加一个遮罩页来防止用户在弹出键盘后进行其它无关操作。
在线 DEMO3 :http://yunkus.com/demo/mobile-input-pop-keyboard-solution/index-3.html
注意
上面的几个在线 DEMO 需要在手机真机上访问才可以看到效果,上面事件是绑定了touchmove,而不是 touchstart,因为如果是绑定了touchstart 的话,当你点再遮罩时就会触发 touchstart ,有趣的是它会冒泡到 document ,而document 绑定了 touchstart ,此时就会执行绑定的相关函数。但如果我们把 touchstart 换成 touchmove 虽然也会冒泡到 document 但我们是给遮罩加了个点击事件,点击就相当于 touchstart ,而document 绑定的是 touchmove 所有是不会触发 document 上绑定的事件的。这很很好的解决了,遮罩无法点击关闭的问题。
我们还需要注意小细节,那就是在安卓机中是有物理返回键的,所以我们也需要作相应的处理,不然在项目中有可能会出现一些问题。毕竟在项目中的情况比上面的例子复杂得多得多,你很有可能会遇到各种奇葩的问题。
峰回路转
如果你觉得本文已经接近尾声,那么你就大错特错了,如果本文到此为止也就没有分享的必要了。上面的这些都是在 ios 的 safari 浏览器里运行的。但如果你尝试在 ios 里用第三方浏览器,比如:UC 浏览器浏览上面的三个 DEMO ,那么你就会发现当键盘弹出时页面真的是乱得一塌糊涂,所以好戏才刚刚开始。
我们在UC浏览器里用浏览 DEMO3 ,如下图:
输入框竟然没有在可视区的最下方,而是大概在中间的位置。要想解决这个问题,我们只需要添加两行代码就可以了:
JavaScript 代码
var footer = document.getElementById("footer");
var ftInput = document.getElementById("footer-input");
var mask = document.getElementById("mask");
var handleScroll = function(ev){
ev.preventDefault();
}
ftInput.onfocus = function(){
// 这两行就是关键代码
footer.style.position = "relative";
ftInput.scrollIntoView();
// 这两行就是关键代码 END
document.addEventListener("touchmove",handleScroll);
mask.style.display = "block";
}
ftInput.onblur = function(){
footer.style.position = "fixed";
mask.style.display = "none";
document.removeEventListener("touchmove",handleScroll);
}
mask.onclick = function(){
ftInput.blur();
mask.style.display = "none";
document.removeEventListener("touchmove",handleScroll);
}
下面这两行代码就可以实现在 IOS 里让输入框回到可见区的最下方。
关键代码
footer.style.position = "relative";
ftInput.scrollIntoView();
当输入框获取焦点后,我们让底部的 <div> 变为relative 定位。然后再使用scrollIntoView();让底部 <div> 滚动到可视区域中。这两行代码的顺序不可对调,否则不会有效果。
这个问题解决了之后,你会发现遮罩而又不见了。其实当你用安卓机浏览页面时你会看到有遮罩页,一切都很正常。在IOS 中用UC 浏览器却不见了,这就跟 IOS 的键盘弹出处理机制与安卓机不一样靠成的。要想解决这个问题我们得另谋出路。我们不妨对 IOS 进行单独处理,如果是 IOS 的话,我们就遮罩变成绝对定位,与 body 同高,这样就可以完全盖住页面了。代码如下:
回来吧遮罩
var ftInput = document.getElementById("footer-input");
var mask = document.getElementById("mask");
var handleScroll = function(ev){
ev.preventDefault();
}
ftInput.onfocus = function(){
// 这两行就是关键代码
footer.style.position = "relative";
ftInput.scrollIntoView();
// 这两行就是关键代码 END
document.addEventListener("touchmove",handleScroll);
mask.style.display = "block";
mask.style.height = document.body.offsetHeight+"px";
}
ftInput.onblur = function(){
footer.style.position = "fixed";
mask.style.display = "none";
mask.style = "";
document.removeEventListener("touchmove",handleScroll);
}
mask.onclick = function(){
ftInput.blur();
mask.style.display = "none";
mask.style = "";
document.removeEventListener("touchmove",handleScroll);
}
美美的页面就这样出来了:
在线 DEMO4 :http://yunkus.com/demo/mobile-input-pop-keyboard-solution/index-4.html
虽然说上面这里是针对 IOS 的,但在安卓下也是没问题的,但还是建议分别处理,不要一起处理,毕竟安卓已经很体贴。
这里总结一下:
- 在安卓机中,按正常的套路就好,怎么fixed 就怎么写,安卓显示都没问题。
- 在 IOS 中我们需要先把 fixed 定位的元素改成 absolute 定位,然后使用 scrollIntoView() 来让元素滚动可视区域,除此之处,我们还得使遮罩与 body 同高,输入框失去焦点后设置回fixed 定位,并去掉遮罩的高度样式。
- 不管是安卓,还是 IOS 我们都需要在键盘弹出后禁止页面滚动。
使用 scrollIntoView() 可能还会引出一个问题,如果页面是滚动加载更多的话,由于 scrollIntoView() 的作用相当于把页面滚动到底部,此时就会触发滚动加载,加载更多的数据,导航页面很有可能出现问题,所以当键盘弹出时,我们还得禁止掉滚动加载功能,以确保万无一失。
不管是安卓还是 IOS 上面的 DEMO 都是在 UC 浏览器下运行的。这里有一个意外的收获:安卓、 IOS 弹出键盘时都会触发 window.onresize 事件,可以利用这个来解决一些问题。至于怎么用,这个目前还没有实践过,但我们要知道有这么一回事。
上面只是很粗糙地重现并解决了在 IOS 下 fixed 与 键盘闹别扭的问题,本文其实也没什么技术含量,只是为了兼容 IOS 作了几个样式的修改而已。毕竟这只是给你提供一个思路,至于怎么实现,那就得看你自己了。
说到最后,还是那句话,好的开始就是成功的一半。所以在项目开始之初,我们就得做好布局的规划。在实际的开发当中,我们要尽量少的使用上面这种通过fixed 来实现上中下的布局,因为这会产生一此坑,让你很是抓狂。强烈建议通过弹性布局来实现上中下的结构,怎么做可以看这里《移动端布局解决方案》。