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

实现点击外部或关闭按钮关闭颜色选择器对话框

解决方案:为Blazor颜色选择器添加两种关闭方式

一、添加关闭按钮

首先修改自定义的ColorPicker组件,增加关闭按钮,点击时触发Closed回调并传递当前颜色,确保未选择新颜色时保留原有值。

修改ColorPicker组件代码

@if (IsOpened)
{
    <div class="color-picker-container">
        <!-- 原有颜色选择器UI内容 -->
        <div class="color-picker-actions mt-2">
            <button class="btn btn-sm btn-secondary" 
                    @onclick="() => Closed.InvokeAsync(MyColor)">
                关闭
            </button>
        </div>
    </div>
}

@code {
    [Parameter, EditorRequired]
    public bool IsOpened { get; set; }

    [Parameter, EditorRequired]
    public string MyColor { get; set; }

    [Parameter, EditorRequired]
    public EventCallback<string> Closed { get; set; }

    // 组件原有其他逻辑...
}

点击关闭按钮时,会调用Closed回调并传入当前颜色,你的OnClose方法只会关闭弹窗,不会修改已选颜色。

二、实现点击外部区域关闭弹窗

提供两种可靠实现方式,可根据场景选择:

方式1:利用Blazor事件冒泡与阻止冒泡

通过页面根容器的点击事件关闭所有弹窗,同时在弹窗内部阻止事件冒泡,避免点击弹窗内部触发关闭。

修改页面代码

<!-- 根容器:点击外部区域时关闭所有弹窗 -->
<div @onclick="CloseAllColorPickers" class="container mt-3">
    @foreach(var picker in _pickers)
    {
         <button class="btn btn-primary me-2" 
                 @onclick="() => this.OnOpen(picker)"
                 @onclick:stopPropagation> <!-- 阻止按钮点击事件冒泡到根容器 -->
            <div style="background-color:@picker.Color" class="buttonColor">&nbsp;</div>
            @picker.Title
        </button>

        @if (picker.IsOpen)
        {
            <!-- 弹窗容器:阻止内部点击事件冒泡到根容器 -->
            <div @onclick:stopPropagation class="position-absolute p-2 border rounded bg-white">
                <ColorPicker IsOpened="picker.IsOpen" 
                             MyColor="@picker.Color" 
                             Closed="(value) => this.OnClose(picker, value)" />
            </div>
        }
    }
</div>

@code{
    private List<ColorPickerData> _pickers = new();

    protected override Task OnInitializedAsync()
    {
        _pickers.Add(new() { Title="Picker 1" });
        _pickers.Add(new() { Title="Picker 2" });
        _pickers.Add(new() { Title = "Picker 3" });
        return Task.CompletedTask;
    }

    private Task OnClose(ColorPickerData data, string value)
    {
        data.IsOpen = false;
        data.Color = value;
        return Task.CompletedTask;
    }

    private Task OnOpen(ColorPickerData data)
    {
        // 打开当前弹窗前关闭其他弹窗(可选,避免多弹窗同时显示)
        foreach(var p in _pickers)
        {
            p.IsOpen = false;
        }
        data.IsOpen = true;
        return Task.CompletedTask;
    }

    // 关闭所有颜色选择器弹窗
    private void CloseAllColorPickers()
    {
        foreach(var picker in _pickers)
        {
            picker.IsOpen = false;
        }
    }

    public class ColorPickerData
    { 
        public string Title { get; set; } = "Color Picker";
        public bool IsOpen { get; set; }
        public string Color { get; set; } = "#000000";
    }
}

方式2:使用JS互操作监听外部点击

如果需要精确控制仅关闭当前打开的弹窗,可通过JS监听document点击事件,判断点击位置是否在弹窗外部。

步骤1:添加JS代码

在wwwroot/js/site.js中添加以下代码:

let clickOutsideHandler = null;

// 添加外部点击监听
window.addClickOutsideListener = function(dotNetRef, targetElementId) {
    // 移除之前的监听,避免重复绑定
    if (clickOutsideHandler) {
        document.removeEventListener('click', clickOutsideHandler);
    }

    clickOutsideHandler = (event) => {
        const targetElement = document.getElementById(targetElementId);
        // 点击目标不在弹窗内时,通知Blazor关闭弹窗
        if (targetElement && !targetElement.contains(event.target)) {
            dotNetRef.invokeMethodAsync('CloseCurrentColorPicker');
        }
    };

    document.addEventListener('click', clickOutsideHandler);
};

// 移除外部点击监听
window.removeClickOutsideListener = function() {
    if (clickOutsideHandler) {
        document.removeEventListener('click', clickOutsideHandler);
        clickOutsideHandler = null;
    }
};

步骤2:修改页面代码

@implements IDisposable
@inject IJSRuntime JSRuntime

<div class="container mt-3">
    @foreach(var picker in _pickers)
    {
         <button class="btn btn-primary me-2" 
                 @onclick="() => this.OnOpen(picker)">
            <div style="background-color:@picker.Color" class="buttonColor">&nbsp;</div>
            @picker.Title
        </button>

        <ColorPicker IsOpened="picker.IsOpen" 
                     MyColor="@picker.Color" 
                     Closed="(value) => this.OnClose(picker, value)"
                     ElementId="@($"color-picker-{picker.GetHashCode()}")" />
    }
</div>

@code{
    private List<ColorPickerData> _pickers = new();
    private DotNetObjectReference<YourComponentName> _dotNetRef; // 替换为你的组件类名

    protected override void OnInitialized()
    {
        _dotNetRef = DotNetObjectReference.Create(this);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        foreach (var picker in _pickers)
        {
            if (picker.IsOpen)
            {
                // 为当前打开的弹窗添加外部点击监听
                await JSRuntime.InvokeVoidAsync("addClickOutsideListener", 
                    _dotNetRef, 
                    $"color-picker-{picker.GetHashCode()}");
            }
            else
            {
                // 弹窗关闭时移除监听
                await JSRuntime.InvokeVoidAsync("removeClickOutsideListener");
            }
        }
        await base.OnAfterRenderAsync(firstRender);
    }

    protected override Task OnInitializedAsync()
    {
        _pickers.Add(new() { Title="Picker 1" });
        _pickers.Add(new() { Title="Picker 2" });
        _pickers.Add(new() { Title = "Picker 3" });
        return Task.CompletedTask;
    }

    private Task OnClose(ColorPickerData data, string value)
    {
        data.IsOpen = false;
        data.Color = value;
        // 移除监听
        JSRuntime.InvokeVoidAsync("removeClickOutsideListener");
        return Task.CompletedTask;
    }

    private Task OnOpen(ColorPickerData data)
    {
        // 关闭其他弹窗
        foreach(var p in _pickers)
        {
            p.IsOpen = false;
        }
        data.IsOpen = true;
        return Task.CompletedTask;
    }

    // JS调用的方法:关闭当前弹窗
    [JSInvokable]
    public void CloseCurrentColorPicker()
    {
        foreach(var picker in _pickers)
        {
            picker.IsOpen = false;
        }
        StateHasChanged();
    }

    public void Dispose()
    {
        _dotNetRef.Dispose();
        // 页面销毁时移除监听
        JSRuntime.InvokeVoidAsync("removeClickOutsideListener");
    }

    public class ColorPickerData
    { 
        public string Title { get; set; } = "Color Picker";
        public bool IsOpen { get; set; }
        public string Color { get; set; } = "#000000";
    }
}

步骤3:修改ColorPicker组件

添加ElementId参数,用于标识弹窗容器:

@if (IsOpened)
{
    <div id="@ElementId" class="color-picker-container p-2 border rounded bg-white">
        <!-- 原有颜色选择UI -->
        <button class="btn btn-sm btn-secondary mt-2" 
                @onclick="() => Closed.InvokeAsync(MyColor)">
            关闭
        </button>
    </div>
}

@code {
    [Parameter, EditorRequired]
    public bool IsOpened { get; set; }

    [Parameter, EditorRequired]
    public string MyColor { get; set; }

    [Parameter, EditorRequired]
    public EventCallback<string> Closed { get; set; }

    [Parameter, EditorRequired]
    public string ElementId { get; set; }

    // 组件原有逻辑...
}

为什么@onfocusout不生效?

@onfocusout依赖元素的焦点状态,但弹窗容器通常不是可聚焦元素(除非设置tabindex),且当焦点在弹窗内部子元素时,父容器的focusout会被触发,导致误关闭,因此不适用于弹窗外部点击关闭场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:54:53