如何拦截移动端键盘的显示与隐藏事件?焦点监听方案适配返回按钮失效问题求助
解决移动端键盘收起(返回键触发)时无法显示导航栏的问题
这个场景我做移动端Blazor项目时也踩过坑——系统级的键盘收起操作(比如手机自带的返回键、系统状态栏的收起按钮)并不会触发输入框的focusout或blur事件,因为输入框其实还处于聚焦状态,只是键盘被强制隐藏了。下面给你几个亲测有效的方案:
方案一:监听视口高度变化(最通用)
移动端键盘弹出时,浏览器视口高度会明显压缩;键盘收起时,视口高度会恢复到初始值。我们可以通过监听resize事件,对比视口高度的变化来判断键盘状态,进而控制导航栏的显示。
实现代码:
@inject IJSRuntime JS @implements IDisposable <input type="text" @onfocusin="OnFocusInInput" @onfocusout="OnFocusOutInput"/> @if(isShowNav) { <!-- 你的导航栏代码 --> <div class="nav-bar">导航栏</div> } @code { private bool isShowNav = true; private int originalViewportHeight; private DotNetObjectReference<YourComponent>? _componentRef; // 替换成你的组件类名 protected override void OnAfterRender(bool firstRender) { if (firstRender) { // 获取初始视口高度 originalViewportHeight = JS.Invoke<int>("eval", "window.innerHeight"); _componentRef = DotNetObjectReference.Create(this); // 注册防抖后的resize监听(避免频繁触发) JS.InvokeVoid("eval", $@" function debounce(func, delay) {{ let timer; return function() {{ clearTimeout(timer); timer = setTimeout(() => func(), delay); }} }} window._viewportResizeHandler = debounce(() => {_componentRef.Value.InvokeAsync<bool>('OnViewportResize')}, 200); window.addEventListener('resize', window._viewportResizeHandler); "); } base.OnAfterRender(firstRender); } void OnFocusInInput() { isShowNav = false; StateHasChanged(); } void OnFocusOutInput() { isShowNav = true; StateHasChanged(); } [JSInvokable] public async Task<bool> OnViewportResize() { int currentHeight = JS.Invoke<int>("eval", "window.innerHeight"); // 阈值可根据你的设备调整,一般200px足够区分键盘弹出/正常状态 bool isKeyboardOpen = originalViewportHeight - currentHeight > 200; // 仅在键盘收起且导航栏当前隐藏时更新状态,避免和focus事件冲突 if (!isKeyboardOpen && !isShowNav) { isShowNav = true; await InvokeAsync(StateHasChanged); } return true; } public void Dispose() { // 销毁时移除监听,避免内存泄漏 JS.InvokeVoid("eval", "window.removeEventListener('resize', window._viewportResizeHandler)"); _componentRef?.Dispose(); } }
方案优势:
- 覆盖所有键盘收起场景:包括返回键、系统按钮、点击空白区(部分浏览器点击空白区会触发resize)
- 无需依赖原生API,跨移动端浏览器通用
方案二:结合文档点击事件(补充优化)
如果担心视口高度变化的误判(比如平板用户手动调整窗口),可以额外监听文档的点击事件,判断点击区域是否为输入框,若不是则强制显示导航栏:
补充代码:
在OnAfterRender中添加:
JS.InvokeVoid("eval", $@" window._outsideClickHandler = function(e) {{ const isInput = e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA'; if (!isInput) {{ {_componentRef.Value.InvokeAsync<bool>('OnOutsideClick')} }} }}; document.addEventListener('touchstart', window._outsideClickHandler); ");
然后添加对应的方法:
[JSInvokable] public async Task<bool> OnOutsideClick() { if (!isShowNav) { isShowNav = true; await InvokeAsync(StateHasChanged); } return true; }
记得在Dispose中移除这个监听:
JS.InvokeVoid("eval", "document.removeEventListener('touchstart', window._outsideClickHandler)");
注意事项:
- 一定要在组件销毁时移除所有JS事件监听,避免内存泄漏
- 视口高度的阈值可以根据你的目标设备调整,比如某些大屏手机可能需要调高到250px
- 防抖函数是必要的,因为
resize事件在键盘弹出/收起时可能会触发多次,避免频繁更新组件状态
内容的提问来源于stack exchange,提问作者FordRelak
相关产品推荐
相关产品推荐

