58

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?

Jespertheend
  • 1,814
  • 19
  • 27
Marc Gerrit Langer
  • 607
  • 1
  • 5
  • 6

10 Answers10

98

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.

Northern
  • 2,338
  • 1
  • 17
  • 20
32

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');
Nagy Zoltán
  • 659
  • 8
  • 7
  • 1
    This one is really precise and simple. The only drawback is probably lack of IE <9 support, which is quite acceptable nowadays. – Kostiantyn Ko Jun 23 '21 at 20:22
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>
Mohammad
  • 21,175
  • 15
  • 55
  • 84
8

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.

Jespertheend
  • 1,814
  • 19
  • 27
5

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
}
Adam Šipický
  • 838
  • 7
  • 6
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>
Ritesh Jagga
  • 1,387
  • 1
  • 11
  • 23
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);
1

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)
aduensing
  • 11
  • 1
  • 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? – chrslg Nov 06 '22 at 16:51
  • 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. – PJ Eby Aug 13 '23 at 22:29
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 }
jfudman
  • 94
  • 4
-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);
omri_saadon
  • 10,193
  • 7
  • 33
  • 58