IOS下fixed失效

问题

IOS下,当input唤起软键盘,fixed会失效变成absolute定位,如果说body的长度超过了屏幕长度,也就是说body是滚动的,那么导致的结果是原本fixed定位的模块都会表现成跟随body一起滚动了

栗子🌰

栗子中测试

  • 常规的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div id="app">
<header></header>
<main>
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
</main>
<footer>
<input type="text" placeholder="footer...">
</footer>
</div>
</template>

<script>
export default {
name: 'app'
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
header{
height:60px;
width:100%;
background-color:#333;
position:fixed;
top:0;
left:0;
}
main{
margin-top:60px;
margin-bottom:50px;
}
img{
display:block;
}
footer{
height:50px;
width:100%;
background-color:#333;
position:fixed;
bottom:0;
left:0;
}
input{
height:30px;
width:80%;
margin:7px;
}
</style>
  • BUG现象

未唤起软键盘,页面正常

唤起软键盘,fixed失效

不一样的webview反应不太相同,上面的截图是在钉钉端(非wkwebview内核),在fixed失效的同时,错误定位在失效之前该模块对应在正常文档流中的那个位置,截图展示如下

现在我滚动到底栏在第四个图的中间位置,顶栏大概在第一个位置的1/3处

然后唤起软键盘后,fixed失效并正好定位在失效之前的位置,headerfooter都是


这种情况除外,在safari中以及微信(wkwebview内核)上fixed失效但不会定位到失效前对应的位置,还是正常定位了top:0bottom:0

如下是在微信上截图,也是在上次的位置唤起软键盘,但是没错误定位,如下:

分别滚动到底部和顶部,如期就在原始的定位位置


这种差距和这篇博客没关系,暂不讨论,总之结果是fixed会失效

解决方案

采取的方式是利用CSS更改布局来搞定,存在这个问题有个前提是body长度超出了屏幕高度,如此fixed失效成为absolute才会有影响,如果说本身body都没有超过高度,那么即便是absolute定位也是能正常在顶部或底部,所以,解决方式一是让body长度不超过屏幕高度,内容多就放在某个元素里滚动,比如放在main中滚,不让body滚动

更改代码:

1
2
3
4
5
6
7
8
main{
width:100%;
position:absolute;
top:60px;
bottom:50px;
overflow-y:scroll;
-webkit-overflow-scrolling: touch;//解决IOS滑动卡顿问题
}

验证是可以的,不过有个新坑,就是input掉下来一截,没有正好在软键盘的上部,当输入内容后才会再次弹上来在软键盘上面,这和使用的不是原软键盘有关,看下差别

原软键盘,没问题:

搜狗软键盘,掉下来一截:

对比可见其实input定位一样,是搜狗软键盘会高一些些,如图:

应该不少数人用第三方输入法吧,特别是女生喜欢更换各种输入法皮肤,所以这个bug出现率很高
查阅资料IOS唤起软键盘的过程是通过滚动可视窗口让输入框滚动上来,也就是document.body.scrollTop()会改变(安卓的方式是改变window.height()), IOS会遮挡输入框, 是因为,第三方输入法的tool bar或者键盘也被当做可视区域了(包含在键盘弹出时的window.innerHeight),所以滚动得低了

针对解决方式也是搜集大量的资料,看别人的总结,方法多样,比如说底端input就是个摆设,点击它时展示一个真正让用户输入的input紧贴软键盘上方,我觉得方法麻烦没尝试,更多的方法是说唤起键盘时自己控制下body的滚动,既然之前滚动的不够,那就自己控制让其滚动到最底端,大致思路如此,实现存在不少形式,比如由于没有监听键盘被唤起的事件,所以监听事件的选择很重要,对于安卓来说唤起键盘后是会有resize事件,但是IOS不会触发resize,那随之自然想到focus事件,但是唤起键盘是个过程,需要一定的时间,触发focus时可能这个过程还没结束,自然又会想到用setTimeout()事件做延时执行,实际尝试下来,效果不好,又换成setInterval()不断触发,使用感更好,滚动部分直接赋值页面内容高度了,最后总结觉得比较好的方式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div id="app">
<header></header>
<main>
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
</main>
<footer>
<input type="text" placeholder="footer..." ref="input" v-on:focus="changeScroll">
</footer>
</div>
</template>

<script>
export default {
name: 'app',
data: function () {
return {
timer: null
}
},
methods: {
changeScroll: function () {
this.timer = setInterval(function () {
document.body.scrollTop = document.body.scrollHeight
}, 100)
}
}
}
</script>

效果:

PS:这里只是缓解IOS唤起软键盘导致fixed失效导致页面错乱,顺便处理了IOS的输入框被软键盘隐藏的bug,至于安卓软键盘遮住输入框的情况暂时没测

又调了代码,失去焦点的时候clearInterval(),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div id="app">
<header></header>
<main>
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
<img src="./assets/logo.png">
</main>
<footer>
<input type="text" placeholder="footer..." ref="input" v-on:focus="changeScroll" v-on:blur="removeTimer">
</footer>
</div>
</template>

<script>
export default {
name: 'app',
data: function () {
return {
timer: null
}
},
methods: {
changeScroll: function () {
this.timer = setInterval(function () {
document.body.scrollTop = document.body.scrollHeight
}, 100)
},
removeTimer: function () {
clearInterval(this.timer)
}
}
}
</script>