41

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,258
  • 14
  • 21
Marc Gerrit Langer
  • 437
  • 1
  • 4
  • 6

9 Answers9

76

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
  • 1,968
  • 1
  • 16
  • 19
17

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
  • 389
  • 3
  • 6
  • 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
  • 20,339
  • 15
  • 51
  • 79
3

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ý
  • 738
  • 5
  • 5
3

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

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

const el = document.getElemen
el.addEventListener("focusin", () => alert("focus!"));
el.addEventListener("focusout", () => alert("blur!"));
Jespertheend
  • 1,258
  • 14
  • 21
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

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,329
  • 1
  • 9
  • 22
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
  • 3
-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
  • 9,513
  • 6
  • 31
  • 58