Skip to content

Instantly share code, notes, and snippets.

@pvorb
Created November 26, 2012 23:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pvorb/4151443 to your computer and use it in GitHub Desktop.
Save pvorb/4151443 to your computer and use it in GitHub Desktop.

Almost one year ago I wrote an article that dealt with an emerging WebKit CSS technique, the CSS filter effects, and the question if we could not have/emulate them in other browsers, too. Turned out we could.

Today I want to talk about another WebKit-only technique and show you how you might be able to use it across all of the browsers: This is about...

CSS masks

CSS masks were added to the WebKit engine by Apple quite a while ago, namely back in April 2008. Masks offer the ability to control the opacity/transparency of elements on a per-pixel basis, similar to how the alpha/transparency-channel of "24-bit"-PNGs or 32-bit-TIFFs work.

These images consist of the usual R(ed) G(reen) and B(lue) channels that define the colors of each pixel. But on top there is a fourth channel, the alpha channel, that defines every pixel's opacity through luminance: White meaning opaque, black meaning transparent, and countless grey-values defining the semi-transparent inbetweens. Like so:

RGBA

With CSS masks it is an HTML element that gets this type of treatment. And instead of using an alpha channel you assign an image resource to a CSS property named -webkit-mask-image, e.g. -webkit-mask-image: url(mouse.png). Transparency is then read from that mask image and applied to the HTML element, like so:

Mask

Neat! So what could that be good for? Well, masking could be used to have user avatars in a certain shape, e.g.

Comments

Or you could have long scrollable text smoothly fade out by using an mask image with a gradient from opaque to transparent:

Scroll fade

As with the background-image property you can also use a CSS gradient instead of an actual bitmap image, like so:

-webkit-mask-image: -webkit-linear-gradient(top, rgba(0,0,0,1), rgba(0,0,0,0));

(beware that this is the "old" syntax that will soon be replaced by linear-gradient(to bottom, rgba(0,0,0,1), rgba(0,0,0,0)))

In fact the -webkit-mask property has quite a lot of subproperties in common with the background property:

-webkit-mask            /* = background */
-webkit-mask-attachment /* = background-attachment */
-webkit-mask-clip       /* = background-clip */
-webkit-mask-origin     /* = background-origin */
-webkit-mask-image      /* = background-image */
-webkit-mask-repeat     /* = background-repeat */
-webkit-mask-size       /* = background-repeat */

As with the background property you can layer as many masks on top of each other as you like.

And then there is -webkit-mask-box-image which works similar to the border-image property: The mask image gets cut into 9 pieces, of which the 4 corners get placed in the corners of the to be masked element, and everything else gets stretched in order to fill the gaps in between:

mask-box-image

So what keeps us from rolling with CSS masks? It is that they aren't supported in any other browser :( Reason being that Apple again "forgot" to hand the W3C over a spec that describes the precise functioning of those masks. Which means that if other vendors would have wanted to implement masks they would have had to reverse engineer and find out about all the implementation details first. Dirty work nobody wanted to do. So it was not until a few days ago, on the 15th of November, that there appeared a first public working draft by the W3C called CSS Masking.

Emulating CSS masks in other browsers

The good news: Turns out the other browser do have different capabilities which we can use as hooks to emulate CSS masks. Woot!

So let's say we want to mask an HTML element to a certain shape:

Element masked to a mouse shape

In WebKit we would define:

.element {
    width: 400px;
    height: 300px;
    -webkit-mask-image: url(mouse.png);
}

As simple as that. mouse.png would currently look like that:

Mouse shape in black

Now let's turn to Firefox. What Firefox is able to do since ages is applying stuff defined in an SVG to HTML elements at another place. And what SVG knows that we can use is SVG masks. Contrary to WebKit those SVG masks are not interpreted by their transparency but by their luminance values - as with image alpha channels. So what we need to do first to have our one mask image work in WebKit and in Firefox, is paint everything opaque into shades of white. In Photoshop that's super easy:

  • Open the 24-bit-PNG that you already use as mask for WebKit
  • Select the "Layer" menu, then "Layer Style" and finally "Color Overlay"
  • In the "Color Overlay" dialog change the color to white
  • Close the dialog with "OK"
  • Select the "File" menu, "Save for web", and save it as 24-bit-PNG replacing the old one.

This is the result:

Mouse shape in white

Now if you reload WebKit everything should look unchanged. Next we need to encapsulate our PNG as SVG mask in an SVG file. We create the following mouse.svg:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <mask id="mouse" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
      <image x="0" y="0" width="400px" height="300px" xlink:href="mouse.png" />
    </mask>
  </defs>
</svg>

Note that we gave the mask an id="mouse". In our stylesheet we now add the CSS property mask and let it point to that mask id in our SVG:

.element {
    width: 400px;
    height: 300px;
    mask: url(mouse.svg#mouse);
    -webkit-mask-image: url(mouse.png);
}

And BAAAM! Masking in Firefox is up and running. Finally let's turn to Internet Explorer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment