处理下拉菜单的焦点状态
Handle dropdown's focus state
我目前正在制作纯 JavaScript 的下拉组件。当用户单击下拉开关时,内容获得焦点并显示其内容。当用户在其外部单击时,内容将失去焦点并隐藏。
到目前为止,效果很好。但是,我遇到了两个问题。
第一个是当下拉菜单中的元素被点击时(例如:锚标记),下拉菜单失去焦点,这是不应该的。
第二个是,在显示下拉内容的同时单击下拉切换,下拉应该关闭,而不是关闭然后重新打开,因为在下拉切换上注册了点击。
HTML:
<div class="dropdown">
<a href="#" class="dropdown-toggle">Dropdown toggle</a>
<div class="dropdown-content" tabindex="0">
<ul>
<li>
<a href="http://example.com">My profile</a>
</li>
<li>
<a href="http://example.com">Log out</a>
</li>
</ul>
</div>
</div>
CSS:
.dropdown {
position: relative;
}
.dropdown.is-open .dropdown-content {
display: block;
}
.dropdown-content {
display: none;
position: absolute;
top: 100%;
width: 100%;
padding: 0.5rem 0;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
JS:
class Dropdown {
constructor(element) {
this.element = element;
this.toggle = this.element.querySelector('.dropdown-toggle');
this.content = this.element.querySelector('.dropdown-content');
this.bindings();
}
bindings() {
this.toggle.addEventListener('click', this.open.bind(this));
this.content.addEventListener('keydown', this.handleKeyDown.bind(this));
this.content.addEventListener('focusout', this.close.bind(this));
}
open(e) {
e.preventDefault();
this.element.classList.add('is-open');
this.content.focus();
}
close(e) {
this.element.classList.remove('is-open');
}
handleKeyDown(e) {
if (e.keyCode === 27) {
this.close(e);
}
}
}
document.querySelectorAll('.dropdown').forEach(dropdown => new Dropdown(dropdown));
我一直在努力思考如何在没有任何运气的情况下解决这些问题。知道如何解决这些问题吗?
经过一些测试,我找到了一个可行的解决方案。试试下面的代码
class Dropdown {
constructor(element) {
this.element = element;
this.toggle = this.element.querySelector('.dropdown-toggle');
this.content = this.element.querySelector('.dropdown-content');
this.bindings();
}
bindings() {
this.toggle.addEventListener('click', this.open.bind(this));
this.content.addEventListener('keydown', this.handleKeyDown.bind(this));
this.element.addEventListener('focusout', this.onFocusOut.bind(this),true);
}
onFocusOut(e) {
if (!this.element.contains(e.relatedTarget)) {
this.close(e)
}
}
open(e) {
e.preventDefault();
this.element.classList.toggle('is-open');
this.content.focus();
}
close(e) {
e.preventDefault();
this.element.classList.remove('is-open');
}
handleKeyDown(e) {
if (e.keyCode === 27) {
this.close(e);
}
}
}
我目前正在制作纯 JavaScript 的下拉组件。当用户单击下拉开关时,内容获得焦点并显示其内容。当用户在其外部单击时,内容将失去焦点并隐藏。
到目前为止,效果很好。但是,我遇到了两个问题。
第一个是当下拉菜单中的元素被点击时(例如:锚标记),下拉菜单失去焦点,这是不应该的。
第二个是,在显示下拉内容的同时单击下拉切换,下拉应该关闭,而不是关闭然后重新打开,因为在下拉切换上注册了点击。
HTML:
<div class="dropdown">
<a href="#" class="dropdown-toggle">Dropdown toggle</a>
<div class="dropdown-content" tabindex="0">
<ul>
<li>
<a href="http://example.com">My profile</a>
</li>
<li>
<a href="http://example.com">Log out</a>
</li>
</ul>
</div>
</div>
CSS:
.dropdown {
position: relative;
}
.dropdown.is-open .dropdown-content {
display: block;
}
.dropdown-content {
display: none;
position: absolute;
top: 100%;
width: 100%;
padding: 0.5rem 0;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
JS:
class Dropdown {
constructor(element) {
this.element = element;
this.toggle = this.element.querySelector('.dropdown-toggle');
this.content = this.element.querySelector('.dropdown-content');
this.bindings();
}
bindings() {
this.toggle.addEventListener('click', this.open.bind(this));
this.content.addEventListener('keydown', this.handleKeyDown.bind(this));
this.content.addEventListener('focusout', this.close.bind(this));
}
open(e) {
e.preventDefault();
this.element.classList.add('is-open');
this.content.focus();
}
close(e) {
this.element.classList.remove('is-open');
}
handleKeyDown(e) {
if (e.keyCode === 27) {
this.close(e);
}
}
}
document.querySelectorAll('.dropdown').forEach(dropdown => new Dropdown(dropdown));
我一直在努力思考如何在没有任何运气的情况下解决这些问题。知道如何解决这些问题吗?
经过一些测试,我找到了一个可行的解决方案。试试下面的代码
class Dropdown {
constructor(element) {
this.element = element;
this.toggle = this.element.querySelector('.dropdown-toggle');
this.content = this.element.querySelector('.dropdown-content');
this.bindings();
}
bindings() {
this.toggle.addEventListener('click', this.open.bind(this));
this.content.addEventListener('keydown', this.handleKeyDown.bind(this));
this.element.addEventListener('focusout', this.onFocusOut.bind(this),true);
}
onFocusOut(e) {
if (!this.element.contains(e.relatedTarget)) {
this.close(e)
}
}
open(e) {
e.preventDefault();
this.element.classList.toggle('is-open');
this.content.focus();
}
close(e) {
e.preventDefault();
this.element.classList.remove('is-open');
}
handleKeyDown(e) {
if (e.keyCode === 27) {
this.close(e);
}
}
}