更新时间:2023-11-30 22:02:40
问题是按住SHIFT + TAB键,则会看到relatedTarget
已按预期设置,因为在这种情况下该按钮确实获得了焦点.
我将运行代码以将div隐藏一段时间,以使click
处理程序有机会先运行.
在下面的示例中,我实现了此建议,并添加了一些日志记录,以使查看工作情况更加容易:
let openState = false;
let contentDiv = document.getElementById("show-hide-content");
let accordionDiv = document.getElementById("accordion");
let showHideButton = document.getElementById("show-hide-toggle");
function setOpenState(state) {
openState = state;
if(openState) {
contentDiv.style.visibility = "visible";
contentDiv.focus();
}
else {
contentDiv.style.visibility = "hidden";
}
}
function buttonClickHandler(event) {
console.log("click, setting openState to", !openState);
setOpenState(!openState);
}
function contentBlurHandler(event) {
console.log("blur, relatedTarget is", event.relatedTarget);
setTimeout(function() {
console.log("blur, after timeout, openState is", openState);
setOpenState(false);
}, 100);
}
showHideButton.onclick = buttonClickHandler;
showHideButton.onmousedown = function() {console.log("button mousedown"); };
contentDiv.onblur = contentBlurHandler;
showHideButton.onfocus = function(ev) { console.log("button onfocus"); }
.parent {
display: flex;
width: 100vw;
height: 100vh;
flex-direction: row;
background-color: #409958;
}
.accordion {
background-color: #28a7c9;
}
.show-hide-content {
visibility: hidden;
background-color: #c91e63;
}
<h1>Show/Hide Test</h1>
<div class="parent">
<div class="drawer">
<div class="accordion" id="accordion">
<button class="show-hide-toggle" id="show-hide-toggle">Show/Hide</button>
<div class="show-hide-content" id="show-hide-content" tabindex="-1">
<p>This is some content that should be shown or hidden depending on the toggle.</p>
</div>
</div>
<div class="spacer">
</div>
</div>
</div>
I'm trying to create a help message that will disappear when the user either clicks the toggle button to display the help message or clicks away by clicking elsewhere on the page. The solution appears to be to look at the relatedTarget
property of the onblur
event and prevent the onblur
handler from running when the relatedTarget
is the button to toggle the help message. This seems to work in Chrome, but in Firefox and Safari, the relatedTarget
property is set to the container div
rather than the button, which makes it impossible to distinguish between the toggle button click and a "click away".
I've created a simple demonstrator that illustrates the problem:
let openState = false;
let contentDiv = document.getElementById("show-hide-content");
let accordionDiv = document.getElementById("accordion");
let showHideButton = document.getElementById("show-hide-toggle");
function setOpenState(state) {
openState = state;
if(openState) {
contentDiv.style.visibility = "visible";
contentDiv.focus();
}
else {
contentDiv.style.visibility = "hidden";
}
}
function toggleVisibility(override) {
if (typeof override === "boolean") {
setOpenState(override);
}
else {
setOpenState(!openState);
}
}
function buttonClickHandler(event) {
toggleVisibility();
}
function contentBlurHandler(event) {
if(!accordionDiv.contains(event.relatedTarget)) {
toggleVisibility(false);
}
}
showHideButton.onclick = buttonClickHandler;
contentDiv.onblur = contentBlurHandler;
.parent {
display: flex;
width: 100vw;
height: 100vh;
flex-direction: row;
background-color: #409958;
}
.accordion {
background-color: #28a7c9;
}
.show-hide-content {
visibility: hidden;
background-color: #c91e63;
}
<h1>Show/Hide Test</h1>
<div class="parent">
<div class="drawer">
<div class="accordion" id="accordion">
<button class="show-hide-toggle" id="show-hide-toggle">Show/Hide</button>
<div class="show-hide-content" id="show-hide-content" tabindex="-1">
<p>This is some content that should be shown or hidden depending on the toggle.</p>
</div>
</div>
<div class="spacer">
</div>
</div>
</div>
This code works correctly in Chrome. However, in Firefox, clicking the "Show/Hide" button displays the hidden content, but doesn't hide it when the button is clicked again. As far as I can tell, this is because the onblur
handler is hiding the div, and then the onclick
handler is toggling it open again, even though I'm checking onblur.relatedTarget
to ensure that it's not anywhere inside the drawer.
What is the correct way to detect click-away in Firefox?
The problem is that clicking the <button>
doesn't focus it on some browser/OS combinations (notably on macOS except in Chrome), so onblur
's event.relatedTarget
is null
as nothing on the page receives focus. If you SHIFT+TAB from the <div id="show-hide-content">
, you'll see that relatedTarget
is set as you expect, as the button does receive focus in this scenario.
I would run the code to hide the div off a small timeout, to give the click
handler a chance to run first.
In the example below I implemented this suggestion and added some logging to make it easier to see what's going on:
let openState = false;
let contentDiv = document.getElementById("show-hide-content");
let accordionDiv = document.getElementById("accordion");
let showHideButton = document.getElementById("show-hide-toggle");
function setOpenState(state) {
openState = state;
if(openState) {
contentDiv.style.visibility = "visible";
contentDiv.focus();
}
else {
contentDiv.style.visibility = "hidden";
}
}
function buttonClickHandler(event) {
console.log("click, setting openState to", !openState);
setOpenState(!openState);
}
function contentBlurHandler(event) {
console.log("blur, relatedTarget is", event.relatedTarget);
setTimeout(function() {
console.log("blur, after timeout, openState is", openState);
setOpenState(false);
}, 100);
}
showHideButton.onclick = buttonClickHandler;
showHideButton.onmousedown = function() {console.log("button mousedown"); };
contentDiv.onblur = contentBlurHandler;
showHideButton.onfocus = function(ev) { console.log("button onfocus"); }
.parent {
display: flex;
width: 100vw;
height: 100vh;
flex-direction: row;
background-color: #409958;
}
.accordion {
background-color: #28a7c9;
}
.show-hide-content {
visibility: hidden;
background-color: #c91e63;
}
<h1>Show/Hide Test</h1>
<div class="parent">
<div class="drawer">
<div class="accordion" id="accordion">
<button class="show-hide-toggle" id="show-hide-toggle">Show/Hide</button>
<div class="show-hide-content" id="show-hide-content" tabindex="-1">
<p>This is some content that should be shown or hidden depending on the toggle.</p>
</div>
</div>
<div class="spacer">
</div>
</div>
</div>