Logotyp för Mirello Media — en webbyrå i Borås

:not(bad) part I
— better feature detection

Written by

Feature detection has been on everybody's lips the past few years. Many turn to the javascript library Modernizr to detect and report what features your visitor's browser does, or doesn't have. This is a major step forward for front-end development, but it makes us dependent on javascript in modern browsers. Let's fix that!

If you're already familiar with feature detection, you may want to jump directly to the good part.

About feature detection

A big reason to use feature detection is to be able to style elements differently based on those results. To help you do this, Modernizr creates class names in your html element.

Here, we detected that javascript is active, that there's no touch support and that the browser supports CSS animations:

<html class="js no-touch cssanimations">

To make an example, let's assume we want to animate an item fading in using CSS animations:

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .item { animation: fadeIn 1s ease forwards; opacity: 0; }

In modern browsers, .item will fade in with smooth easing, and we set the initial opacity to 0 to prevent .item from being visible before the animation is triggered. Nice beans.

However, older browsers — without support for CSS animations — won't understand the animation property and therefore ignore it. But they will still see and understand opacity property.

It's a disaster!

Now, .item is hidden in all browsers without support for CSS animations.

This is where we make use of the class name that Modernizr created for us:

.cssanimations .item { animation: fadeIn 1s ease forwards; opacity: 0; }

Since the class name cssanimations is only applied to browsers that do support CSS animations, we won't have to worry about .item being invisible in browsers that don't.

Great, now let's move on to the good part!

The good part (the problem)

While Modernizr and javascript driven feature detection is a major step forward for front end-development, it's making a dangerous assumption that javascript is enabled.

Users with javascript disabled won't see the styles applied since they're reliant on a class name created with javascript.

And even worse...

Even worse, you're now completely reliant on Modernizr to apply said class name for all future, even when browsers without support for whatever feature you tested for are since long gone.

Sure, you can kind of work around it by separating the critical CSS properties that the feature-less browser doesn't understand from the ones that they do, like this:

.item { animation: fadeIn 1s ease forwards; } .cssanimations .item { opacity: 0; }

Or by reversing whatever critical CSS property you set for browsers that tested false:

.item { animation: fadeIn 1s ease forwards; opacity: 0; } .no-cssanimations .item { opacity: 1; }

While this is an improvement, you're still reliant on javascript for the solution to be flawless in modern browsers. It's also not very DRY, and makes for a horrible authoring experience.

There is a better way!

What we actually want is to:

  • Not be dependent on javascript in modern browsers.
  • Not apply CSS properties to feature-less browsers.
  • Not have to repeat ourselves, ever.

This is where our hero — :not() — steps into the spotlight!

Look at this selector for a few seconds to let it sink in:

html:not(.no-cssanimations) .item { animation: fadeIn 1s ease forwards; opacity: 0; }

I call this the "double negation technique". Because that's what it is, and I like things simple.

Put into words, this selector says:

"When the browser hasn't reported that it doesn't support CSS animations, apply these styles."

This completely satisfies the first criteria, since it doesn't matter if javascript is disabled or not.

This also satisfies the second and third criteria, since it doesn't apply said CSS when the browser does report that it doesn't support them.

:not(bad), eh?

There you have it. Javascript-based feature detection, without being reliant on javascript or Modernizr in modern browsers.

Not bad.

Do take note that a feature-less browser with javascript disabled that supports :not() will still see the styles we intended to hide from it. In this kind of scenario you will need to patch your CSS to compensate.

There's also the scenario where a browser supports the feature you're testing for but not :not(), although it may be a very rare scenario it's worth keeping in mind.

What do you think of this approach? Join the conversation at twitter!

Porträtt av Nils Kaspersson

Nils Kaspersson

comments powered by Disqus