如何在移动 Safari 中打开虚拟键盘时将视口固定到位?
How to fix viewport in place when virtual keyboard opens in mobile Safari?
目标
在移动版 Safari 上,当虚拟键盘打开时,屏幕应该呈现如下图像:
其中:
- 导航栏和输入固定到位
- 短信列表可滚动
问题
在移动版 Safari 上,当软键盘打开时,拖动输入框或导航栏可以上下移动整个视口。
我有一个演示问题的屏幕截图视频:https://youtu.be/GStBjRVpoGU
我在这个问题上花了几个月的时间,包括在 Stack Overflow 上搜索许多类似的问题,但我一直无法找到有效的解决方案(或者,至少,我能够找到的解决方案工作)。
背景
此应用是一款混合移动应用:使用 React.js 作为 Web 应用构建,但使用 WebView 组件包装在 React Native 应用中。然而,即使在普通移动 Safari 中打开网络应用程序,也存在同样的问题 window.
Mobile Safari 有一个相关的问题,软键盘在打开时将整个视口向上推,从而使视口的上半部分被向上推离屏幕。 This excellent blog 提供了该问题的描述和解决方案。我实施了该解决方案。它阻止了软键盘打开时视口向上移动,但软键盘打开后视口仍然可以滑动。
另一个问题是当软键盘为 opened/closed 时,移动版 Safari 不会更新 window.innerHeight
。为了解决这个问题,我在 React Native 应用程序中使用了 react-native-keyboard-spacer
,有点像这样:
render() {
return (
<React.Fragment>
<SafeAreaView >
<WebView/>
</SafeAreaView>
<KeyboardSpacer />
</React.Fragment>
);
}
每当软键盘 opens/closes 时,这会更改 Webview 的高度,因此 window.innerHeight
等也会更改。
众所周知,当软键盘打开时,移动版 Safari 上的 position: fixed;
效果不佳,因此我使用了 position: absolute;
,感谢其他 [=19] 的建议=].
我创建了一个 code sandbox to demonstrate the problem. Open it in mobile Safari to see the screen slide after the virtual keyboard is opened. You can also the actual code sandbox code here,它代表了我最接近解决这个问题的方法。
不过,关于代码沙箱需要注意一件事。没有 WebView 或 KeyboardSpacer:它只是一个网页。因此,我不得不硬编码一些高度。但是如果你在移动 Safari 中打开它,你会看到视口在软键盘打开后到处滑动。
有人以前遇到过这个问题吗?你怎么修好它的?非常感谢。
在JMathew的帮助下,我找到了解决这个问题的方法。
查看工作代码和框示例 here。
- 将导航栏、消息列表和输入内容放入容器 div。
- 向那些容器 div 添加引用。例如:
const messageListContainerRef = useRef<HTMLDivElement>(null);
//...
return (
//...
<div ref={messageListContainerRef}>
<MessageList/>
</div>
//...
)
- 当虚拟键盘打开时(当虚拟输入获得焦点时),将 touchmove 事件的事件侦听器添加到
inputContainerRef
和 navbarContainerRef
。将其用于 preventDefault()
触摸移动事件,这将阻止用户上下滑动输入和导航栏。例如:
if (inputContainerRef.current) {
inputContainerRef.current.addEventListener('touchmove', (e) => {
e.preventDefault();
});
}
- 您仍然希望能够滚动邮件列表,因此您不能
preventDefault()
就可以了。相反,使用 this solution(link 现已失效)通过设置 scrollTop
值来防止用户滚动到页面底部(和顶部):
if (messageListContainerRef.current) {
messageListContainerRef.current.addEventListener('touchmove', (e: any) => {
if (!e.currentTarget) {
return;
}
if (e.currentTarget.scrollTop === 0) {
e.currentTarget.scrollTop = 1;
} else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop +
e.currentTarget.offsetHeight) {
e.currentTarget.scrollTop -= 1;
}
});
}
根据您的 want/need,您可以将那些 touchmove
事件侦听器设置在组件安装上,而不是输入焦点上。如果适合您,您甚至可以为整个 document.body 设置它们。
Javascript解法,终于得到完美解法
window.scrollBy(0, 100); // Scroll 100px downwards
window.scrollBy(100, 0); // Scroll 100px to the right
目标
在移动版 Safari 上,当虚拟键盘打开时,屏幕应该呈现如下图像:
其中:
- 导航栏和输入固定到位
- 短信列表可滚动
问题
在移动版 Safari 上,当软键盘打开时,拖动输入框或导航栏可以上下移动整个视口。
我有一个演示问题的屏幕截图视频:https://youtu.be/GStBjRVpoGU
我在这个问题上花了几个月的时间,包括在 Stack Overflow 上搜索许多类似的问题,但我一直无法找到有效的解决方案(或者,至少,我能够找到的解决方案工作)。
背景
此应用是一款混合移动应用:使用 React.js 作为 Web 应用构建,但使用 WebView 组件包装在 React Native 应用中。然而,即使在普通移动 Safari 中打开网络应用程序,也存在同样的问题 window.
Mobile Safari 有一个相关的问题,软键盘在打开时将整个视口向上推,从而使视口的上半部分被向上推离屏幕。 This excellent blog 提供了该问题的描述和解决方案。我实施了该解决方案。它阻止了软键盘打开时视口向上移动,但软键盘打开后视口仍然可以滑动。
另一个问题是当软键盘为 opened/closed 时,移动版 Safari 不会更新 window.innerHeight
。为了解决这个问题,我在 React Native 应用程序中使用了 react-native-keyboard-spacer
,有点像这样:
render() {
return (
<React.Fragment>
<SafeAreaView >
<WebView/>
</SafeAreaView>
<KeyboardSpacer />
</React.Fragment>
);
}
每当软键盘 opens/closes 时,这会更改 Webview 的高度,因此 window.innerHeight
等也会更改。
众所周知,当软键盘打开时,移动版 Safari 上的 position: fixed;
效果不佳,因此我使用了 position: absolute;
,感谢其他 [=19] 的建议=].
我创建了一个 code sandbox to demonstrate the problem. Open it in mobile Safari to see the screen slide after the virtual keyboard is opened. You can also the actual code sandbox code here,它代表了我最接近解决这个问题的方法。
不过,关于代码沙箱需要注意一件事。没有 WebView 或 KeyboardSpacer:它只是一个网页。因此,我不得不硬编码一些高度。但是如果你在移动 Safari 中打开它,你会看到视口在软键盘打开后到处滑动。
有人以前遇到过这个问题吗?你怎么修好它的?非常感谢。
在JMathew的帮助下,我找到了解决这个问题的方法。
查看工作代码和框示例 here。
- 将导航栏、消息列表和输入内容放入容器 div。
- 向那些容器 div 添加引用。例如:
const messageListContainerRef = useRef<HTMLDivElement>(null);
//...
return (
//...
<div ref={messageListContainerRef}>
<MessageList/>
</div>
//...
)
- 当虚拟键盘打开时(当虚拟输入获得焦点时),将 touchmove 事件的事件侦听器添加到
inputContainerRef
和navbarContainerRef
。将其用于preventDefault()
触摸移动事件,这将阻止用户上下滑动输入和导航栏。例如:
if (inputContainerRef.current) {
inputContainerRef.current.addEventListener('touchmove', (e) => {
e.preventDefault();
});
}
- 您仍然希望能够滚动邮件列表,因此您不能
preventDefault()
就可以了。相反,使用 this solution(link 现已失效)通过设置scrollTop
值来防止用户滚动到页面底部(和顶部):
if (messageListContainerRef.current) {
messageListContainerRef.current.addEventListener('touchmove', (e: any) => {
if (!e.currentTarget) {
return;
}
if (e.currentTarget.scrollTop === 0) {
e.currentTarget.scrollTop = 1;
} else if (e.currentTarget.scrollHeight === e.currentTarget.scrollTop +
e.currentTarget.offsetHeight) {
e.currentTarget.scrollTop -= 1;
}
});
}
根据您的 want/need,您可以将那些 touchmove
事件侦听器设置在组件安装上,而不是输入焦点上。如果适合您,您甚至可以为整个 document.body 设置它们。
Javascript解法,终于得到完美解法
window.scrollBy(0, 100); // Scroll 100px downwards
window.scrollBy(100, 0); // Scroll 100px to the right