Checking To See If An Element Has A CSS Pseudo-Class In JavaScript
This morning, I sat down to see if I could detect "autofill" behaviors in the browser. In doing so, I came across several StackOverflow posts that mentioned a ":-webkit-autofill" pseudo-class getting applied to inputs in WebKit-based browsers. When I read this, it occurred to me that I didn't know how to use JavaScript in order to check to see if an input Element actually had a given pseudo-class applied to it. After some Googling, I came across a great post by Pallxk on working with pseudo-classes in JavaScript. That article turned me onto the Element.matches() method, which is what I wanted to play around with today.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
According to the Mozilla Developer Network (MDN), the Element.matches() method tests to see if the contextual node matches the given CSS selector. The .matches() method has solid support going back to IE9; though, IE uses a different method name (.msMatchesSelector() as opposed to .matches()). As such, we can pretty much depend on this method in all modern browsers.
To see the Element.matches() method in action, I've put together a simple demo that tests for a given set of CSS pseudo-classes on a single "username" form element. We can then use the Developer Tools to explicitly apply pseudo-classes like ":hover" and ":active" to the "username" before we use JavaScript to see if those pseudo-classes can be found on the given element.
NOTE: I chose to create a "username" field on purpose so that my browser would have some autofill capabilities that it would apply to the field.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>
Checking To See If An Element Has A CSS Pseudo-Class In JavaScript
</title>
<link rel="stylesheet" type="text/css" href="./demo.css" />
</head>
<body>
<h1>
Checking To See If An Element Has A CSS Pseudo-Class In JavaScript
</h1>
<form method="get" action="./">
<label for="username">Username:</label>
<input type="text" id="username" name="username" class="username" />
<button type="submit">Login</button>
</form>
<ul>
<li>
<a href="javascript:testFor( ':-webkit-autofill' )">
Test for <code>:-webkit-autofill</code>
</a>
</li>
<li>
<a href="javascript:testFor( ':hover' )">
Test for <code>:hover</code>
</a>
</li>
<li>
<a href="javascript:testFor( ':active' )">
Test for <code>:active</code>
</a>
</li>
<li>
<a href="javascript:testFor( ':focus' )">
Test for <code>:focus</code>
</a>
</li>
</ul>
<script type="text/javascript">
// Gather our DOM references.
var username = document.querySelector( "#username" );
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I test the USERNAME input for the existence of the given pseudo-class.
function testFor( pseudoClass ) {
console.group( "Testing for Pseudo-Class" );
console.log( "Pseudo-class:", pseudoClass );
console.log( "Has pseudo-class:", matches( username, pseudoClass ) );
console.log( "Class list:", username.classList.toString() ); // IE doesn't support .value.
console.groupEnd();
}
// I determine if the given node matches the given selector.
function matches( node, selector ) {
// Most modern browsers support the ".matches" method. However, IE9+ uses a
// non-standard method name. As such, we can fall-back to the IE version when
// the standard one doesn't exist and that should cover all the modern
// browsers that are in use.
// --
// READ MORE: https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
var nativeMatches = ( node.matches || node.msMatchesSelector );
// CAUTION: If an invalid pseudo-selector is used in Firefox or IE, the
// browser will throw a SyntaxError, "is not a valid selector". It will do
// the same for .querySelector() as well, just an FYI.
try {
return( nativeMatches.call( node, selector ) );
} catch ( error ) {
// In the case of an error, we're going to assume it's a pseudo-selector
// issue and NOT a general support issue (since we don't care about
// really old browsers).
return( false );
}
}
</script>
</body>
</html>
As you can see, there's very little going on in this demo. Essentially, I'm looking for the node.matches() method but, will fall-back to the node.msMatchesSelector() method in IE. I then use the chosen method to look for the given CSS pseudo-class.
Now, if I run this in the browser, explicitly add the ":hover" pseudo-class and then autofill the username, we get the following test outcome:
As you can see, we were able to successfully detect the applied CSS pseudo-classes in JavaScript. But, it should be noted that the browser will throw a SyntaxError if you try to use a pseudo-class Selector that the browser doesn't support. For example, all the browsers allow you to test for ":hover", since it's universally supported. But, all browsers other than Chrome will throw a SyntaxError when you test for, ":-webkit-autofill". That's why we need to wrap the underlying call to matches() inside a try/catch block.
Anyway, this is more of a note-to-self; but, it's definitely good to know that there's a cross-browser way to check an element for CSS pseudo-classes. If nothing else, this will help me when trying to detect autofill behaviors in the browser.
Want to use code from this post? Check out the license.
Reader Comments
@All,
I should mention that you can also search for the pseudo-classes using
node.querySelector()
andnode.querySelectorAll()
. In fact, on the MDN page, that's how they polyfill the.matches()
method -- using the.querySelectorAll()
and then iterating over the matches and checking for element reference equality.Great information. Since last week, I am gathering details about the AngularJS experience.
There are some amazing details on your blog which I didn't know. Thanks.
@Wilfred,
Excellent to hear that -- glad you are finding my writing helpful :)
Unfortunately :hover is only true if you are currently hovering.
This method will not detect if a HTML Element has a style defined with the pseudo class :hover when the mouse cursor is somewhere else.
#username:hover { outline: 1px solid orange; }
I haven't found a good way to do this yet :(
It doesn't appear to be exposed in the DOM.