如果你在开发ios中的webview界面的时候会遇到以下问题,并且没有一个很好的解决方案的时候,可以继续阅读以下的文章,如果已经有了比较好的解决方案,我希望你可以继续阅读以下的内容,并给与我真诚的建议,谢谢!!!
问题
- 使用header container footer 三个absolute布局的时候,当键盘弹出的时候,header消失在视图外面
- 当键盘弹出的时候、遮住了最底层的输入框
- 当键盘弹出的时候、输入框出现在视图外,无法有效的观测到自己的输入
本文将会对这些问题进行讲解,给出一套比较通用的解决方案。帮助大家进行ios中webview的开发
ps: 我们将会搭配cordova进行使用,因为webview依赖原生开发,如果你们的项目不使用cordova,请找你们的原生给出一些我们将会用到的cordova插件的替代方案,请不要自行背锅。这样不利于项目的正常开发。
问题1
我们问题1在说什么?在移动端做布局的时候,我们很多的时候会给body一个
height:100%;
overflow:hidden;
的设置,这样会比较方便我们后面的布局,而此后的布局,大致可以分成三份
header
container
footer
这三块都使用absolute绝对布局来进行处理,可以控制,我们的header跟footer能够永远出现在我们的视线之内,header footer上通常有部分操作按键帮助我们执行一些操作,根据情况footer也经常不进行使用
如图
而当我们的键盘弹出的时候,在ios可能会出现以下的情况
可以看到,我们的header被顶出了视图之外,这样实际的操作体验是会不如人意的。怎么处理这种问题呢?
如果你使用的是cordova这些,那么你可以借助一些插件,我们使用的是ionic-plugin-keyboard,虽然是个ionic的插件,但是在vue项目中也能使用。。。
键盘插件
使用
Keyboard.disableScroll
设置为true,这样我们的webview就不会再进行滚动了,那么当键盘弹出的时候,则会是
这样就可以有效的解决header超出屏幕的问题了。
但是很明显,我们在这里也可以看到一个新的问题,那就是keyboard遮住了footer部分。这也是我们的第二个问题
问题2
来解决keyboard遮住了footer部分这个问题呢?其实思路也很简单,一般来说,我们的footer都是固定到底部的,他会有代码
bottom:0px;
而其相对位置,一般是body或者#app这种与body等高的元素,其高度为一屏幕。
那么我们只要让我们的body减小到原本的高度,减去键盘高度那么我们的footer自然就会在键盘上面了。
ps:这套方案不可以同时作用于安卓上面存在问题
那么代码设计就很简单了。
键盘弹出事件,我们可以通过onresize或者上面提到的cordova的键盘插件进行监听,或者让原生给出桥接。
这部分的代码是比较好处理的。
我们使用window.innerHeight获取初始的高度
使用document.body.style.height改变body高度
但是也存在问题
在ios上,有的时候,具体处罚条件不明,不管是resize还是cordova都有可能在一次聚焦触发多次事件,而第二次的事件中获取的键盘高度是不正确的,因此可能你需要一个节流处理,可以使用rxjs或者loadsh的节流行数进行处理
而为了获得更佳的用户体验,我们就有了问题三。
问题3
我们当然希望自己在输入的时候,能够始终拥有最佳的视图效果,也就是,我们输入的内容,始终在我们的视野范围之内,而经过前面哪一步,有的时候键盘弹出的时候我们的输入框是不会出现在我们的视野范围之内的
此时我们的聚焦区域不是footer而位于可滚动的container之内,因为body的高度缩小,就有可能导致我们container中的输入框出现在视图之外。
怎么解决呢?首先我们给出一个解决的目标
我们希望当聚焦键盘弹出的时候,输入框位置始终位于header下方20px的位置上
其中 20px 这个值可以根据产品喜好进行设置,且,存在一些情况你并不会希望其位置为20px,根据具体需求来设定,但是大致方向不变化。
那么我们怎么才能做到这样呢?比较愚蠢的做法是,监听每个input或者textare的focus事件,然后给定一个死值,但是这样代码十分的冗余,可维护性低下,而如果你知道了document.activeElement,通过这个元素来获取当前聚焦的元素,结合问题2中的解决方案,我们可以在键盘弹出事件中进行处理,获取到当前聚焦元素,再判断他是哪个元素,从而实现滚动。
整体处理方式我们使用了vuex这种store工具进行处理,键盘聚焦的时候设置state.common中的变量showKeyborad为true,在我们有input元素的组件中watch这个store值,再判断是哪个元素聚焦了,从而给出了滚动值。
这个方案也是我们第一版的处理方案,所以很明显,我们还有第二版~因为我们知道了另外一个属性
offsetParent
根据这个属性,我们可以知道当前元素的定位父元素
定位父级offsetParent的定义是:与当前元素最近的经过定位(position不等于static)的父级元素,主要分为下列几种情况
而元素的另外一个属性offsetTop则是当前元素距离定位父元素的位置。
为什么我们要使用这个呢?
因为在大部分的情况下,我们往往会使用组件库进行开放,而不是自己手写input,而对于大部分的组件库的设计来说,input组件的最外层都是一些包裹元素,其真实的表单元素并不在最外层,这一点我们可以在外面观测到。
那么我们的设计思路就很简单了。
因为我们要求滚动的是中间这个部分,如下图
而对于我们的整体dom结构往往是
body
#app
.container
.a
.b
.input-wrapper1
.inpuy-wrapperx
input
很明显,我们的滚动区域,就是 .container区域
其一般有css
top:headerHeight;
bottom:footerHeight;
那么实际上只需要我们逐步计算,从当前聚焦元素到container的offsetTop那么我们就知道应该让container滚动多少距离,从而实现滚动到我们需要的位置上去。
具体参考demo
核心代码
handleTestInputFocus () {
let activeElement = document.activeElement
let offsetTop = activeElement.offsetTop
let offsetParent = activeElement.offsetParent
if (activeElement === document.body) {
return
}
while (offsetParent.id !== this.wrapperId && offsetParent !== document.body) {
offsetTop += offsetParent.offsetTop
offsetParent = offsetParent.offsetParent
}
const viewTop = 20 // 不应该让光标紧贴header 这样体验依旧不好
document.getElementById(this.wrapperId).scrollTop = offsetTop - viewTop
}
注意在demo中我使用了focus时间来代替键盘弹出事件,这样会比较好进行观测。
所以会有一句对activeElement为body的处理。
此外,最外层一定要保证没margin,padding这些
demo地址为
Demo
切换到focus-scroll查看demo
但是这里还有一点需要注意的是,在ios的webview中,出现光标浮动在header上的情况,这种时候需要监听滚动事件,在滚动的时候失焦
如果你有不需要失焦就能解决这个问题的办法,希望可以联系我,教我一下谢谢!