如何使用JavaScript检查元素是否有焦点子元素?

59

我正在尝试将我的代码中的所有jQuery移除。 目前为止,我使用了

if ($(selector).find(':focus').length === 0) {
    // focus is outside of my element
} else {
    // focus is inside my element
}

您能否向我展示一种不需要使用jQuery的方法来区分焦点是否位于我的元素内部?


1
可能是Javascript detect if input is focused的重复问题。 - Mohammad
1
不是这样的,请仔细阅读我的问题。 - Marc Gerrit Langer
1
Array.from(document.querySelectorAll(selector)).some(node => node.contains(document.activeElement)) 或许可以。 - Felix Kling
@FelixKling:太棒了。我认为这就是我一直在寻找的解决方案。谢谢你。 - Marc Gerrit Langer
1
实际上,使用:focus:focus-within更好、更容易。 - Felix Kling
10个回答

98
你可以使用原生的 DOM 方法 Node.contains 来完成此操作。
el.contains(document.activeElement);

将检查activeElement是否是el的子孙节点。 如果你有多个元素需要检查,可以使用some函数来进行迭代。


32
使用Element的matches()方法和一个简单的选择器字符串,是可能的,具体如下:
let hasFocused = elem.matches(':focus-within:not(:focus)');
let focusedOrHasFocused = elem.matches(':focus-within');

1
这个非常精确和简单。唯一的缺点可能是缺乏IE <9支持,但这在现今是相当可以接受的。 - Kostiantyn Ko

9

querySelectorAll() 中使用 CSS 伪类 :focus

setTimeout(function(){
  if (document.querySelectorAll("div :focus").length === 0)
    console.log("not focused");
  else
    console.log("focused")
}, 2000);
<div>
  <input type="text">
</div>


1
你能解释一下为什么要把它放在setTimeout里面吗?谢谢! - carl-johan.blomqvist
1
@carl-johan.blomqvist 这只是为了更好地理解!为了测试代码,您需要聚焦/取消聚焦输入。因此,超时将在2秒后返回响应。 - Mohammad
也许你的示例可以使用一个不同于“div”的id,这有点令人困惑。 - Aaron Beall

8
根据您的情况,使用事件可能会更有效率。
在这种情况下,您可以使用 focusinfocusout 事件。
const el = document.getElementById("myEl");
el.addEventListener("focusin", () => console.log("focus!"));
el.addEventListener("focusout", () => console.log("blur!"));

请注意,在focusout事件期间,document.activeElement将是文档主体。为了解决这个问题,您可以利用FocusEvent.relatedTarget

6
如果你发现在模糊事件之后,document.activeElement返回<body>元素,那么你只需要使用setTimeout()包装它,就能返回正确的元素。
handleBlur() {
    setTimeout(() => { 
        console.log(document.activeElement); // this actually return active/focused element
    });
}

如果你正在使用没有超时的独立程序

handleBlur() {
    console.log(document.activeElement); // this is returning <body> element
}

2

结合了这里发布的一些答案。使用focusinfocusoutcontainsrelatedTarget的组合,您应该能够知道焦点是否在特定元素的子元素上。

const elm = document.getElementById('check-focus-here')
elm.addEventListener('focusin', (event) => {
  console.log(event.target, event.relatedTarget)
  // console.log(elm.contains(event.relatedTarget))
})

elm.addEventListener('focusout', (event) => {
  console.log(event.target, event.relatedTarget)
  console.log(elm.contains(event.relatedTarget))
})
#check-focus-here {
  border: 2px solid;
  padding: 8px;
  margin: 8px;
}
<div id="check-focus-here">
  <input id="first-name" type="text" />
  <input id="middle-name" type="text" />
  <input id="last-name" type="text" />
  <button type="button">Save Name</button>
</div>

<button type="button">Tab to this for outside focus</button>


2

所有现有的非基于CSS的解决方案都没有考虑JavaScript上下文与呈现节点的框架不匹配的情况。为了解决这个问题,您需要执行以下操作:

el.contains(el.ownerDocument.activeElement)

请在回答已有赞同答案的旧问题时,详细阐述一下。特别是,这与得到最多赞同(几乎相同)的答案有何不同? - chrslg
答案是它考虑了el可能在iframe或者与当前window不同的窗口中的情况。这是你在开发Obsidian桌面应用时需要注意的一点。在那里,document始终是主窗口,但你的代码需要能够处理其他窗口中的元素。 - PJ Eby

1
这是一个关于编程的工作示例,遵循@Northern和@Adam Šipický的回答...
const tr = document.querySelector("table tbody tr");

tr.addEventListener('blur', () => {
  setTimeout(() => {
    if (!tr.contains(document.activeElement)) {
      // Execute your condition code here...
    }
  }, 200);
}, true);

0
在2021年,您可能完全可以避免使用JavaScript来检查元素或任何元素的子节点是否具有焦点 - 除非您正在操作父元素之外的DOM元素。
例如:
<div class="parent">
  <button>foo</button>
  <button>food</button>
  <button>foosh</button>
</div>

.parent { background: white }
.parent:focus-within { background: red }
.parent:focus-within button:not(:focus) { opacity: .5 }

-1

要检索所选元素,您可以使用:

let activeElement = document.activeElement

检查特定元素:

let elem = document.getElementById('someId');

let isFocused = (document.activeElement === elem);

1
我知道这个。但这并不能解决我的问题。我想知道,活动元素是否在我的元素内部。 - Marc Gerrit Langer
你可以通过 getElementById 获取元素,并检查它是否为 activeElement。已添加到我的回答中。@MarcGerritLanger - omri_saadon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接