I’m rather late to the party, but I recently ran across JavaScript’s Element.matches()
, which allows you to check if a given CSS selector matches an element. Put another way, it tells you if an element would beselected if the DOM were being queried by something like document.querySelector()
.
Why I Needed It
Somewhere in TypeIt, I’m searching an array of nodes for the index of the node that has a specific CSS selector. At a super zoomed-in level during that process, I’m doing a version of this:
const nodeIndex = allNodes.findIndex(n => n.matches('.selector'));
It’s a little bit of a face-palm moment, because although it’s supported in browsers going back to IE9, I’ve been using less-than-ideal techniques I’ve used in the past. This is post was written to honor them before saying goodbye forever.
Using Element.closest()
Using .closest()
allows you to query up through the DOM tree rather than down, and it starts that query with the element on which it’s called. So, if the node itself matches that selector, you’ll get a truthy result. But this approach requires you to verify that the node you’ve found is the same node you’re interested in checking. Otherwise, you’ll still get a truthy result when .closest()
matches against a parent element that happens to have the same selector.
Wrapping up this functionality might look like so:
function hasSelector(element: HTMLElement, selector: string): boolean {
const matchedNode = node.closest(selector);
if (!matchedNode) {
return false;
}
return matchedNode === element;
}
There’s another downside worth noting. Because .closest()
will continue traversing up the tree when it doesn’t find a match, you’re taking on some performance baggage. After all, you’re only actually interested in checking one element, but .closest()
will keep searching all the way up the tree if it hasn’t found a match.
Checking the Parent’s Children for Same Node
You can’t use .querySelector()
on an element to check itself for a selector, but you can check each of the parent’s children for it. If any of those matched elements are the same node as the target, you’ve got what you need.
function hasSelector(element: HTMLElement, selector: string): boolean {
if (!element.parentElement) {
return false;
}
return Array.from(element.parentElement.querySelectorAll(selector)).some(
(node) => node === element
);
}
In terms of performance, this approach is better than the previous because the maximum amount of DOM you’ll crawl is the target element’s direct parent. But readability arguably suffers, and depending on your DOM tree, you still carry some performance overhead because you’re evaluating multiple sibling elements, even though you’re concerned with only one.
Welcome to the 21st Century
With the .matches()
method available, neither performance nor verbosity are problems anymore. Only one element — the target element — is ever being handled at a time. On top of that, the API is extremely clear. I’m still kicking myself that this thing has been around for years (and even longer if you consider the browser-specific implementations that existed before that). Hope you find it as useful as I have!
Top comments (0)