-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8f219e4
commit 3c7f0a1
Showing
2 changed files
with
148 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
# IOS 浏览器页面布局错位(如:点不到)的分析与解决 | ||
|
||
IOS 浏览器软键盘的拉起与收缩、微信 IOS 浏览器底部导航条的显示与隐藏,很容易导致页面布局错位(相对窗体的绝对定位元素): | ||
|
||
- 明明按钮在这里,却要在上面一点儿点击屏幕才能点到它 | ||
- 明明弹框是居中显示的,却向上偏移了很多,导致下面很多空白 | ||
- 明明是固定浮动在某个位置,却点不到它 | ||
|
||
## 1. Android 与 IOS 的差异 | ||
|
||
- 在 Android 中,软键盘的弹起与收缩会触发 `window` 对象的 `resize` 事件,而 IOS 不会 | ||
- 微信 IOS 浏览器底部导航条的显示与隐藏会触发 `window` 对象的 `resize` 事件,而 Android 中没有底部导航条 | ||
|
||
## 2. IOS 里的一些特性 | ||
|
||
- 为了达到极致的体验,IOS 浏览器很多特性是不遵循 W3C 规范的 | ||
- 软键盘的弹起与收缩不会触发 `window` 对象的 `resize` 事件 | ||
- 软键盘收缩后,固定定位的元素处于错位状态,需要滑动页面后才刷新页面恢复到正常状态 | ||
|
||
## 3. 具体情况分析 | ||
|
||
不管是 IOS 浏览器软键盘的拉起与收缩,还是微信 IOS 浏览器底部导航条的显示与隐藏, | ||
都是改变的 window 窗体的大小,并且有一个动画的过程(不是一下子消失的,而是滑动慢慢消失的)。 | ||
|
||
如果你的弹框的动画(如:从底部向上弹起)与浏览器收缩键盘的动画在同一时间进行,就很可能会导致元素布局错位。 | ||
|
||
微信 IOS 浏览器底部导航条的显示与隐藏跟软键盘的拉起与收缩是差不多的,但微信 IOS 浏览器底部导航条还有一个很大的特点: | ||
|
||
在单页面应用中(SPA),当路由发生变化时,底部导航条会一下子就显示,而这很难确定是先渲染了页面还是先显示了底部导航条, | ||
这也很容易导致元素布局错位。 | ||
|
||
## 4. 怎么解决 | ||
|
||
### 4.1 监听键盘弹起与收缩,自动做一些操作 | ||
|
||
新建 `watch-keyboard.js` 脚本,引入到页面中。 | ||
|
||
当页面中键盘弹起时,`body` 会有 `keyboard-active` class,可以根据这个隐藏一些元素。 | ||
|
||
``` | ||
import {isIos} from '../utils'; | ||
import debounce from 'lodash/debounce'; | ||
// 初始高度 | ||
const winHeight = window.innerHeight; | ||
// 判断是不是弹起了软键盘 | ||
const judgeDistance = 200; | ||
if (!isIos) { | ||
window.addEventListener( | ||
'resize', | ||
debounce(() => { | ||
if (window.innerHeight < winHeight - judgeDistance) { | ||
// 键盘弹起 | ||
document.body.classList.add('keyboard-active'); | ||
} else { | ||
document.body.classList.remove('keyboard-active'); | ||
} | ||
}, 300), | ||
!1 | ||
); | ||
} | ||
else { | ||
// IOS 软键盘的弹起与收缩不会触发 `window` 对象的 `resize` 事件,用定时器实现 | ||
// 保证能够滚动 | ||
document.body.style.minHeight = (winHeight + 2) + 'px'; | ||
// 上两次高度记录 | ||
let secondLastWinHeight = winHeight; | ||
// 上一次高度记录 | ||
let lastWinHeight = winHeight; | ||
setInterval(() => { | ||
const newWinHeight = window.innerHeight; | ||
// 变化结束 | ||
if (secondLastWinHeight !== lastWinHeight && lastWinHeight === newWinHeight) { | ||
if (newWinHeight < winHeight - judgeDistance) { | ||
// 键盘弹起 | ||
document.body.classList.add('keyboard-active'); | ||
} else { | ||
document.body.classList.remove('keyboard-active'); | ||
// window 需要滚动一下,让页面刷新一下,否则弹框会出现错位的问题 | ||
window.scrollTo(0, window.scrollY ? window.scrollY - 1 : 1); | ||
} | ||
} | ||
secondLastWinHeight = lastWinHeight; | ||
lastWinHeight = newWinHeight; | ||
}, 300); // 可以根据需要调整间隔时间(越小越精确) | ||
} | ||
``` | ||
|
||
### 4.2 监听窗体大小变化,执行一个回调,做更多操作 | ||
|
||
当一个软键盘弹起时,又点击了一个按钮,然后需要弹框选择一个东西的时候,这个时候就需要等待软键盘收起之后,IOS 刷新屏幕之后,再显示弹框选择东西。 | ||
|
||
新建 `wait-for-stable-win-height.js` 脚本,引入到页面中。 | ||
|
||
``` | ||
import { isIos } from '../utils'; | ||
/** | ||
* 等待 window 高度不变了之后执行一个回调函数 | ||
* | ||
* @param onComplete 完成的回调 | ||
* @param delay 延迟多少时间再判断 | ||
* @param interval 定时器间隔时间 | ||
*/ | ||
export default ({ onComplete, delay = 200, interval = 50 }) => { | ||
setTimeout(() => { | ||
let winHeight = window.innerHeight; | ||
const timer = setInterval(() => { | ||
const newWinHeight = window.innerHeight; | ||
if (winHeight === newWinHeight) { | ||
clearInterval(timer); | ||
if (onComplete) { | ||
if (!isIos) { | ||
setTimeout(() => { | ||
onComplete(); | ||
}, 100); | ||
return; | ||
} | ||
// window 需要滚动一下,让页面刷新一下,否则弹框会出现错位的问题 | ||
window.scrollTo(0, window.scrollY ? window.scrollY - 1 : 1); | ||
setTimeout(() => { | ||
onComplete(); | ||
}, 200); | ||
} | ||
} else { | ||
winHeight = newWinHeight; | ||
} | ||
}, interval); | ||
}, delay); | ||
}; | ||
``` | ||
|
||
## 后续 | ||
|
||
更多博客,查看 [https://github.com/senntyou/blogs](https://github.com/senntyou/blogs) | ||
|
||
作者:[深予之 (@senntyou)](https://github.com/senntyou) | ||
|
||
版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)) |