# 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))