How to check if element has focused child using javascript?
Asked Answered
C

10

61

I'm trying to remove all jQuery from my code. Until now I used

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

to distinguish wether the focus is inside of one of my elements. Can you show me a jQuery-free way of doing it?

Cioffred answered 19/11, 2018 at 7:30 Comment(6)
Possible duplicate of Javascript detect if input is focusedErnestinaernestine
It is not. Please read my questionCioffred
ancestorElement:focus-within?Tented
Array.from(document.querySelectorAll(selector)).some(node => node.contains(document.activeElement)) maybe.Manizales
@FelixKling : Perfect. I think this is the solution, that i was looking for. Thank youCioffred
Actually using :focus or :focus-within is better and easier.Manizales
D
105

You can use Node.contains native DOM method for this.

el.contains(document.activeElement);

will check if activeElement is a descendant of el. If you have multiple elements to check, you can use a some function to iterate.

Devolution answered 12/4, 2019 at 6:9 Comment(0)
H
33

It is possible with Element's matches() method and with a simple selector string as follows:

let hasFocused = elem.matches(':focus-within:not(:focus)');
let focusedOrHasFocused = elem.matches(':focus-within');
Heisler answered 5/8, 2020 at 18:27 Comment(1)
This one is really precise and simple. The only drawback is probably lack of IE <9 support, which is quite acceptable nowadays.Chrysler
E
9

Use CSS :focus pseudo-class in querySelectorAll()

setTimeout(function(){
  if (document.querySelectorAll("div :focus").length === 0)
    console.log("not focused");
  else
    console.log("focused")
}, 2000);
<div>
  <input type="text">
</div>
Ernestinaernestine answered 19/11, 2018 at 8:2 Comment(3)
Can you explain why you put it inside a setTimeout? Thanks!Clementineclementis
@Clementineclementis It is just for better understanding! For testing code you need to focus/unfocus on input. So timeout return response after 2 secondsErnestinaernestine
Maybe your example could use a different id than div, that was a bit confusingEyewitness
B
9

Depending on your situation, using events might be more performant.

You can use the focusin and focusout events in that case.

const el = document.getElementById("myEl");
el.addEventListener("focusin", () => console.log("focus!"));
el.addEventListener("focusout", () => console.log("blur!"));

Note that during focusout events the document.activeElement will be the document body. To work around this issue, you can make use of FocusEvent.relatedTarget.

Britain answered 3/7, 2021 at 23:22 Comment(0)
R
6

If you have issue where document.activeElement is returning <body> element after blur event, you just need to wrap it with setTimeout() and it will return correct element.

handleBlur() {
    setTimeout(() => { 
        console.log(document.activeElement); // this actually return active/focused element
    });
}

if you are using it standalone without timeout

handleBlur() {
    console.log(document.activeElement); // this is returning <body> element
}
Roughrider answered 28/7, 2020 at 14:26 Comment(0)
R
2

Combined some of answers posted here. Using a combination of focusin, focusout, contains and relatedTarget, you should be able to know when focus is on the children of a particular element.

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>
Revert answered 10/7, 2021 at 16:11 Comment(0)
D
2

None of these existing non CSS based solutions account for the situation where the JavaScript context does not match the frame the node was rendered in. To account for this you would want to do something like the following:

el.contains(el.ownerDocument.activeElement)
Deduction answered 1/11, 2022 at 17:55 Comment(2)
Please, when answering old questions with upvoted answers, elaborate a bit. In particular, how is this different from the most upvoted (and almost identical) answer?Ouse
The answer is that it accounts for situations where el might be in an iframe or a different window than the current window. This is something you need to watch out for when developing for the Obsidian desktop app, for example. There, document is always the main window, but your code needs to work even if it's dealing with elements in another window.Mastaba
F
1

Here's a working example following @Northern and @Adam Šipický answers...

const tr = document.querySelector("table tbody tr");

tr.addEventListener('blur', () => {
  setTimeout(() => {
    if (!tr.contains(document.activeElement)) {
      // Execute your condition code here...
    }
  }, 200);
}, true);
Frigorific answered 21/6, 2021 at 15:46 Comment(0)
F
0

In 2021 you can probably avoid javascript altogether to check if an element or any of the element's child nodes have focus – unless you are manipulating DOM elements outside of a parent element.

For example:

<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 }
Forborne answered 16/8, 2021 at 13:24 Comment(0)
T
-1

To retrieve the selected element you can use:

let activeElement = document.activeElement

To check a specific element:

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

let isFocused = (document.activeElement === elem);
Tanny answered 19/11, 2018 at 7:34 Comment(2)
I know this. But this solves not my problem. I want to know, if the active element is somewhere inside my element.Cioffred
You can get the element via getElementById and check if this is the activeElement. Added to my answer. @MarcGerritLangerTanny

© 2022 - 2024 — McMap. All rights reserved.