Skip to content

Latest commit

 

History

History
1046 lines (690 loc) · 60.7 KB

File metadata and controls

1046 lines (690 loc) · 60.7 KB

第 19 章 过滤器,混合,剪切和掩蔽 Filters, Blending, Clipping, and Masking

Over the past decade, CSS has accumulated some interesting new features. These allow authors to alter the appearance of element with visual filters, specify different ways to visually blend elements with whatever is behind them, and alter the presentation of elements by showing parts and hiding other parts. While these may seem like disparate concepts, they all share one thing in common: they allow elements to be altered in ways that were previously very difficult or impossible.

在过去的十年中,CSS 积累了一些有趣的新特性。这允许作者使用可视化过滤器来改变元素的外观,指定不同的方式来可视化地混合元素及其背后的内容,并通过显示部分和隐藏其他部分来改变元素的表示。虽然这些可能看起来是不同的概念,但它们都有一个共同点:它们允许以以前非常困难或不可能的方式更改元素。

19.1 CSS Filters

The veterans among us may remember that a long time ago, Microsoft put a filter property into their CSS support, which was used to pull in DirectX visual effects. In the time since, CSS has gained a filter property of its own, and while it’s similar in concept to what Microsoft did, it isn’t really the same thing. Among other changes, CSS defines a number of built-in visual filter effects, in addition to permitting the loading of filters defined in external files.

我们中的老手可能还记得,很久以前,微软在他们的 CSS 支持中加入了一个 filter 属性,用来获取 DirectX 的视觉效果。从那以后,CSS 获得了自己的 filter 属性,虽然它在概念上类似于微软所做的,但实际上并不是一回事。除了其他更改外,CSS 还定义了许多内置的视觉过滤器效果,允许加载外部文件中定义的过滤器。

The value syntax permits a comma-separated list of filter functions, with each filter applied in sequence. Thus, given the declaration filter: opacity(0.5) blur(1px);, the opacity is applied to the element, and the semi-transparent result is then blurred. If the order were reversed, so too would be the order of application: the fully opaque element is burred, and the resulting blur made semi-transparent.

The CSS specification talks of “input images” when discussing filter, but this doesn’t mean filter is only used on images. Any HTML element can be filtered, and all graphic SVG elements can be filtered. The input image is a visual copy of the rendered element before it is filtered. Filters are applied to this input, and the final filtered result is then rendered to the display medium.

All the values permitted are effectively functions, with the permitted value types for each being dependent on the function in question. I’ve grouped these functions into a few broad categories for ease of understanding.

19.1.1 Basic Filters

These filters are basic in the sense that they cause changes that their names directly describe: blurring, drop shadows, and opacity changes:

blur(<length>)

Blurs the element’s contents using a Gaussian blur whose standard deviation is defined by the <length> value supplied, where a value of 0 leaves the element unchanged. Negative lengths are not permitted.

opacity( [ <number> | <percentage> ] )

Applies a transparency filter to the element in a manner very similar to the opac ity property, where the value 0 yields a completely transparent element and a value of 1 or 100% leaves the element unchanged. Negative values are not permitted. Values greater than 1 and 100% are permitted, but are clipped to be 1 or 100% for the purposes of computing the final value.

The specification makes clear that filter: opacity() is not meant to be a replacement or shorthand for the opacity property, and in fact both can be applied to the same element, resulting in a sort of double-transparency.

drop-shadow(<length>{2,3} <color>?)

Creates a drop shadow that matches the shape of the element’s alpha channel, with a blur and using an optional color. The handling of the lengths and colors is the same as for the property box-shadow, which means that while the first two <length>s can be negative, the third (which defines the blur) cannot. If no <color> value is supplied, the used color is the same as the computed value of the color property for the element.

The effects of these filter functions, alone and in combination, is shown in Figure 19-1.

Basic filter effects

Before we go one, there are two things that deserve further exploration. The first is how drop-shadow() really operates. Just by looking at Figure 19-1, it’s easy to get the impression that drop shadows are bound to the element box, because of the boxlike natures of the drop shadows shown there. But that’s just because the image used to illustrate filters is a PNG, which is to say a raster image, and more importantly one that doesn’t have any alpha channel. The white parts are opaque white, in other words.

If the image has transparent bits, then drop-shadow() will use those in computing the shadow. This can be a GIF89a, a PNG, a JPEG2000, an SVG, or any other alpha-aware image format. To see what this means, consider Figure 19-2.

The other thing to point out in Figure 19-2 is the last image has two drop shadows. This was accomplished as follows:

filter: drop-shadow(0 0 0.5em yellow) drop-shadow(0.5em 0.75em 30px gray);

Drop shadows and alpha channels

Any number of filters can be chained together like this. To pick another example, you could write:

filter: blur(3px) drop-shadow(0.5em 0.75em 30px gray) opacity(0.5);

That would get you a blurry, drop-shadowed, half-opaque element. It might not be the most reader-friendly effect for text, but it’s possible nonetheless. This functionchaining is possible with all filter functions, both those we’ve seen and those to come.

19.1.2 Color Filtering

This next set of filter functions alter the colors present in the filtered element in some way. This can be as simple as leaching out the colors, or as complex as shifting all the colors by way of an angle value.

Note that for the first three of the four of the following functions, all of which accept either a <number> or <percentage>, negative values are not permitted:

grayscale( [ <number> | <percentage> ] )

Alters the colors in the element to be shifted toward shades of gray. A value of 0 leaves the element unchanged, and a value of 1 or 100% will result in a fully grayscale element.

sepia( [ <number> | <percentage> ] )

Alters the colors in the element to be shifted toward shades of sepia tones (sepia is a reddish-brown color, defined by Wikipedia to be equivalent to #704214 or rgba(112,66,20) in the sRGB color space). A value of 0 leaves the element unchanged, and a value of 1 or 100% will result in a fully sepia element.

invert( [ <number> | <percentage> ] )

Inverts all colors in the element. Each of the R, G, and B values for a given color are inverted by subtracting them from 255 (in 0-255 notation) or from 100% (in 0%-100% notation). For example, a pixel with the color rgb(255,128,55) will be rendered as rgb(0,127,200); a different pixel with the value rgb(75%,57.2%, 23%) will become rgb(25%,42.8%,77%). A value of 0 leaves the element unchanged, and a value of 1 or 100% will result in a fully inverted element. A value of 0.5 or 50% would stop the inversion of each color at the midpoint of the color space, leading to an element of uniform gray.

hue-rotate( <angle> )

Alters the colors of the image by displacing their hue around an HSL color wheel, leaving saturation and lightness alone. A value of 0deg leaves the element unchanged. A value of 360deg (a full single rotation) will also present an apparently unchanged element, though the value is maintained, and values above 360deg are permitted. Negative values are also permitted, and cause an anticlockwise rotation as opposed to the clockwise rotation imposed by positive values. (In other words, the rotation is “compass-style,” with 0º at the top and increasing angle values in the clockwise direction.)

Examples of the preceding filter functions are shown in Figure 19-3, though fully appreciating them depends on a color rendering of the figure.

Color filter effects

19.1.3 Brightness, Contrast, and Saturation

While these filter functions also manipulate color, they do so in closely related ways, and are a familiar grouping to anyone who’s worked with images, particularly photographic images.

Note that for all these functions, values greater than 1 and 100% are permitted, but are clipped to be 1 or 100% for the purposes of computing the final value:

brightness( [ <number> | <percentage> ] )

Alters the brightness of the element’s colors. A value of 0 leaves the element a solid black, and a value of 1 or 100% leaves it unchanged. Values above 1 and 100% yield colors brighter than the base element, and can eventually reach a state of solid white.

contrast( [ <number> | <percentage> ] )

Alters the contrast of the element’s colors. The higher the contrast, the more colors are differentiated from each other; the lower the contrast, the more they converge on each other. A value of 0 leaves the element a solid gray, and a value of 1 or 100% leaves it unchanged. Values above 1 and 100% yield colors with greater contrast than is present in the base element.

saturate( [ <number> | <percentage> ] )

Alters the saturation of the element’s colors. The more saturated an element’s colors, the more intense they become; the less saturated they are, the more muted they appear. A value of 0 leaves the element completely unsaturated, leaving it effectively grayscale, whereas a value of 1 or 100% will leave the element unchanged. Unlike the preceding functions, saturate() permits and acts upon values greater than 1 or 100%; such values result in supersaturation.

Examples of the preceding filter functions are shown in Figure 19-4, though fully appreciating them depends on a color rendering of the figure. Also, the effects of greater-than-one values may be hard to make out in the figure, but they are present.

Brightness, contrast, and saturation filter effects

19.1.4 SVG Filters

The last filter value type is a function of a familiar kind: the url() value type. This allows authors to point to a (potentially very complicated) filter defined in SVG, whether it’s embedded in the document or stored in an external file.

This takes the form url(<uri>), where the <uri> value points to a filter defined using SVG syntax, specifically the <filter> element. This can be a reference to a single SVG image which contains only a filter, such as url(wavy.svg), or it can be a pointer to an identified filter embedded in an SVG image, such as url(filters.svg#wavy).

The advantage of the latter pattern is that a single SVG can define multiple filters, thus consolidating all your filtering into one file for easy loading, caching, and referencing.

If a url() function points to a nonexistent file, or points to an SVG fragment that is not a <filter> element, the function is invalid and the entire function list is ignored (thus rendering the filter declaration invalid).

Examining the full range of filtering possibilities in SVG is well beyond the scope of this work, but let’s just say that the power of the offered features is substantial. A few simple examples of SVG filtering are shown in Figure 19-5, with brief captions to indicate what kinds of operations the filters were built to create. (The actual CSS used to apply these filters looked like filter: url(filters.svg#rough).)

SVG filter effects

It’s easily possible to put every last bit of filtering you do into SVG, including replacements for every other filter function we’ve seen. (In fact, all the other filter functions are defined by the specification as literal SVG filters, to give a precise rendering target for implementors.) Remember, however, that you can chain CSS functions together. Thus, you might define a specular-highlight filter in SVG, and modify it with blurring or grayscale functions as needed. For example:

img.logo {
  filter: url(/assets/filters.svg#spotlight);
}
img.logo.print {
  filter: url(/assets/filters.svg#spotlight) grayscale(100%);
}
img.logo.censored {
  filter: url(/assets/filters.svg#spotlight) blur(3px);
}

Always keep in mind that the filter functions are applied in order. That’s why the gray scale() and blur() functions each come after the url()-imported spotlight filter. If they were reversed, the logos would be made grayscale or blurred first, and then have a highlight applied afterward.

19.2 Compositing and Blending

In addition to filtering, CSS offers the ability to determine how elements are composited together. Take, for example, two elements that partially overlap due to positioning. We’re used to the one in front obscuring the one behind. This is sometimes called simple alpha compositing, in that you can see whatever is behind the element as long as some (or all) of it has alpha channel values less than 1. Think of, for example, how you can see the background through an element with opacity: 0.5, or in the areas of a PNG or GIF87a that are set to be transparent.

But if you’re familiar with image-editing programs like Photoshop or GIMP, you know that image layers which overlap can be blended together in a variety of ways. CSS has gained the same ability. There are two blending strategies in CSS (at least as of late 2017): blending entire elements with whatever is behind them, and blending together the background layers of a single element.

19.2.1 Blending Elements

In situations where elements overlap, it’s possible to change how they blend together with the property mix-blend-mode.

The way the CSS specification puts this is: “defines the formula that must be used to mix the colors with the backdrop.” That is to say, the element is blended with whatever is behind it (the “backdrop”), whether that’s pieces of another element, or just the background of its parent element.

The default, normal, means that the element’s pixels are shown as is, without any mixing with the backdrop, except where the alpha channel is less than 1. This is the “simple alpha compositing” mentioned previously. It’s what we’re all used to, which is why it’s the default value. A few examples are shown in Figure 19-6.

Simple alpha channel blending

For the rest of the mix-blend-mode keywords, I’ve grouped them into a few categories. Let’s also nail down a few definitions:

  • The foreground is the element that has mix-blend-mode applied to it.
  • The backdrop is whatever is behind that element. This can be other elements, the background of the parent element, and so on.
  • A pixel component is the color component of a given pixel: R, G, and B.

If it helps, think of the foreground and backdrop as images that are layered atop one another in an image-editing program. With mix-blend-mode, you can change the blend mode applied to the top image (the foreground).

19.2.2 Darken, Lighten, Difference, and Exclusion

These blend modes might be called simple-math modes—they achieve their effect by directly comparing values in some way, or using simple addition and subtraction to modify pixels:

darken

Each pixel in the foreground is compared with the corresponding pixel in the backdrop, and for each of the R, G, and B values (the pixel components), the smaller of the two is kept. Thus, if the foreground pixel has a value corresponding to rgb(91,164,22) and the backdrop pixel is rgb(102,104,255), the resulting pixel will be rgb(91,104,22).

lighten

This blend is the inverse of darken: when comparing the R, G, and B components of a foreground pixel and its corresponding backdrop pixel, the larger of the two values is kept. Thus, if the foreground pixel has a value corresponding to rgb(91,164,22) and the backdrop pixel is rgb(102,104,255), the resulting pixel will be rgb(102,164,255).

difference

The R, G, and B components of each pixel in the foreground are compared to the corresponding pixel in the backdrop, and the absolute value of subtracting one from the other is the final result. Thus, if the foreground pixel has a value corresponding to rgb(91,164,22) and the backdrop pixel is rgb(102,104,255), the resulting pixel will be rgb(11,60,233). If one of the pixels is white, the resulting pixel will be the inverse of the non-white pixel. If one of the pixels is black, the result will be exactly the same as the non-black pixel.

exclusion

This blend is a milder version of difference. Rather than being | back - fore |, the formula is back + fore - (2 × back × fore), where back and fore are values in the range from 0-1. For example, an exclusion calculation of an orange (rgb(100%, 50%,0%)) and medium gray (rgb(50%,50%,50%)) will yield rgb(50%,50%,50%). For the red component, the math is 1 + 0.5 - (2 × 1 × 0.5), which reduces to 0.5, corresponding to 50%. For the blue and green components, the math is 0 + 0.5 - (2 × 0 × 0.5), which again reduces to 0.5. Compare this to difference, where the result would be rgb(50%,0%,50%), since each component is the absolute value of subtracting one from the other.

This last definition highlights the fact that for all blend modes, the actual values being operated on are in the range 0-1. The previous examples showing values like rgb(11,60,233) are normalized from the 0-1 range. In other words, given the example of applying the difference blend mode to rgb(91,164,22) and rgb(102,104,255), the actual operation is as follows:

  • rgb(91,164,22) is R = 91 ÷ 255 = 0.357; G = 164 ÷ 255 = 0.643; B = 22 ÷ 255 = 0.086. Similarly, rgb(102,104,255) corresponds to R = 0.4; G = 0.408; B = 1.
  • Each component is subtracted from the corresponding component, and the absolute value taken. Thus, R = | 0.357 - 0.4 | = 0.043; G = | 0.643 - 0.408 | = 0.235; B = | 1 - 0.086 | = 0.914. This could be expressed as rgba(4.3%,23.5%,91.4%), or (by multiplying each component by 255) as rgb(11,60,233).

From all this, you can perhaps understand why the full formulas are not written out for every blend mode we cover. If you’re interested in the fine details, each blend mode’s formula is provided in the “Compositing and Blending Level 1” specification. Examples of the blend modes in this section are depicted in Figure 19-7.

Darken, lighten, difference, and exclusion blending

19.2.3 Multiply, Screen, and Overlay

These blend modes might be called the multiplication modes—they achieve their effect by multiplying values together:

multiply

Each pixel component in the foreground is multiplied by the corresponding pixel component in the backdrop. This yields a darker version of the foreground, modified by what is underneath. This blend mode is symmetric, in that the result will be exactly the same even if you were to swap the foreground with the backdrop.

screen

Each pixel component in the foreground is inverted (see invert in the earlier section “Color Filtering” on page 948), multiplied by the inverse of the corresponding pixel component in the backdrop, and the result inverted again. This yields a lighter version of the foreground, modified by what is underneath. Like multiply, screen is symmetric.

overlay

This blend is a combination of multiply and screen. For foreground pixel components darker than 0.5 (50%), the multiply operation is carried out; for foreground pixel components whose values are above 0.5, screen is used. This makes the dark areas darker, and the light areas lighter. This blend mode is not symmetric, because swapping the foreground for the backdrop would mean a different pattern of light and dark, and thus a different pattern of multiplying versus screening.

Examples of these blend modes are depicted in Figure 19-8.

Multiply, screen, and overlay blending

19.2.4 Hard and Soft Light

There blend modes are covered here because the first is closely related to a previous blend mode, and the other is just a muted version of the first:

hard-light

This blend is the inverse of overlay blending. Like overlay, it’s a combination of multiply and screen, but the determining layer is the backdrop. Thus, for backdrop pixel components darker than 0.5 (50%), the multiply operation is carried out; for backdrop pixel components lighter than 0.5, screen is used. This makes it appear somewhat as if the foreground is being projected onto the backdrop with a projector that employs a harsh light.

soft-light

This blend is a softer version of hard-light. That is to say, it uses the same operation, but is muted in its effects. The intended appearance is as if the foreground is being projected onto the backdrop with a projector that employs a diffuse light.

Examples of these blend modes are depicted in Figure 19-9.

Hard- and soft-light blending

19.2.5 Color Dodge and Burn

Color dodging and burning are interesting modes, in that they’re meant to lighten or darken a picture with a minimum of change to the colors themselves. The terms come from old darkroom techniques performed on chemical film stock:

color-dodge

Each pixel component in the foreground is inverted, and the component of the corresponding backdrop pixel component is divided by the inverted foreground value. This yields a brightened backdrop unless the foreground value is 0, in which case the backdrop value is unchanged.

color-burn

This blend is a reverse of color-dodge: each pixel component in the backdrop is inverted, the inverted backdrop value is divided by the unchanged value of the corresponding foreground pixel component, and the result is then inverted. This yields a result where the darker the backdrop pixel, the more its color will burn through the foreground pixel.

Examples of these blend modes are depicted in Figure 19-10.

Color dodge and burn blending

19.2.6 Hue, Saturation, Luminosity, and Color

The final four blend modes are different than those we’ve seen before, because they do not perform operations on the R/G/B pixel components. Instead, they perform operations to combine the hue, saturation, luminosity, and color of the foreground and backdrop in different ways:

hue

For each pixel, combines the luminosity and saturation levels of the backdrop with the hue angle of the foreground.

saturation

For each pixel, combines the hue angle and luminosity level of the backdrop with the saturation level of the foreground.

color

For each pixel, combines the luminosity level of the backdrop with the hue angle and saturation level of the foreground.

luminosity

For each pixel, combines the hue angle and saturation level of the backdrop with the luminosity level of the foreground.

Examples of these blend modes are depicted in Figure 19-11.

Hue, saturation, luminosity, and color blending

These blend modes can be a lot harder to grasp without busting out raw formulas, and even those can be confusing if you aren’t familiar with how things like saturation and luminosity levels are determined. If you don’t feel like you quite have a handle on how they work, the best thing is to practice with a bunch of different images and simple color patterns.

Two things to note:

  • Remember that an element always blends with its backdrop. If there are other elements behind it, it will blend with them; if there’s a patterned background on the parent element, the blending will be done against that pattern.
  • Changing the opacity of a blended element will change the outcome, though not always in the way you might expect. For example, if an element with mix-blendmode: difference is also given opacity: 0.8, then the difference calculations will be scaled by 80%. More precisely, a scaling factor of 0.8 will be applied to the color-value calculations. This can cause some operations to trend toward flat middle gray, and others to shift the color changes.

19.3 Blending Backgrounds

Blending an element with its backdrop is one thing, but what if an element has multiple background images that overlap and also need to be blended together? That’s where background-blend-mode comes in.

We won’t go through an exhaustive list of all the blend modes and what they mean, because we did that in the section on mix-blend-mode. What they meant there, they mean here.

The difference is that when it comes to blending multiple backgrounds images together, they’re blended with each other against an empty background—that is, a completely transparent, uncolored backdrop. They do not blend with the backdrop of the element, except as directed by mix-blend-mode.

To see what that means, consider the following:

#example {
  background-image: url(star.svg), url(diamond.png), linear-gradient(135deg, #f00, #aea);
  background-blend-mode: color-burn, luminosity, darken;
}

Here we have three background images, each with its own blend mode. These are blended together into a single result, shown in Figure 19-12.

Three backgrounds blended together

So far, fine. Here’s the kicker: the result will be the same regardless of what might appear behind the element. We can change the parent’s background to white, gray, fuchsia, or a lovely pattern of repeating gradients, and in every case those three blended backgrounds will look exactly the same, pixel for pixel. They’re blended in isolation, a term we’ll return to shortly. We can see the above example (Figure 19-12) sitting atop a variety of backgrounds in Figure 19-13.

Blending with color versus transparency

Like multiple blended elements stacked atop each other, the blending of background layers works from the back to the front. Thus, if you have two background images over a solid background color, the background layer in the back is blended with the background color, and then the frontmost layer is blended with the result of the first blend. Consider:

#example {
  background-image: url(star.svg), url(diamond.png);
  background-color: goldenrod;
  background-mix-mode: color-burn, luminosity;
}

Given these styles, diamond.png is blended with the background color goldenrod using the luminosity blend. Once that’s done, star.png is blended with the results of the diamond-goldenrod blend using a color-burn blend.

Although it’s true that the background layers are blended in isolation, they’re also part of an element which may have its own blending rules via mix-blend-mode. Thus, the final result of the isolated background blend may be blended with the element’s backdrop after all. Given the following styles, the first example’s background will sit atop the element’s backdrop, but the rest will end up blended with it in some fashion, as illustrated in Figure 19-14:

.one {
  mix-blend-mode: normal;
}
.two {
  mix-blend-mode: multiply;
}
.three {
  mix-blend-mode: darken;
}
.four {
  mix-blend-mode: luminosity;
}
.five {
  mix-blend-mode: color-dodge;
}
<div class="bbm one"></div>
<div class="bbm two"></div>
<div class="bbm three"></div>
<div class="bbm four"></div>
<div class="bbm five"></div>

Blending elements with their backdrops

Throughout this section, we’ve touched on the concept of blending in isolation, as a thing that backgrounds naturally do. Elements, on the other hand, do not naturally blend in isolation. As we’ll see next, that behavior can be changed.

19.3.1 Blending in Isolation

There may be times when you want to blend a number of different elements together, but in a group of their own, in the same way background layers on an element are blended. This is, as we’ve seen, called blending in isolation. If that’s what you’re after, then the isolation property is for you.

This pretty much does exactly what it says: it either defines an element to create an isolated blending context, or not. Given the following styles, then, we get the result shown in Figure 19-15:

img {
  mix-blend-mode: difference;
}
p.alone {
  isolation: isolate;
}
<p class="alone"><img src="diamond.png" /></p>
<p><img src="diamond.png" /></p>

Blending in isolation, and not

Take particular note of where isolation was applied, and where mix-blend-mode was applied. The image is given the blend mode, but the containing element (in this case, a paragraph) is set to isolation blending. It’s done this way because you want the parent (or some ancestor element) to be isolated from the rest of the document, in terms of how its descendant elements are blended. So if you want an element to blend in isolation, look for an ancestor element to set to isolation: isolate.

There is an interesting wrinkle in all of this, which is that any element which establishes a stacking context is automatically isolated, regardless of the value for isola tion. If you transform an element using the transform property, for example, it will become isolated.

The complete list of stacking-context-establishing conditions, as of late 2017, are:

  • The root element (e.g., <html>)
  • Positioning an element relatively or absolutely and setting its z-index to anything other than auto
  • Positioning an element with fixed, regardless of its z-index value
  • Setting opacity to anything other than 1
  • Setting transform to anything other than none
  • Setting mix-blend-mode to anything other than normal
  • Setting filter to anything other than none
  • Setting perspective to anything other than none
  • Setting isolation to isolate
  • Applying will-change to any of the previous properties, even if they are not actually changed

Thus, if you have a group of elements that are blended together and then blended with their shared backdrop, and you then transition the group’s opacity from 1 to 0, the group will suddenly become isolated during the transition. This might have no visual impact, depending on the original set of blends, but it very well might.

19.4 Clipping and Masking

Besides filtering and blending, CSS also has the ability to do both clipping and masking. These are methods of only showing portions of an element, using permitting a variety of simple shapes as well as the application of complete images and SVG elements. These can be used to make decorative bits of a layout more visually interesting, among other things—a common technique is to frame images or give them ragged edges.

19.4.1 Clipping

One of the possibilities we saw with filter was to apply a clipping path via SVG. That’s a valid use of filters, but if all you want to do is clip off pieces of the element, you can use the property clip-path instead.

With clip-path, you’re able to define a clipping shape. This is essentially the area of the element inside which visible portions are drawn. Any part of the element that fall outside the shape is clipped off, leaving behind empty transparent space. The following code gives a clipped and an unclipped example of the same paragraph, with the result depicted in Figure 19-16:

p {
  background: orange;
  color: black;
  padding: 0.75em;
}
p.clipped {
  clip-path: url(shapes.svg#cloud02);
}

Clipped and unclipped paragraphs

The default value, none, means no clipping is preformed, as you’d probably expect. If a <uri> value is given (as in the previous example) and it points to a missing resource, or to an element in an SVG file that isn’t a <clipPath>, then no clipping occurs.

The rest of the values are either shapes written in CSS, reference boxes, or both.

As of late 2017, URL-based clip paths work in Chrome only if the URL points to an embedded SVG inside the same document as the clipped element. External SVGs were not supported.

19.4.2 Clip Shapes

You can define clip shapes with one of a set of four simple shape functions. These are identical to the shapes used to define float shapes with shape-outside (see Chapter 10), so we won’t re-describe them in detail here. Here’s a brief recap:

inset()

Accepts from one to four lengths or percentage values, defining offsets from the edges of the bounding box, with optional corner rounding via the round keyword and another set of one to four lengths or percentages.

circle()

Accepts a single length, percentage, or keyword defining the radius of the circle, with an optional position for the circle’s center with the at keyword followed by one or two lengths or percentages.

ellipse()

Accepts a mandatory two lengths, percentages, or keywords defining the radii of the vertical and horizontal axes of the ellipse, with an optional position for the ellipse’s center with the at keyword followed by one or two lengths or percentages.

polygon()

Accepts a comma-separated list of space-separated x and y coordinates, using either lengths or percentages. Can be prefaced by a keyword defining the fill rule for the polygon.

A variety of examples of these clip shapes is shown in Figure 19-17, corresponding to the following styles. (The dotted borders have been added to show the outer edges of the original image, before clipping.)

.ex01 {
  clip-path: none;
}
.ex02 {
  clip-path: inset(10px 0 25% 2em);
}
.ex03 {
  clip-path: circle(100px at 50% 50%);
}
.ex04 {
  clip-path: ellipse(100px 50px at 75% 25%);
}
.ex05 {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}
.ex06 {
  clip-path: polygon(0 0, 50px 100px, 150px 5px, 300px 150px, 0 100%);
}

Various clip shapes

As Figure 19-17 shows, the elements are only visible inside the clip shapes. Anything outside that is just gone. But take note of how the clipped elements still take up the same space they would if they weren’t clipped at all. In other words, clipping doesn’t make the elements smaller. It just limits the part of them that’s actually drawn.

19.4.3 Clip Boxes

Unlike clip shapes, clip boxes aren’t specified using lengths or percentages. They correspond, for the most part, directly to boundaries in the box model.

If you just say clip-path: border-box, for example, the element is clipped along the outside edge of the border. This is likely what you’d expect anyway, since margins are transparent. Remember, however, that outlines can be drawn outside borders, so if you do clip at the border edge, any outlines will be clipped away.

When used by themselves, the values margin-box, padding-box, and content-box dictate that the clipping occur at the outer edges of the margin, padding, or content areas, respectively. These are diagrammed in Figure 19-18.

Various clipping boxes

There’s another part to Figure 19-18, which shows the SVG bounding boxes:

view-box

The nearest (that is, the closest ancestor) SVG viewport is used as the clipping box.

fill-box

The object bounding box is used as the clipping box. The object bounding box is the smallest box that will fit every part of the element’s geometry, taking into account any transformations (e.g., rotation), not including any strokes along its outside.

stroke-box

The stroke bounding box is used as the clipping box. The object bounding box is the smallest box that will fit every part of the element’s geometry, taking into account any transformations (e.g., rotation), including any strokes along its outside.

These values only apply to SVG elements that don’t have an associated CSS layout box. For such elements, if the CSS-style boxes (margin-box, border-box, paddingbox, content-box) are given, fill-box is used instead. Conversely, if one of the SVG bounding box values is applied to an element that does have a CSS layout box—which is most elements—then border-box is used instead.

It can be useful at times to be able to say something like clip-path: content-box just to clip off everything outside the content area, but where these box values really come into their own is in conjunction with a clipping shape. Suppose you have an ellipse() clip shape you want to apply to an element, and furthermore, you want to have it just touch the outer edges of the padding box. Rather than have to calculate the necessary radii by subtracting margins and borders from the overall element, you can just say clip-path: ellipse(50% 50%) padding-box;. That will center an elliptical clip shape at the center of the element, with horizontal and vertical radii half the element’s reference box (see Chapter 10), as shown in Figure 19-19, along with the effect of fitting to other boxes.

Fitting an elliptical clip shape to various boxes

Notice how the ellipse is cut off in the margin-box example? That’s because the margin is invisible, so while parts of it fall inside the elliptical clip shape, we can’t actually see those parts.

Interestingly, the bounding-box keywords can only be used in conjunction with clip shapes—not with an SVG-based clip path. The keywords that relate to SVG bounding boxes apply only if an SVG image is being clipped via CSS.

A warning about SVG clip paths: as of late 2017, all path coordinates are expressed in absolute units, and can’t be declared as percentages of the image’s height and width as the polygon() shape can. There are techniques involving the clipPathUnits SVG attribute, sometimes in conjunction with the transform SVG attribute, that yield equivalent results. Here’s an example of such a clipping path, with the result shown in Figure 19-20:

<clipPath id="hexlike" clipPathUnits="objectBoundingBox">
  <polygon points="0.5 0, 1 0.25, 1 0.75, 0.5 1, 0 0.75, 0 0.25" />
</clipPath>

An image clipped with a scaling SVG clip path

The objectBoundingBox value fits the coordinates to the bounding box in use, and the coordinates are all in the range of 0–1. With that sort of setup, you get a clip path that behaves the same as a percentage-based polygon shape. You’d get the same clip shape shown in Figure 19-20 by using the following:

clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);

19.4.4 Clip Filling Rules

As with float shapes, it’s possible to change the way SVG shapes are filled, which is to say the exact clipping shape that is created when the path crosses over itself. This is managed with the property clip-rule.

It’s much easier to show than describe, so the difference between nonzero and even odd shape filling is depicted in Figure 19-21.

The two shape-filling options

Here, you can see how the star is drawn by following lines from the top center through each successive point. The nonzero star fills all of its interior, even when lines cross over each other. The evenodd star, by contrast, leaves parts of itself unfilled, which is why we can see the light blue gradient through its center.

The problem is that as of late 2017, even browsers that supported SVG clipping paths did not support this property, regardless of whether the SVG was embedded in the HTML or external files. Thus, if you want to set the shape-fill of a clipping path to evenodd, you’ll either need to recreate the SVG path as a CSS polygon, or make use of the SVG fill-rule attribute in the SVG file itself.

19.5 Masks

When we say a “mask,” what we mean is a shape inside which things are visible, and outside which they are not. Masks are thus very similar in concept to clipping paths. The primary differences are twofold: first, you can only use an image to define the areas of the element that are shown or clipped away with masks; and second, there are a lot more properties available to use with masks, allowing you to do things such as position, size, and repeat the masking image.

As of late 2017, the Blink family supported most of the masking properties, but only with the -webkit- prefix. So instead of maskimage, Chrome and Safari supported -webkit-mask-image instead.

19.5.1 Defining a Mask

The first step to applying a mask is to point to the image that you’ll be using to define the mask. This is accomplished with mask-image, which accepts any image type.

Assuming the image reference is valid, this will give the user agent an image to use as a mask for the element to which it’s been applied.

We’ll start with a simple situation: one image applied to another, where both are the same height and width. Consider Figure 19-22, where both images are shown separately, and then with the first masked by the second.

A simple image mask

As you can see, in the parts of the second image that are opaque, the first image is visible. In the parts that are transparent, the first image is not visible. For the parts that are semi-transparent, the first image is also semi-transparent.

Here’s the basic code for the end result shown in Figure 19-22:

img.masked {
  mask-image: url(theatre-masks.svg);
}

CSS doesn’t require that you apply mask images only to other images, though. You can mask pretty much any element with an image, and that image can be a raster image (GIF, JPG, PNG) or a vector image (SVG). The latter is usually a better choice, if available. You can even construct your own image with gradients, whether linear or radial, repeated or otherwise.

The following styles will have the result shown in Figure 19-23:

*.masked.theatre {
  mask-image: url(theatre-masks.svg);
}
*.masked.compass {
  mask-image: url(Compass.png);
}

A variety of image masks

An important point to keep in mind is that when a mask clips off pieces of an element, it clips off all pieces. The best example of this is how, if you apply an image that clips off the outer edges of elements, the markers on list items can very easily become invisible. An example can be seen in Figure 19-24, which is the result of the following:

*.masked {
  mask-image: url(i/Compass_masked.png);
}
<ol class="masked">
  <li>One</li>
  <li>Two</li>
  <li>Three</li>
  <li>Four</li>
  <li>Five</li>
</ol>

List items, masked and unmasked

There is one other value option we haven’t seen yet, which is the ability to point directly at a <mask> element in SVG to use the mask it defines. This analogous to pointing to a <clipPath> or other SVG element from the property clip-path, as was discussed previously in “Clipping” on page 965.

Here’s an example of how a mask might be defined:

<svg
  viewbox="0 0 100 100"
  height="100"
  width="100"
  xmlns="http://www.w3.org/2000/svg"
  version="1.1"
>
  <mask id="hexlike">
    <path fill="#FF0000" d="M 50,0 100,25 100,75 50,100 0,75 0,25" />
  </mask>
</svg>

With that SVG embedded in the HTML file directly, the mask can be referenced like this:

.masked {
  mask-image: url(#hexlike);
}

If the SVG is in an external file, then this is how to reference it from CSS:

.masked {
  mask-image: url(masks.svg#hexlike);
}

19.5.2 Changing the Mask’s Mode

Thus far, we’ve seen masking accomplished by applying an image with an alpha channel to another element. That’s one of two ways to use an image as a mask. The other is to use the brightness of each part of the masking image to define the mask. Switching between these two options is accomplished with the mask-mode property.

Two of the three values are straightforward: alpha means the alpha channel of the image should be used to compute the mask, and luminance means the brightness levels should be used. The difference is illustrated in Figure 19-25, which is the result of the following code:

img.theatre {
  mask-image: url(i/theatre-masks.svg);
}
img.compass {
  mask-image: url(i/Compass_masked.png);
}
img.lum {
  mask-mode: luminance;
}
<img src="i/theatre-masks.svg" />
<img class="theatre" src="i/mask.JPG" />
<img class="theatre lum" src="i/mask.JPG" />
<img src="i/Compass_masked.png" />
<img class="compass" src="i/mask.JPG" />
<img class="compass lum" src="i/mask.JPG" />

When luminance is used to calculate the mask, brightness is treated the same way alpha values are in alpha masking. Consider how alpha masking works: any part of the image with opacity of zero hides that part of the masked element. A part of the image with opacity of one (that is, fully opaque) reveals that part of the masked element.

The same is true with luminance-based masking. A part of the mask with luminosity of one reveals that part of the masked element. A part of the mask with luminosity of zero (that is, fully black) hides that part of the masked element. But note that any fully transparent part of the mask is also treated as having a luminance of zero. This is why the shadow portion of the theatre-mask image doesn’t show any part of the masked image: its alpha value is greater than zero.

Alpha and luminance mask modes

The third (and default) value, match-source, is a combination of alpha and lumi nance, choosing between them based on the actual source image for the mask as follows:

  • If the source is a type of <image>, then use alpha. <image>s can be an image such as a PNG or visible SVG; a CSS gradient; or a piece of the page referred to by the element() function.
  • If the source is an SVG <mask> element, then use luminance.

19.5.3 Sizing and Repeating Masks

Thus far, nearly all the examples have been carefully crafted to make each mask’s size match the size of the element it’s masking. (This is why we keeping applying masks to images.) Mask images may be a different size than the masked element. There a couple of ways to deal with this, starting with mask-size.

If you’ve ever sized background images, then you know exactly how to size masks, because the value syntax is exactly the same, as are the behaviors. As an example, consider the following styles, which have the result shown in Figure 19-26:

p {
  mask-image: url(i/hexlike.svg);
}
p:nth-child(1) {
  mask-size: 100% 100%;
}
p:nth-child(2) {
  mask-size: 50% 100%;
}
p:nth-child(3) {
  mask-size: 2em 3em;
}
p:nth-child(4) {
  mask-size: cover;
}
p:nth-child(5) {
  mask-size: contain;
}
p:nth-child(6) {
  mask-size: 200% 50%;
}

Sizing masks

Again, these should be immediately familiar to you if you’ve ever sized backgrounds. If not, please see “Sizing Background Images” on page 433 in Chapter 9 for a more detailed exploration of the possibilities.

In a like vein, just as the pattern of backgrounds repeating throughout the background area of the element can be changed or suppressed, mask images can be affected with mask-repeat.

The values available here are the same as those for background-repeat. Some examples are shown in Figure 19-27, based on the following styles:

p {
  mask-image: url(i/theatre-masks.svg);
}
p:nth-child(1) {
  mask-repeat: no-repeat;
  mask-size: 10% auto;
}
p:nth-child(2) {
  mask-repeat: repeat-x;
  mask-size: 10% auto;
}
p:nth-child(3) {
  mask-repeat: repeat-y;
  mask-size: 10% auto;
}
p:nth-child(4) {
  mask-repeat: repeat;
  mask-size: 30% auto;
}
p:nth-child(5) {
  mask-repeat: repeat round;
  mask-size: 30% auto;
}
p:nth-child(6) {
  mask-repeat: space no-repeat;
  mask-size: 21% auto;
}

Repeating masks

19.5.4 Positioning Masks

Given that sizing and repetition of mask images mirrors the sizing and repetition of background images, you might think that the same is true for positioning the origin mask image, similar to background-position, as well as the origin box, similar to background-origin. And you’d be exactly right.

Once again, if you’ve ever positioned a background image, then you know how to position mask images. Following are a few examples, illustrated in Figure 19-28 (dotted borders have been added for clarity):

p {
  mask-image: url(i/Compass_masked.png);
  mask-repeat: no-repeat;
  mask-size: 67% auto;
}
p:nth-child(1) {
  mask-position: center;
}
p:nth-child(2) {
  mask-position: top right;
}
p:nth-child(3) {
  mask-position: 33% 80%;
}
p:nth-child(4) {
  mask-position: 5em 120%;
}

Positioning masks

By default, the origin box for mask images is the outer border edge. If you want to move it further inward, or define a specific origin box in an SVG context, then maskorigin does for masks what background-origin does for backgrounds.

This is a newer capability for backgrounds, so you might not be familiar with it. For the full story, see “Changing the Positioning Box” on page 414 in Chapter 9, but for a quick example, see Figure 19-29.

Changing the origin box

19.5.5 Clipping and Compositing Masks

There’s one more property that echoes backgrounds, and that’s mask-clip, the mask equivalent of background-clip.

All this does is clip the overall mask to a specific area of the masked element. In other words, it restricts the area in which the visible parts of the element are in fact visible. Figure 19-30 shows the result of the following styles:

p {
  pading: 2em;
  border: 2em solid purple;
  margin: 2em;
  mask-image: url(i/Compass_masked.png);
  mask-repeat: no-repeat;
  mask-size: 125%;
  mask-position: center;
}
p:nth-child(1) {
  mask-clip: border-box;
}
p:nth-child(2) {
  mask-clip: padding-box;
}
p:nth-child(3) {
  mask-clip: content-box;
}

Clipping the mask

The last focused masking property, mask-composite, is quite interesting because it can radically change how multiple masks interact.

mask-composite is not supported by Chrome, even in a prefixed form.

If you aren’t familiar with compositing operations, a diagram is in order. See Figure 19-31.

Compositing operations

As depicted in Figure 19-31, the image on top in the operation is called the source, and the image beneath it is called the destination.

This doesn’t particularly matter for three of the four operations: add, intersect, and exclude, all of which have the same result regardless of which image is the source and which the destination. But for subtract, the question is: which image is being subtracted from which? The answer: the destination is subtracted from the source.

The difference is quite substantial. You can see this by considering Figure 19-32, which shows how switching the order of the shapes in the subtraction operation changes the outcome.

Subtracted masks

The other place the distinction between source and destination becomes important is when compositing multiple masks together. In these cases, the compositing order is from back to front, with each succeeding layer being the source and the alreadycomposited layers beneath it comprising the destination.

To see why, consider Figure 19-33, which shows the various ways three overlapping masks are composited together, and how results change with changes to their order and compositing operations.

The figure is constructed to show the bottommost mask at the bottom, the topmost above the other two, and the resulting mask shown at the very top. Thus, in the first column, the triangle and circle are composited with an exclusion operation. The resulting shape is then composited with the square using an additive operation. That results in the mask shown at the top of the first column.

Just remember that when doing a subtraction composite, the bottom shape is subtracted from the shape above it. Thus, in the third column, the addition of the triangle and circle are subtracted from the square above them.

Compositing masks

19.5.6 Bringing It All Together

All of the preceding mask properties are brought together in the shorthand property mask.

mask, like all the other masking properties, accepts a comma-separated list of masks.

The order of the values in each mask can be anything except for the mask size, which always follows the position and is separated from it by a solidus (/).

Thus, the following rules are equivalent:

#example {
  mask-image: url(circle.svg), url(square.png), url(triangle.gif);
  mask-repeat: repeat-y, no-repeat;
  mask-position: top right, center, 25% 67%;
  mask-composite: subtract, add, add;
  mask-size: auto, 50% 33%, contain;
}
#example {
  mask: url(circle.svg) repeat-y top right / auto subtract, url(square.png)
      no-repeat center / 50% 33% add,
    url(triangle.gif) repeat-y 25% 67% / contain add;
}

What will happen is the triangle and square are added together, and then the result of that additive composite is subtracted from the circle. The result is shown in Figure 19-34 as applied to a square element (the teal shape on the left) and a shape wider than it is tall (the goldenrod shape on the right).

Two masks

19.5.7 Mask Types

In situations where you’re using CSS to style SVG elements, and you want to set the type of mask an SVG <mask> element is, then mask-type is for you.

This property is very much similar to mask-mode, except there is no equivalent to match-source. You can only choose luminance or alpha.

The interesting thing is that if mask-type is set for a <mask> element that’s used to mask an element, and mask-mode is declared for that masked element, then maskmode wins. As example, consider the following rules:

svg #mask {
  mask-type: alpha;
}
img.masked {
  mask: url(#mask) no-repeat center/cover luminance;
}

Given these rules, the masked images will have a mask with luminance compositing, not alpha compositing. If the mask-mode value were left at its default value, matchsource, then ask-type’s value would be used instead.

19.5.8 Border-image Masking

The same specification that defines clipping paths and element masking, CSS Masking Level 1, also defines a number of properties that are used to apply masking images in a way that mirrors border-image properties. In fact, the properties between border images and border-image masks are direct analogues, and the values the same.

The drawback is that as of late 2017, no browser had even a hint of support for these properties, nor was there any indication of plans for such in the near future. So rather than going through them in detail here, we’ll just summarize them here:

mask-border-source

Points to the image to be used as a mask. Can be a URL, gradient, or other <image> value type.

mask-border-slice

Defines how the source image is sliced into pieces for use as borders, and whether the interior is filled.

mask-border-width

Defines the actual width(s) of the border area around the element, into which the various slices of the source image will be placed (and resized, if necessary).

mask-border-outset

Defines a distance past the edges of the element’s default border where the border image may be drawn.

mask-border-repeat

Sets a repetition pattern for cases when the source image’s slices do not precisely fit the border area into which they are placed. This includes behaviors like resizing the image slice to fit.

mask-border-mode

Declares whether the masking mode is alpha-based, or luminance-based.

mask-border

A shorthand property covering all the previous properties.

If you want to get an idea of how these would work in practice, refer to the section of Chapter 8 titled “Image Borders” on page 352 and imagine the border images as masks instead.

19.6 Object Fitting and Positioning

There is one more variety of masking, sort of, that applies solely to replaced elements like images. With object-fit, you can change how the replaced element fills its element box—or have it not fill that box completely.

If you’ve ever worked with background-size, these values probably look familiar.

They do similar things, too, only with replaced elements.

For example, assume a 50 × 50 pixel image. We can change its size via CSS, something like this:

img {
  width: 250px;
  height: 150px;
}

The default expectation is that will stretch the 50 × 50 image to be 250 × 150. And if object-fit is its default value, fill, that’s exactly what happens.

Change the value of object-fit, however, and other behaviors occur, as illustrated in Figure 19-35, which might result from CSS like this:

img {
  width: 250px;
  height: 150px;
  background: silver;
  border: 3px solid;
}
img:nth-of-type(1) {
  object-fit: none;
}
img:nth-of-type(2) {
  object-fit: fill;
}
img:nth-of-type(3) {
  object-fit: cover;
}
img:nth-of-type(4) {
  object-fit: contain;
}

Four kinds of object fitting

In the first instance, none, the img element is drawn 250 pixels wide by 150 pixels tall. The image itself, however, is drawn 50 × 50 pixels—its intrinsic size—because it was directed to not fit the element box. The second instance, fill, is the default behavior, as mentioned.

In the third instance, cover, the image is scaled up until no part of the element box is left “uncovered”—but the image itself keeps its intrinsic aspect ratio. In other words, the image stays a square. In this case, the longest axis of the img element is 250px long, so the image is scaled up to be 250 × 250 pixels. That 250 × 250 image is then placed in the 250 × 150 img element.

The fourth instance, contain, is similar, except the image is only big enough to touch two sides of the img element. This means the image is 150 × 150 pixels, and placed into the 250 × 150 pixel box of its img element.

To reiterate, what you see in Figure 19-35 is four img elements. There are no wrapper div or span or anything other elements around those images. The border and background color are part of the img element. The image placed inside the img element is fitted according to object-fit. The element box of the img element then acts rather like it’s a simple mask for the fitted image inside it. (And then you can mask and clip the element box with the properties covered earlier in this chapter.)

There is a fifth value for object-fit not represented in Figure 19-35, which is scaledown. The meaning of scale-down is “do the same as either none or contain, whichever leads to a smaller size.” This lets an image always be its intrinsic size unless the img element gets too small, in which case it’s scaled down á la contain. This is illustrated in Figure 19-36, where each img element is labeled with the height values they’ve been given; the width in each case is 100px.

Various scale-down scenarios

So if a replaced element is bigger or smaller than the element box into which it’s being fit, how can we affect its alignment within that box? object-position is the answer.

The value syntax here is just like that for mask-position or background-position, allowing you to position a replaced element within its element box if it isn’t set to object-fit: fill. Thus, given the following CSS, we get the result shown in Figure 19-37:

img {
  width: 200px;
  height: 100px;
  background: silver;
  border: 1px solid;
  object-fit: none;
}
img:nth-of-type(2) {
  object-position: top left;
}
img:nth-of-type(3) {
  object-position: 67% 100%;
}
img:nth-of-type(4) {
  object-position: left 142%;
}

A variety of object positions

Notice that the first example in Figure 19-37 has a value of 50% 50%, even though that isn’t present in the CSS sample. That illustrates how the default value of objectposition is 50% 50%. The next two examples show how various object-position values move the image around within the img element box.

As the last example shows, it’s possible to move an unscaled replaced element like an image so that it’s partly clipped by its element box. This is similar to positioning background images or masks so that they are clipped at the element boundaries.

It’s also possible to position fitted elements that are larger than the element box, as can happen with object-fit: cover, although the results can be very different than with object-fit: none. The following CSS will have results like those shown in Figure 19-38:

img {
  width: 200px;
  height: 100px;
  background: silver;
  border: 1px solid;
  object-fit: cover;
}
img:nth-of-type(2) {
  object-position: top left;
}
img:nth-of-type(3) {
  object-position: 67% 100%;
}
img:nth-of-type(4) {
  object-position: left 142%;
}

Positioning a covered object

If any of these results confuse you, review the section “Background Positioning” on page 404 for more details.