63

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?

6
  • 1
    Possible duplicate of Javascript detect if input is focused Commented Nov 19, 2018 at 7:34
  • 1
    It is not. Please read my question Commented Nov 19, 2018 at 7:54
  • 2
    ancestorElement:focus-within? Commented Nov 19, 2018 at 7:55
  • 1
    Array.from(document.querySelectorAll(selector)).some(node => node.contains(document.activeElement)) maybe. Commented Nov 19, 2018 at 7:58
  • 1
    Actually using :focus or :focus-within is better and easier. Commented Nov 19, 2018 at 8:06

10 Answers 10

108

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

el.contains(document.activeElement)

This will check if the currently focused element is a descendant of el.

Beware, as this also evaluates to true if the currently focused element (activeElement) is the element you're calling Node.contains on. E.g. el.contains(el) is true. If you want to ensure that the focused element is inside the container, but not the container itself, use the following snippet.

el.contains(document.activeElement) && el !== document.activeElement

If you have multiple elements to check, you can use a some function to iterate.

Sign up to request clarification or add additional context in comments.

Comments

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');

1 Comment

This one is really precise and simple. The only drawback is probably lack of IE <9 support, which is quite acceptable nowadays.
12

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.

Comments

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>

3 Comments

Can you explain why you put it inside a setTimeout? Thanks!
@carl-johan.blomqvist It is just for better understanding! For testing code you need to focus/unfocus on input. So timeout return response after 2 seconds
Maybe your example could use a different id than div, that was a bit confusing
7

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
}

Comments

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>

Comments

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)

2 Comments

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?
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.
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);

Comments

0

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);

2 Comments

I know this. But this solves not my problem. I want to know, if the active element is somewhere inside my element.
You can get the element via getElementById and check if this is the activeElement. Added to my answer. @MarcGerritLanger
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 }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.