CSS: Flexible Repeating SVG Masks
This is a technique I used recently to create a flexible,
repeating pattern using SVG with CSS mask
. There are live
examples in this post and they’re available as a
CodePen.
The need came from something I designed at work. I gave the bottom of the header on each page a little squiggle to make it a bit more interesting than a straight line. Along with that, I designed a color system for each type of page. Common pages parade in purple, company content is true blue, blog posts read in red, and contests own orange. With StreetCred I’ve been throwing a rainbow of bright colors to see what’s fun and what sticks.
Along with the current colors, we’re getting ready to ship new features that use even more colors. We’ll make pages for those and they’ll need themed banner squiggles. We need a flexible way to do that.
How Should This Work?
For the design, I needed a seamless, horizontally repeating pattern. I needed SVG so it was crisp at any size. I wanted a single SVG image that I could color using CSS. Because we could have n-number of colors, creating a new SVG for every color wasn’t a future-facing option. The squiggly banner using this approach is live on streetcred.co. Let’s look at an isolated demo to see how the pieces fit together.
Creating the Demo SVG
First, I made a quick doodle of a squiggly line in Sketch using the vector tool. The important part was to make sure the line was seamless when repeated horizontally. I did that by making sure the first point and last point were at the same location on the y-axis. And that the bezier curves leading to them was flat when it reached the edge of the artboard. This is a simple shape, so it was easy to eyeball. A more complex design would involve more work.
To test if the shape is seamless I just duplicated the artboard and dragged it into place. Nothing fancy again, a quick eyeball is enough for this.
When I create graphics for SVG use, I simplify them as much as possible.
For this, I expanded the stroke. That way instead of exporting a path
with a stroke
, I end up with a path
with a fill
.
In my experience, filled shapes are more flexible when you’re working with them
in browsers. Helps avoid scaling issues with stroke
s. SVG prep is a
deep topic that I plan to write more about in the future.
Exporting SVG from Sketch leaves a lot of crud in that we don’t need. My next
stop is SVGOMG. After
optimizing we end up with an SVG with a single path
element. I
truncated the value of the d
attribute for brevity.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 75">
<path id="squiggle" d="M12 19.778C12 1.318..." />
</svg>
We’ll use the id
attribute as a reference in CSS. Notice we don’t need
presentational attributes like fill
. SVG provides the
shape. CSS handles the color.
The Code
The HTML is a single div
. The CSS is three properties.
<div class="repeater"></div>
.repeater {
background-color: red;
/* This fixed height is only for demo, your use cases might not need it */
height: 75px;
mask-image: url("/path/to/repeater.svg#squiggle");
}
As of this writing, this CSS works in Firefox, but not in Chrome, Safari,
or Edge. WebKit/Blink browsers still require vendor-prefixed mask
properties. We don’t want to have to repeat the path to the image. Let’s store
it in a custom property to make it reusable.
.repeater {
background-color: red;
/* This fixed height is only for demo, your use cases might not need it */
height: 75px;
--svg: url("/path/to/repeater.svg#squiggle");
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
}
With the prefixed version of mask-image
in place, this works in all current
versions of Chrome, Firefox, Safari, and Edge.
What did we do?
mask-image
is doing the heavy lifting here. We’re giving it a reference to an external
SVG file and the id
attribute of the path
we want.
Mask is hiding anything in our div
that doesn’t intersect
with that shape. So we see the background-color
only where the
path
is.
mask
is like CSS bacground
. It has a mask-repeat
property that defaults to repeat
. Check the MDN docs
for more that.
What else?
With a functional foundation in place, we can have fun with it. We can style the underlying div
any
way we want to create different effects. We can change the color.
.repeater--orange {
background-color: orange;
}
We can take that further by setting a background image.
.repeater--gradient {
background: transparent linear-gradient(90deg, red, purple, blue, green);
}
Changing the background color and image is fun, we can also change the height
of the div
to produce something different.
.repeater--sized {
height: 18px;
}
We can change the color, bacground-image, and height. Let’s introduce the
mask-size
property to squish the SVG into a type
of texture.
.repeater--textured {
--texture-lines: 10;
--mask-size: calc(250px / var(--texture-lines));
-webkit-mask-size: var(--mask-size);
mask-size: var(--mask-size);
}
Again, we’re using a custom property to not have to repeat the size value. We’re
using calc
here as a convenience. 250px
is the height
of the viewbox
of the SVG. We divide that by the number of rows
we want to make sure we don’t have partial rows.
We can also change the position of the mask using mask-position
.
And, if we can change a value with CSS, that means we can change it over time.
Change over time equals motion.
@keyframes move {
to {
--pos: 150%;
-webkit-mask-position: var(--pos);
mask-position: var(--pos);
}
}
.repeater--animated {
animation: move 0.6s infinite linear alternate;
}
These are toy examples, but they show that with a few building blocks we can construct a lot of variations. With more complex SVG images and more CSS, we can use this technique to produce all sorts of fun stuff. And be future-flexible while doing it.