【问题标题】:Event for click outside a div or element to close it on Blazor在 Blazor 上单击 div 或元素外部以将其关闭的事件
【发布时间】:2020-08-05 17:34:08
【问题描述】:

在我的Blazorserver-side 项目中,我需要通过单击菜单外部来关闭弹出菜单。 我使用一个简单的If 语句通过触发onClick 事件来显示/隐藏弹出窗口。但是没有这样的事件可以通过在弹出菜单之外单击来关闭弹出窗口。因此用户只能通过单击带有onClick 事件的元素来关闭它。 所以我的问题是我们如何在不使用JS 的情况下更好地解决这个问题?

提前谢谢你。

【问题讨论】:

    标签: c# .net-core blazor razor-pages blazor-server-side


    【解决方案1】:

    我想出了一个不使用 JS 的解决方案,使用 onmouseover 事件我们可以将 bool var 的值设置为 true,使用 onmouseout 事件我们可以设置为 false 等,验证鼠标是否点击了标签元素

     <button @onclick="Show">Click</button>
    <div  @onmouseover="MouseIn" @onmouseout="MouseOut">...</div>
        
    @code{
    bool IsInside; 
    void Show()
    {
       if(!IsInside)
       {
           ...
       }
    }
    void MouseIn()
    {
       IsInside= true
    }; 
    void MouseOut()
    {
       IsInside= False;
    }
    }
    

    【讨论】:

    • +1。我认为这是最好的方法。我认为这是 Blazor 的思维方式。其他人使用的 JSInterop 方法不值得麻烦。
    【解决方案2】:

    添加一个&lt;div&gt; 元素,它是屏幕大小,z-indexed 比弹出菜单低 1,但高于应用程序的其余部分。您需要在 CSS 类中使用position: fixed,然后使用width: 100%; height: 100%; top: 0%; left: 0%;,这样它就会填满页面。初始 CSS 显示值应为 display: none;。当您打开弹出窗口时,还将 div 的显示属性更改为 display: flex 以将其添加到 DOM,然后在此浮动 div 上添加一个 @OnClick 单击处理程序,该处理程序将关闭弹出窗口并将浮动 div 显示属性设置回来到display: none;。 div 应该是清晰的,因此您仍然可以看到它后面的应用程序的其余部分,但允许您在弹出窗口之外单击您正在寻找的行为,因为弹出窗口之外的任何地方都将在覆盖屏幕其余部分的 div 上.

    请注意,在弹出窗口中,当您从弹出窗口内部关闭弹出窗口时,您还需要将浮动 Div 显示值设置回“无”,否则它会一直停留并需要额外单击才能使其运行离开。

    您还可以用透明度稍微遮蔽 Div 元素,以便在弹出窗口打开时为其提供突出显示效果。

    【讨论】:

    • Thnx @NikProtsman 为您提供建议,但是,这是一种我不喜欢将其用于实际项目的 hack。在这种情况下,您可以使用JS 轻松地为窗口分配一个功能,以便它获得所有点击,然后您可以过滤它并将其用于您的特定目标。虽然,这仍然不是我的口味:D
    【解决方案3】:

    这是我使用 div 来触发 onfocusout 事件的方法,在该事件中,if 语句使用 ShowSelectBox 变量来显示或隐藏某些内容:

    <div tabindex="0" @onfocusout="@(() => ShowSelectBox = false)">
    ...
    </div>
    

    我在 blazor webassambly 应用程序中使用了它,但对于服务器端应该是相同的。

    【讨论】:

    • 不幸的是,这似乎不起作用,因为首先您需要为弹出窗口提供焦点。因此,如果您单击弹出窗口,然后单击外部,它将被关闭。而且 AFAIK 你不能在不使用 JS 的情况下将窗口设置为焦点,这里也提到了:stackoverflow.com/questions/60309188/…
    • “然后在外面点一下,它就会被关闭”——这不是你的本意吗?
    • 问题是,如果你不点击弹出窗口它不会获得焦点,所以点击其他任何地方都不会关闭它。您必须先单击弹出窗口以使其获得焦点,以便在其外部单击以将其关闭。
    【解决方案4】:

    我遇到了同样的情况,最终我使用了 css :hover state。假设您有一个下拉菜单弹出窗口,当您将鼠标悬停在 div 元素时,将显示菜单弹出窗口,当您将鼠标悬停时,菜单弹出窗口将自动关闭。

    你可以这样做,

    <div class="dropdown is-right is-hoverable @(PopupCollapsed ? "is-active" : null)"
                 @onclick="e => this.PopupCollapsed = !this.PopupCollapsed"
                 @onfocusout="e => this.PopupCollapsed = false">
    </div>
    
    @code {
    public bool PopupCollapsed { get; set; }
    }
    

    【讨论】:

    • 我认为 onfocusout 是一个非常好的主意,问题是如果导致 focusout 的点击在弹出框上并且您不希望它关闭 onclick
    【解决方案5】:

    我的解决方案是仅使用 blazor。 所有下拉列表都来自一个组件,它控制单击它。如果用户单击关闭的下拉菜单将其打开,则实际上打开的下拉菜单已关闭。或者,如果用户单击正文页面的主 div 或此 div 上的任何对象(不是下拉菜单),则实际打开的下拉菜单也会关闭。属性 stopPropagation 在下拉菜单上设置为 true 以阻止主 div 单击事件以阻止关闭下拉菜单实际打开自身。 下拉菜单有自己的点击事件,因为它还包含一个用于搜索下拉项目的输入元素,点击它会关闭下拉菜单。 它也应该适用于 DatePisker、ColorPicker 和其他实际打开 div 的元素。 我尝试了这个解决方案,看起来它可以很好地工作

    为所有下拉菜单创建类

    public class DropDownState
    {
        public string OpenedDropDownId { get; private set; }
    
        public event Action OnChange;
    
        public void SetOpened(string Id)
        {
            OpenedDropDownId = Id;
            NotifyStateChanged();
        }
    
        private void NotifyStateChanged() => OnChange?.Invoke();
    }
    

    到 Program.cs 添加

    builder.Services.AddScoped<Data.DropDownState>();
    

    下拉组件看起来像这样,停止传播对于这个解决方案很重要

    @inject Data.DropDownState DropDownStateService
    @implements IDisposable
    
    <div class="form-group" @onclick="ObjectClick" 
        @onclick:stopPropagation="true">
        <div class="dropdown">
           ...
    
    // each dropdown needs own unique id
    [Parameter]
    public string ComponentId { get; set; }
    
    bool showResults = false;
    protected override async Task OnInitializedAsync()
    {
        DropDownStateService.OnChange += DropDownOdherOpened;
    }
    public void Dispose()
    {
        DropDownStateService.OnChange -= DropDownOdherOpened;
    }
    async Task DropdownShow(bool _showResults)
    {
        showResults = _showResults
        if (showResults)
        {
            // this informs other dropdowns that this dropdown is opened an other dropdown go to closed state
            DropDownStateService.SetOpened(ComponentId);
        }
    }
    
    public void DropDownOdherOpened()
    {
        if (DropDownStateService.OpenedDropDownId != ComponentId && showResults)
        {
            // close dropdown
            DropdownShow(false);
            StateHasChanged();
        }
    }
    
    // this closes other dropdowns on click, this replaces main div click event functionality
    public async Task ObjectClick()
    {
        DropDownStateService.SetOpened(ComponentId);
    }
    

    并在 MainLayout.razor 添加 onclick

    @inject Data.DropDownState DropDownStateService
    
    <div class="container-fluid" @onclick="ObjectClick">
        @Body
    </div>
    
    public async Task ObjectClick()
    {
        DropDownStateService.SetOpened("Body");
    }
    

    【讨论】:

      【解决方案6】:

      这有点小题大做,但通过一些 JS 互操作来管理它。我是 Blazor 的新手,但我设法从其他各种帖子中找到了这一点;

      向您的弹出窗口添加一个 id - 称为我的个人资料弹出窗口

      <div id="profile-popup">
              <RadzenProfileMenu @ref="profileMenu" Style="width: 200px;" >
                  <Template>
                      <div class="row">...
      

      创建一个 JS 文件以将处理程序附加到文档的单击事件 - 如果单击源在您的弹出窗口中,请忽略它,否则从您的帮助程序类中触发一个帮助程序方法

          window.attachHandlers = (dotnetHelper) => {
          document.addEventListener("click", (evt) => {
              const profileElement = document.getElementById("profile-popup");
              let targetElement = evt.target;
      
              do {
                  if (targetElement == profileElement) {
                      //return as not clicked outside
                      return;
                  }            
                  targetElement = targetElement.parentNode;
              } while (targetElement);
      
              dotnetHelper.invokeMethod("InvokeAction");
          });
      };
      

      创建助手类

      public class ProfileOutsideClickInvokeHelper
      {
          Action _action;
      
          public ProfileOutsideClickInvokeHelper(Action action)
          {
              _action = action;
          }
      
          [JSInvokable]
          public void InvokeAction()
          {
              _action.Invoke();
          }
      }
      

      在 OnAfterRender 覆盖中附加处理程序。我有一个包含弹出窗口的组件。您需要处理对象引用

      public partial class TopBanner : IDisposable
      {        
          [Inject]
          IJSRuntime JSRuntime { get; set; }
      
          public void CloseProfileMenu()
          {
              profileMenu.Close();
          }
      
          DotNetObjectReference<ProfileOutsideClickInvokeHelper> _objRef;
      
          protected override void OnAfterRender(bool firstRender)
          {
              _objRef = DotNetObjectReference.Create(new ProfileOutsideClickInvokeHelper(CloseProfileMenu));
              JSRuntime.InvokeVoidAsync("attachHandlers", _objRef);
          }
      
          public void Dispose()
          {
              _objRef?.Dispose();
          }
      }
      

      不确定这是否是最好的解决方案,甚至是一个好的解决方案,但它似乎工作正常。

      【讨论】:

      • 我正在尝试实现此解决方案,但我遇到了一个问题,即在显示弹出窗口后立即调用处理程序,在使其可见后立即将其关闭。你怎么解决这个问题?我在想一个讨厌的 hack,简单地使用一个计时器,不允许它在 500 毫秒内发生。
      【解决方案7】:

      我制作的自定义下拉菜单也遇到了同样的问题,我觉得任何看起来像下拉菜单的东西的标准行为应该是如果你在它之外点击它会自行关闭。

      我使用一个自定义组件解决了这个问题,该组件将一个全屏 div 放在下拉列表下方(z-index 明智),并向该 div 添加了一个 onclick 事件回调。这样做感觉有点不正统,而且它可能不适合所有情况,但是嘿,它可以工作,并且不需要 JavaScript!

      在我的 .razor 页面上:

      @if(showUserDD)
      {
          <OutsideClickDetector MethodToCallOnClick="@ToggleUserDD" LowestInsideComponentZIndex="1000" />
          <div id="UserDDContent" style="z-index: 1000" class="list-group position-absolute shadow">
              ... some content ...
          </div>
      }
      
      @code {
          private async Task ToggleUserDD() => showUserDD = !showUserDD;
      }
      

      OutsideClickDetector.razor

      <div @onclick="OnClick" class="vh-100 vw-100 position-fixed"
           style="top: 0; left: 0; z-index: @(LowestInsideComponentZIndex-1);
               background-color: @(TransparentOutside ? "none" : "rgba(0,0,0,0.5)");"
       />
      
      @code
      {
          [Parameter] public int LowestInsideComponentZIndex { get; set; }        
          [Parameter] public bool TransparentOutside { get; set; } = true;
          [Parameter] public EventCallback MethodToCallOnClick { get; set; }
          private async Task OnClick() => await MethodToCallOnClick.InvokeAsync(null);
      }
      

      出于调试目的,如果您想查看全屏 div,可以将 TransparentOutside 设置为 false。

      【讨论】:

        【解决方案8】:

        这是我的解决方案。这仅适用于 Blazor/CSS,并且适用于大多数流行的现有弹出/滑块/offcanvas 类型 css(例如:引导程序)。

        打开弹出窗口/滑块等的按钮:

        <a @onclick="ToggleUserPanel">Open</a>
        

        弹出/滑块等:

        <div id="quick_user" class="offcanvas offcanvas-right p-10 @UserPanelClass">
        <a href="javascript:;" class="btn btn-primary" id="quick_user_close">
                            <i @onclick="ToggleUserPanel"></i>
        </a>
        </div>
        

        重要部分,叠加层。大多数基本 css 库将使用带有弹出窗口的 Overlay,这样做的好处是您无需担心为屏幕的“非”弹出部分设置 div 样式。如果您不使用库,则可以很容易地为 z 索引覆盖编写自己的样式。

        @if (UserPanelClass != "")
        {
            <div class="offcanvas-overlay" @onclick="ToggleUserPanel"></div>
        }
        

        BLAZOR STUFF

        @code{
            private string UserPanelClass = "";
            //This will either show the User Panel along with it's overlay
            //or close the User Panel and remove the overlay.
            //It can be triggered by any buttons that call it or (the solution to this 
            //post) clicking on the "Non Pop-Up" part of the screen which is the 
            //overlay div
            private void ToggleUserPanel()
            {
                if (String.IsNullOrEmpty(UserPanelClass))
                    UserPanelClass = "offcanvas-on";
                else
                    UserPanelClass = "";
        
            }
        }
                    
        

        就是这样。

        【讨论】:

          【解决方案9】:

          Github 存储库中找到了解决方案。

              <OutsideHandleContainer OnClickOutside=@OnClickOutside>
                  ...
                  <div>Sample element</div>
                  ...
              </OutsideHandleContainer>
              
              @functions{
                  void OnClickOutside()
                  {
                      // Do stuff
                  }
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-07-08
            • 2019-02-17
            • 1970-01-01
            • 1970-01-01
            • 2016-04-24
            相关资源
            最近更新 更多