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

Blazor中focusout事件获取relatedTarget的方法及相关疑问

在Blazor中实现多选择下拉组件的焦点管理问题

我想在Blazor中实现类似下面原生JS示例的多选择下拉功能,但遇到两个核心问题:

  1. Blazor的FocusEventArgs不支持relatedTarget参数,不知道怎么在focusout事件中获取这个值
  2. 尝试过用取消令牌+延迟隐藏的方案,但效果不好
    另外,官方文档显示Blazor的自定义事件参数里没有relatedTarget,这是不是意味着不建议在自定义参数处理器里依赖这个参数?求解决方案建议。

原生JS参考实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div class="multiselect" onfocusin="focusin">
        <input class="search" type="search" placeholder="search..."></input>
        <div class="dropdown" tabindex="0">
            <select multiple class="select">
                <option>aaaa</option>
                <option>bbbb</option>
                <option>cccc</option>
            </select>
            <button class="btnOk" type="button">Select</button>
            <button class="btnCancel" type="button">Cancel</button>
        </div>
    </div>

    <script>
        const btnOk = document.querySelector('.btnOk');
        const btnCancel = document.querySelector('.btnCancel');
        const dropdown = document.querySelector('.dropdown');
        const select = document.querySelector('.select');
        const search = document.querySelector('.search');
        const multi = document.querySelector('.multiselect');
        multi.addEventListener("focusin", focusin);
        multi.addEventListener("focusout", focusout);
        hide();
    
        function focusin(event) {
            show()
        }
    
        function focusout(event) {
            console.log(event.relatedTarget);
            if (needToHide1(event)) {
                hide();
            }
        }
    
        function needToHide(focusoutEvent) {
            return event.relatedTarget != select
                && event.relatedTarget != search
                && event.relatedTarget != dropdown
                && event.relatedTarget != btnOk
                && event.relatedTarget != btnCancel
        }
    
        function show() {
            dropdown.style.display = "block"
        }
    
        function hide() {
            dropdown.style.display = "none";
        }
    </script>
    <style>
        .multiselect {
            width: 300px;
            font-size: 20px;
        }
    
        .select {
            width: 100%;
            font-size: 20px;
    
        }
    
        .search {
            font-size: 20px;
            width: 100%;
        }
    </style>
</body>

</html>

解决方案建议

1. 通过JS互操作获取relatedTarget

Blazor原生事件参数不包含relatedTarget,但可以通过JS互操作直接处理焦点事件,再把判断结果传给Blazor组件:

Blazor组件代码 (.razor)

<div class="multiselect" @ref="MultiSelectElement">
    <input class="search" type="search" placeholder="search..." @onfocus="ShowDropdown" />
    <div class="dropdown" tabindex="0" style="display: @(IsDropdownVisible ? "block" : "none")">
        <select multiple class="select">
            <option>aaaa</option>
            <option>bbbb</option>
            <option>cccc</option>
        </select>
        <button class="btnOk" type="button" @onclick="HandleOk">Select</button>
        <button class="btnCancel" type="button" @onclick="HandleCancel">Cancel</button>
    </div>
</div>

@code {
    private ElementReference MultiSelectElement;
    private bool IsDropdownVisible { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("registerMultiselectFocusHandlers", MultiSelectElement, DotNetObjectReference.Create(this));
        }
    }

    [JSInvokable]
    public void OnMultiselectFocusOut(bool shouldHide)
    {
        if (shouldHide)
        {
            IsDropdownVisible = false;
            StateHasChanged();
        }
    }

    private void ShowDropdown()
    {
        IsDropdownVisible = true;
    }

    private void HandleOk()
    {
        // 处理确认逻辑
        IsDropdownVisible = false;
    }

    private void HandleCancel()
    {
        IsDropdownVisible = false;
    }
}

全局JS脚本

window.registerMultiselectFocusHandlers = (element, dotNetRef) => {
    const internalElements = Array.from(element.querySelectorAll('.search, .select, .btnOk, .btnCancel, .dropdown'));

    element.addEventListener('focusout', (event) => {
        const isFocusingInternal = internalElements.includes(event.relatedTarget);
        dotNetRef.invokeMethodAsync('OnMultiselectFocusOut', !isFocusingInternal);
    });

    element.addEventListener('focusin', () => {
        dotNetRef.invokeMethodAsync('OnMultiselectFocusOut', false);
    });
}

2. 避免依赖relatedTarget的焦点组方案

如果不想依赖relatedTarget,可以通过标记内部焦点状态来实现:

  • 给组件内所有可聚焦元素添加@onfocus事件,标记焦点在组件内部
  • 在focusout事件触发时,延迟短时间再检查焦点状态,决定是否隐藏下拉框

示例代码:

<div class="multiselect">
    <input class="search" type="search" placeholder="search..." 
           @onfocus="() => IsFocusedInside = true" 
           @onfocusout="OnContainerFocusOut" />
    <div class="dropdown" tabindex="0" style="display: @(IsDropdownVisible ? "block" : "none")">
        <select multiple class="select" 
                @onfocus="() => IsFocusedInside = true" 
                @onfocusout="OnContainerFocusOut" />
        <button class="btnOk" type="button" 
                @onclick="HandleOk" 
                @onfocus="() => IsFocusedInside = true" />
        <button class="btnCancel" type="button" 
                @onclick="HandleCancel" 
                @onfocus="() => IsFocusedInside = true" />
    </div>
</div>

@code {
    private bool IsDropdownVisible { get; set; }
    private bool IsFocusedInside { get; set; }
    private CancellationTokenSource? _cts;

    private void ShowDropdown()
    {
        IsDropdownVisible = true;
        IsFocusedInside = true;
    }

    private async void OnContainerFocusOut()
    {
        _cts?.Cancel();
        _cts = new CancellationTokenSource();
        
        try
        {
            await Task.Delay(100, _cts.Token);
            if (!IsFocusedInside)
            {
                IsDropdownVisible = false;
                StateHasChanged();
            }
        }
        catch (OperationCanceledException)
        {
            // 焦点移到内部元素,取消隐藏
        }
        finally
        {
            IsFocusedInside = false;
            _cts.Dispose();
        }
    }

    private void HandleOk()
    {
        IsDropdownVisible = false;
    }

    private void HandleCancel()
    {
        IsDropdownVisible = false;
    }
}

关于relatedTarget的使用建议

Blazor官方未将relatedTarget纳入原生事件参数,核心原因是跨平台兼容性——Server模式下JS事件的部分属性无法高效序列化传递到.NET端。

如果必须使用relatedTarget,优先通过JS互操作在前端完成判断逻辑,再把结果传给Blazor组件,不要尝试自定义事件参数处理器来获取它,这种方式不仅实现复杂,还可能存在跨平台兼容性隐患。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:44:54