简体   繁体   English

纯JS幻灯片菜单,能够“点击菜单外点击”

[英]Pure JS Slide-Menu, ability to close it “on click outside of the menu”

I am trying to rephrase my question and will go through all the steps i did and especially where i failed. 我试着改写我的问题,并将完成我所做的所有步骤,特别是在我失败的地方。 I don't have a deep knowledge of JS but the will to learn by practice as well as the help of the community. 我对JS没有深入的了解,但对实践以及社区的帮助有了学习的意愿。

I stumbled across this answer and realized the benefit. 我偶然发现了这个答案并意识到了这个好处。 Since i don't want to use jQuery i started to rewrite it in JS. 由于我不想使用jQuery,我开始在JS中重写它。

  1. First step was a to write a basic simple function to open the menu on 'click' and close it on a click outside of the focused element using the blur(); 第一步是编写一个基本的简单函数来打开“click”菜单,然后使用blur()在焦点元素外面的一个点击上关闭它。 method. 方法。

Reference jQuery code from @zzzzBov : @zzzzBov引用jQuery代码:

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});

My JS code: 我的JS代码:

var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0];
var navMenu = document.getElementsByClassName('js-site-nav')[0];

navToggle.addEventListener('click', function() {
  this.focus();
  navMenu.classList.toggle('js-site-nav--open');
});

navMenu.addEventListener('blur', function() {
  this.classList.remove('js-site-nav--open');
}, true);

Opening the menu works, the problem is that it will only close on 'click' outside of the menu if the focused element (Menu) is clicked once before: 打开菜单有效,问题是如果在之前单击一次聚焦元素(菜单),它将仅在菜单外部的“单击”时关闭:

 var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0]; var navMenu = document.getElementsByClassName('js-site-nav')[0]; navToggle.addEventListener('click', function() { this.focus(); navMenu.classList.toggle('js-site-nav--open'); }); navMenu.addEventListener('blur', function() { this.classList.remove('js-site-nav--open'); }, true); 
 .c-site-nav { color: black; list-style-type: none; padding-top: 20px; position: fixed; overflow: hidden; top: 0; right: -200px; width: 200px; height: 100%; transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); opacity: .9; background-color: green; } .js-site-nav--open { right: 0; } .c-site-nav-btn:hover { cursor: pointer; background-color: red; } .c-site-nav-btn { position: fixed; top: 20px; right: 20px; border: 0; outline: 0; background-color: black; position: fixed; width: 40px; height: 40px; } .c-site-nav-btn__line { width: 20px; height: 2px; background-color: white; display: block; margin: 5px auto; } 
 <button class="c-site-nav-btn js-site-nav-btn--toggle"> <span class="c-site-nav-btn__line"></span> <span class="c-site-nav-btn__line"></span> <span class="c-site-nav-btn__line"></span> </button> <nav class="c-site-nav js-site-nav" tabindex="-1" role="navigation"> <ul class="c-site-nav__menu"> <li> <a class="c-site-nav__item js-site-nav__item" href="/">TOPMENU</a> </li> <li>SUBMENU <ul> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> </ul> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/portfolio">TOPMENU</a> </li> </ul> </nav> 

  1. I tried to continue with the second step, that was addressing to the two major issues: 我试图继续第二步,即解决两个主要问题:

The first is that the link in the dialog isn't clickable. 第一个是对话框中的链接不可点击。 Attempting to click on it or tab to it will lead to the dialog closing before the interaction takes place. 试图单击它或选项卡到它将导致对话关闭在交互发生之前。 This is because focusing the inner element triggers a focusout event before triggering a focusin event again. 这是因为聚焦内部元素会在再次触发focusin事件之前触发焦点事件。

The fix is to queue the state change on the event loop. 修复是在事件循环上对状态更改进行排队。 This can be done by using setImmediate(...), or setTimeout(..., 0) for browsers that don't support setImmediate. 这可以通过对不支持setImmediate的浏览器使用setImmediate(...)或setTimeout(...,0)来完成。 Once queued it can be cancelled by a subsequent focusin: 排队后,可以通过后续焦点取消:

The second issue is that the dialog won't close when the link is pressed again. 第二个问题是再次按下链接时对话框不会关闭。 This is because the dialog loses focus, triggering the close behavior, after which the link click triggers the dialog to reopen. 这是因为对话框失去焦点,触发关闭行为,之后链接单击触发对话框重新打开。

Similar to the previous issue, the focus state needs to be managed. 与前一个问题类似,需要管理焦点状态。 Given that the state change has already been queued, it's just a matter of handling focus events on the dialog triggers: 鉴于状态更改已经排队,只需在对话框触发器上处理焦点事件:

Reference jQuery code from @zzzzBov : @zzzzBov引用jQuery代码:

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

My JS code: 我的JS代码:

var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0];
var navMenu = document.getElementsByClassName('js-site-nav')[0];
var navLink = document.getElementsByClassName('js-site-nav__item')[0];

navToggle.addEventListener('click', function() {
  this.focus();
  navMenu.classList.toggle('js-site-nav--open');
});

navMenu.addEventListener('focus', function() {
  this.blur(function() {
    setTimeout(function() {
      this.classList.remove('js-site-nav--open');
    }.bind(this), 0);
  });
  this.focus(function() {
    clearTimeout();
  });
});

navLink.addEventListener('blur', function() {
  navLink.blur(function() {
    setTimeout(function() {
      navMenu.classList.remove('js-site-nav--open');
    }.bind(), 0);
  });
  navLink.focus(function() {
    clearTimeout();
  });
});

Opening the menu still works, but closing on click outside stoped working, after research i figured that blur and focus are the right methods but i guess i am missing something essential. 打开菜单仍然有效,但关闭点击外停止工作,研究后我认为模糊和焦点是正确的方法,但我想我错过了一些必要的东西。

 var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0]; var navMenu = document.getElementsByClassName('js-site-nav')[0]; var navLink = document.getElementsByClassName('js-site-nav__item')[0]; navToggle.addEventListener('click', function() { this.focus(); navMenu.classList.toggle('js-site-nav--open'); }); navMenu.addEventListener('focus', function() { this.blur(function() { setTimeout(function() { this.classList.remove('js-site-nav--open'); }.bind(this), 0); }); this.focus(function() { clearTimeout(); }); }); navLink.addEventListener('blur', function() { navLink.blur(function() { setTimeout(function() { navMenu.classList.remove('js-site-nav--open'); }.bind(), 0); }); navLink.focus(function() { clearTimeout(); }); }); 
 .c-site-nav { color: black; list-style-type: none; padding-top: 20px; position: fixed; overflow: hidden; top: 0; right: -200px; width: 200px; height: 100%; transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); opacity: .9; background-color: green; } .js-site-nav--open { right: 0; } .c-site-nav-btn:hover { cursor: pointer; background-color: red; } .c-site-nav-btn { position: fixed; top: 20px; right: 20px; border: 0; outline: 0; background-color: black; position: fixed; width: 40px; height: 40px; z-index:9999; } .c-site-nav-btn__line { width: 20px; height: 2px; background-color: white; display: block; margin: 5px auto; } 
 <button class="c-site-nav-btn js-site-nav-btn--toggle"> <span class="c-site-nav-btn__line"></span> <span class="c-site-nav-btn__line"></span> <span class="c-site-nav-btn__line"></span> </button> <nav class="c-site-nav js-site-nav" tabindex="-1" role="navigation"> <ul class="c-site-nav__menu"> <li> <a class="c-site-nav__item js-site-nav__item" href="/">TOPMENU</a> </li> <li>SUBMENU <ul> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> </ul> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/portfolio">TOPMENU</a> </li> </ul> </nav> 

I am sure there is still a lot i have to learn, but help would be much appreciated. 我相信我还有很多东西需要学习,但非常感谢帮助。 Thanks a lot guys. 非常感谢你们。

You could set the focus on navmenu as soon as it is displayed. 您可以在显示时立即将焦点设置在navmenu上。 If the user clicks outside of it, the blur event would be triggered and the menu would be removed. 如果用户点击它之外,将触发blur事件并删除菜单。 Since clicking on the links also triggers the blur event, we have to keep the menu on the screen when the users clicks anywhere inside of the menu. 由于点击链接也会触发blur事件,因此当用户点击菜单内的任何位置时,我们必须将菜单保留在屏幕上。 This can be monitored with a isMouseDown flag. 可以使用isMouseDown标志监视此情况。

Here is an enhanced version of the code snippet given in Part 1 of your question. 以下是问题第1部分中提供的代码段的增强版本。

 var navToggle = document.getElementsByClassName('js-site-nav-btn--toggle')[0]; var navMenu = document.getElementsByClassName('js-site-nav')[0]; var isMouseDown = false; navToggle.addEventListener('click', function() { this.focus(); navMenu.classList.toggle('js-site-nav--open'); navMenu.focus(); }); navMenu.addEventListener('mousedown', function() { isMouseDown = true; }); navMenu.addEventListener('mouseup', function() { isMouseDown = false; }); navMenu.addEventListener('mouseleave', function() { isMouseDown = false; }); navMenu.addEventListener('blur', function() { if (!isMouseDown) { navMenu.classList.remove('js-site-nav--open'); } }, true); 
 .c-site-nav { color: black; list-style-type: none; padding-top: 20px; position: fixed; overflow: hidden; top: 0; right: -200px; width: 200px; height: 100%; transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000); opacity: .9; background-color: green; } .js-site-nav--open { right: 0; } .c-site-nav-btn:hover { cursor: pointer; background-color: red; } .c-site-nav-btn { position: fixed; top: 20px; right: 20px; border: 0; outline: 0; background-color: black; position: fixed; width: 40px; height: 40px; } .c-site-nav-btn__line { width: 20px; height: 2px; background-color: white; display: block; margin: 5px auto; } 
 <button class="c-site-nav-btn js-site-nav-btn--toggle"> <span class="c-site-nav-btn__line"></span> <span class="c-site-nav-btn__line"></span> <span class="c-site-nav-btn__line"></span> </button> <nav class="c-site-nav js-site-nav" tabindex="-1" role="navigation"> <ul class="c-site-nav__menu"> <li> <a class="c-site-nav__item js-site-nav__item" href="/">TOPMENU</a> </li> <li>SUBMENU <ul> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a> </li> </ul> </li> <li> <a class="c-site-nav__item js-site-nav__item" href="/portfolio">TOPMENU</a> </li> </ul> </nav> 

I have recently come up against this same issue, and it's not as tricky as it sounds. 我最近遇到了同样的问题,并不像听起来那么棘手。 You need to give your trigger a 'tabindex' (to make it focusable, 0 is good). 你需要给你的触发器一个'tabindex'(使它可以集中,0是好的)。 Give it a 'click' event handler like so... 像这样给它一个'click'事件处理程序......

document.getElementById('myTrigger').addEventListener('click', function(){this.focus(); this.classList.toggle('openClass');});

Where 'openClass' is the one which triggers the menu. 'openClass'是触发菜单的那个。 Then (assuming var myTrigger)... 然后(假设var myTrigger)......

myTrigger.addEventListener('blur', function(){ this.classList.remove('openClass');})

Here, clicking the toggle switches the open class on and off, but it also prgramatically sets the focus. 在这里,单击切换可以打开和关闭打开的类,但它也可以设置焦点。 When clicking away, the element loses focus, the 'blur' event fires and the handler removes the class... 单击时,元素失去焦点,“模糊”事件触发,处理程序删除类...

I took a different approach. 我采取了不同的方法。 I use a toggleClass to determine wether or not the menu is open. 我使用toggleClass确定菜单是否打开。 Based on this classname I changed your css so the menu would open whenever the class 'showMenu' is added to our html tag. 根据这个类名,我改变了你的css,这样只要将类'showMenu'添加到我们的html标签中,菜单就会打开。

The code in the clickOutside method checks if you are clicking outside the given classNames (in this case that is .js-site-nav and .js-site-nav-btn--toggle). clickOutside方法中的代码检查您是否在给定的classNames外部单击(在这种情况下是.js-site-nav和.js-site-nav-btn - toggle)。 If the elements you clicked aren't the elements with the given classnames, then the menu will close. 如果您单击的元素不是具有给定类名的元素,则菜单将关闭。

Sorry for the bad markup in this response, I'm at work so when I'm home I'll try to improve this message. 对不起,这个回复中的标记不好,我正在工作,所以当我回家时,我会尝试改进这个消息。

Here is the code I used: 这是我使用的代码:

HTML HTML

<div class="container">

    <button class="c-site-nav-btn js-site-nav-btn--toggle">
        <span class="c-site-nav-btn__line"></span>
        <span class="c-site-nav-btn__line"></span>
        <span class="c-site-nav-btn__line"></span>
    </button>
    <nav class="c-site-nav js-site-nav" tabindex="-1" role="navigation">
        <ul class="c-site-nav__menu">
            <li>
                <a class="c-site-nav__item js-site-nav__item" href="/">TOPMENU</a>
            </li>
            <li>SUBMENU
                <ul>
                    <li>
                        <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
                    </li>
                    <li>
                        <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
                    </li>
                    <li>
                        <a class="c-site-nav__item js-site-nav__item" href="/">MENU</a>
                    </li>
                </ul>
            </li>
            <li>
                <a class="c-site-nav__item js-site-nav__item" href="/portfolio">TOPMENU</a>
            </li>
        </ul>
    </nav>
</div>

CSS CSS

.c-site-nav {
  color: black;
  list-style-type: none;
  padding-top: 20px;
  position: fixed;
  overflow: hidden;
  top: 0;
  right: -200px;
  width: 200px;
  height: 100%;
  transition: right .6s cubic-bezier(0.190, 1.000, 0.220, 1.000);
  opacity: .9;
  background-color: green;
}
.showMenu .js-site-nav {
  right: 0;
}
.c-site-nav-btn:hover {
  cursor: pointer;
  background-color: red;
}
.c-site-nav-btn {
  position: fixed;
  top: 20px;
  right: 20px;
  border: 0;
  outline: 0;
  background-color: black;
  position: fixed;
  width: 40px;
  height: 40px;
  z-index:9999;
}
.c-site-nav-btn__line {
  width: 20px;
  height: 2px;
  background-color: white;
  display: block;
  margin: 5px auto;
}

.container
{
    width: 100%;
    height: 100%;
    background: red;
}

JavaScript JavaScript的

var $parent = $('html');
var toggleClass = 'showMenu';
var container = $(".js-site-nav, .js-site-nav-btn--toggle");

function init()
{
    $('.js-site-nav-btn--toggle').on('click touchend', toggleMenu);
    $(document).on('click touchend', clickOutside);
}

function toggleMenu()
{
    $parent.toggleClass(toggleClass);
}

function clickOutside(e)
{ 

    if (!container.is(e.target) // if the target of the click isn't the container...
    && container.has(e.target).length === 0
    && $parent.hasClass(toggleClass)) // ... nor a descendant of the container
    {
        $parent.removeClass(toggleClass);
    }
}
init();

https://jsfiddle.net/h7drcett/9/ https://jsfiddle.net/h7drcett/9/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM