You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何拦截移动端键盘的显示与隐藏事件?焦点监听方案适配返回按钮失效问题求助

解决移动端键盘收起(返回键触发)时无法显示导航栏的问题

这个场景我做移动端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)");

注意事项:

  1. 一定要在组件销毁时移除所有JS事件监听,避免内存泄漏
  2. 视口高度的阈值可以根据你的目标设备调整,比如某些大屏手机可能需要调高到250px
  3. 防抖函数是必要的,因为resize事件在键盘弹出/收起时可能会触发多次,避免频繁更新组件状态

内容的提问来源于stack exchange,提问作者FordRelak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:12:30