Filters as a post-processing step

CSS filter effects let you manipulate the pixels of any visible element after the browser has finished layout and painting. Conceptually, a filter takes a snapshot of the rendered output as a bitmap, applies a graphics operation to that snapshot, and then draws the result over the original content. Think of it like putting a physical filter on a camera lens: the scene is unchanged, but what reaches your eye is altered.

This does add a small amount of work to the rendering pipeline, but when used sensibly the performance cost is usually negligible. Like physical lens filters, you can also chain multiple filter functions together, with each one operating on the output of the previous filter.

Where filters came from

Filters were originally part of the SVG specification, designed to apply pixel effects to vector drawings. Robert O’Callahan of Mozilla first prototyped applying SVG filters to ordinary HTML via CSS. The idea caught on, and the CSS and SVG working groups at the W3C later harmonized the effort, producing the CSS filter property. The current draft is maintained by a joint task force and is available in the FX Task Force spec.

Long-time web developers may remember that old versions of Internet Explorer exposed a non-standard filter property for platform-specific effects. That functionality has been deprecated in favor of the standardized CSS property, which modern versions of IE implement just like every other current browser.

Applying filters

You apply a CSS filter with the filter property on any visible element. For example, turning every <div> on the page grayscale can be done with:

div { { % mixin filter: grayscale(100%); % } }

Most filters accept a parameter to control the intensity of the effect. To go halfway between the original color and full grayscale, write:

div { { % mixin filter: grayscale(50%); % } }

To chain filters, just list them in order inside the property:

div { { % mixin filter: grayscale(100%) sepia(100%); % } }

The example above first converts the element to grayscale and then applies a sepia effect.

The CSS filter functions

SVG’s full filter mechanism is powerful but verbose. CSS deliberately offers a compact set of standard effects that are easy to use in stylesheets. In each case a percentage of 100% (or the floating point equivalent of 1.0) means the full effect is applied, and 0% leaves the input unchanged, except where noted.

grayscale(amount)

Converts colors to shades of gray. At 100% everything is gray; smaller amounts apply the effect progressively.

sepia(amount)

Adds a sepia tint similar to old photographs. Works like grayscale: 100% is completely sepia, and lower values produce a subtler effect.

saturate(amount)

Makes colors more vivid, akin to poster or cartoon treatment. Values above 100% are allowed to emphasize the saturation effect significantly.

hue-rotate(angle)

Shifts every color in the image around the color wheel by the angle you supply. All colors are moved by the same amount, which can drastically change the look of an image.

invert(amount)

Flips colors to their opposites, like a photo negative. At 100% you get a full negative; lower values produce a partial effect.

opacity(amount)

Sets the transparency of the element’s pixels. 100% is fully opaque and identical to the input; moving toward 0% reveals whatever is behind the element. At 0% nothing is visible, but note that the element can still receive mouse events — useful for invisible clickable regions.

This function behaves like the CSS opacity property. The key difference is that the property is often not hardware accelerated, while browsers that accelerate filters with the GPU will speed up the filter version too.

brightness(amount)

Similar to the brightness control on a TV. A value of 0% renders black; 100% is the original image. Values above 100% make the output brighter, useful for correcting dark photos.

contrast(amount)

Adjusts the difference between light and dark portions of the image. At 0% you get black; 100% is the original. Values above 100% increase the distinction between light and dark areas.

blur(radius)

Applies a Gaussian blur, smearing colors into one another much like an out-of-focus camera lens. The radius parameter determines how many pixels blend together: larger values produce more blur, and 0 leaves the element unchanged.

drop-shadow(shadow)

Creates a believable shadow by taking a snapshot of the element, recoloring it to a single hue, blurring it, and offsetting it. The shadow parameters are space-separated and specify position, blur radius, and color; some values are optional. Full syntax details are covered in the CSS3 Backgrounds box-shadow definition.

As with opacity, drop-shadow can potentially be hardware accelerated where the existing box-shadow property is not.

Referencing SVG filters from CSS

Because CSS filters can describe the same graphics model, you can also invoke a filter defined in SVG by using its id with the url() syntax. Suppose you have an SVG filter defined like this:

<filter id="foo">...</filter>

Then from CSS you can apply it with:

div { { % mixin filter: url(#foo); % } }

The real cost of filters on page speed

Not every CSS filter carries the same performance weight. Most run quickly with a negligible effect on your page, but the blur-related ones — blur and drop-shadow — can get expensive fast.

The math behind blur is straightforward: for each output pixel, the browser samples pixels in every direction according to your radius value to mix the colors. A radius of 2 means the filter has to inspect 2 pixels in every direction for every single output pixel. Increase the radius and the workload grows quadratically — doubling the radius makes the filter roughly four times slower. Since drop-shadow internally applies a blur, it incurs exactly the same scaling penalty when you adjust its radius or spread.

There is some relief: on certain platforms, the GPU can accelerate blur, but that support is not universal across browsers. The practical approach is to dial in the smallest radius that still delivers the look you want. Start from a value that clearly works, then reduce it until the visual result just begins to suffer. That kind of tuning pays off most for mobile users.

Be similarly cautious with url()-based filters that point to SVG definitions. Because those can reference arbitrary filter effects, there is no guarantee they will be cheap. Know what the referenced SVG does, and test it on a mobile device to validate the frame rate and responsiveness.

Support across current browsers

The CSS filter effects specification is still being finalized, and browser support reflects that. WebKit-based browsers and Mozilla have begun shipping implementations, with Opera and IE10 expected to follow. Until then, vendor prefixes are required: -webkit-filter for WebKit, -moz-filter for Mozilla.

Keep in mind that browser support for individual filter functions is uneven. Mozilla, for example, currently handles only the filter: url() syntax — and that prefix-free, because its SVG support predates the other filter functions in the spec.

The table below summarizes which effects are available in major browsers and how they typically perform in software rendering. Note that several modern browsers are moving these filters to GPU-accelerated paths, which will significantly improve results for the most expensive effects. As always, the only reliable method is to test in the specific browsers you intend to support.

Filter effect Browser support Performance
grayscaleChromevery fast
sepiaChromevery fast
saturateChromevery fast
hue-rotateChromefast
invertChromevery fast
opacityChromecan be slow
brightnessChromefast
contrastChromefast
blurChromeslow unless accelerated
drop-shadowChromecan be slow
url()Chrome, MozillaVaries, fast to slow

Further reading and tools