Tag: patterns

How to Create Wavy Shapes & Patterns in CSS

The wave is probably one of the most difficult shapes to make in CSS. We always try to approximate it with properties like border-radius and lots of magic numbers until we get something that feels kinda close. And that’s before we even get into wavy patterns, which are more difficult.

“SVG it!” you might say, and you are probably right that it’s a better way to go. But we will see that CSS can make nice waves and the code for it doesn’t have to be all crazy. And guess what? I have an online generator to make it even more trivial!

If you play with the generator, you can see that the CSS it spits out is only two gradients and a CSS mask property — just those two things and we can make any kind of wave shape or pattern. Not to mention that we can easily control the size and the curvature of the waves while we’re at it.

Some of the values may look like “magic numbers” but there’s actually logic behind them and we will dissect the code and discover all the secrets behind creating waves.

This article is a follow-up to a previous one where I built all kinds of different zig-zag, scoped, scalloped, and yes, wavy border borders. I highly recommend checking that article as it uses the same technique we will cover here, but in greater detail.

The math behind waves

Strictly speaking, there isn’t one magic formula behind wavy shapes. Any shape with curves that go up and down can be called a wave, so we are not going to restrict ourselves to complex math. Instead, we will reproduce a wave using the basics of geometry.

Let’s start with a simple example using two circle shapes:

Two gray circles.

We have two circles with the same radius next to each other. Do you see that red line? It covers the top half of the first circle and the bottom half of the second one. Now imagine you take that line and repeat it.

A squiggly red line in the shape of waves.

We already see the wave. Now let’s fill the bottom part (or the top one) to get the following:

Red wave pattern.

Tada! We have a wavy shape, and one that we can control using one variable for the circle radii. This is one of the easiest waves we can make and it’s the one I showed off in this previous article

Let’s add a bit of complexity by taking the first illustration and moving the circles a little:

Two gray circles with two bisecting dashed lines indicating spacing.

We still have two circles with the same radii but they are no longer horizontally aligned. In this case, the red line no longer covers half the area of each circle, but a smaller area instead. This area is limited by the dashed red line. That line crosses the point where both circles meet.

Now take that line and repeat it and you get another wave, a smoother one.

A red squiggly line.
A red wave pattern.

I think you get the idea. By controlling the position and size of the circles, we can create any wave we want. We can even create variables for them, which I will call P and S, respectively.

You have probably noticed that, in the online generator, we control the wave using two inputs. They map to the above variables. S is the “Size of the wave” and P is the “curvature of the wave”.

I am defining P as P = m*S where m is the variable you adjust when updating the curvature of the wave. This allows us to always have the same curvature, even if we update S.

m can be any value between 0 and 2. 0 will give us the first particular case where both circles are aligned horizontally. 2 is a kind of maximum value. We can go bigger, but after a few tests I found that anything above 2 produces bad, flat shapes.

Let’s not forget the radius of our circle! That can also be defined using S and P like this:

R = sqrt(P² + S²)/2

When P is equal to 0, we will have R = S/2.

We have everything to start converting all of this into gradients in CSS!

Creating gradients

Our waves use circles, and when talking about circles we talk about radial gradients. And since two circles define our wave, we will logically be using two radial gradients.

We will start with the particular case where P is equal to 0. Here is the illustration of the first gradient:

This gradient creates the first curvature while filling in the entire bottom area —the “water” of the wave so to speak.

.wave {   --size: 50px;    mask: radial-gradient(var(--size) at 50% 0%, #0000 99%, red 101%)      50% var(--size)/calc(4 * var(--size)) 100% repeat-x; }

The --size variable defines the radius and the size of the radial gradient. If we compare it with the S variable, then it’s equal to S/2.

Now let’s add the second gradient:

The second gradient is nothing but a circle to complete our wave:

radial-gradient(var(--size) at 50% var(--size), blue 99%, #0000 101%)    calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%

If you check the previous article you will see that I am simply repeating what I already did there.

I followed both articles but the gradient configurations are not the same.

That’s because we can reach the same result using different gradient configurations. You will notice a slight difference in the alignment if you compare both configurations, but the trick is the same. This can be confusing if you are unfamiliar with gradients, but don’t worry. With some practice, you get used to them and you will find by yourself that different syntax can lead to the same result.

Here is the full code for our first wave:

.wave {   --size: 50px;    mask:     radial-gradient(var(--size) at 50% var(--size),#000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--size) at 50% 0px, #0000 99%, #000 101%)        50% var(--size)/calc(4 * var(--size)) 100% repeat-x; }

Now let’s take this code and adjust it to where we introduce a variable that makes this fully reusable for creating any wave we want. As we saw in the previous section, the main trick is to move the circles so they are no more aligned so let’s update the position of each one. We will move the first one up and the second down.

Our code will look like this:

.wave {   --size: 50px;   --p: 25px;    mask:     radial-gradient(var(--size) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--size) at 50% calc(-1*var(--p)), #0000 99%, #000 101%)        50% var(--size) / calc(4 * var(--size)) 100% repeat-x; }

I have introduced a new --p variable that’s used it to define the center position of each circle. The first gradient is using 50% calc(-1*var(--p)), so its center moves up while the second one is using calc(var(--size) + var(--p)) to move it down.

A demo is worth a thousand words:

The circles are neither aligned nor touch one another. We spaced them far apart without changing their radii, so we lost our wave. But we can fix things up by using the same math we used earlier to calculate the new radius. Remember that R = sqrt(P² + S²)/2. In our case, --size is equal to S/2; the same for --p which is also equal to P/2 since we are moving both circles. So, the distance between their center points is double the value of --p for this:

R = sqrt(var(--size) * var(--size) + var(--p) * var(--p))

That gives us a result of 55.9px.

Our wave is back! Let’s plug that equation into our CSS:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p) * var(--p) + var(--size)*var(--size));    mask:     radial-gradient(var(--R) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0 / calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at 50% calc(-1*var(--p)), #0000 99%, #000 101%)        50% var(--size)/calc(4 * var(--size)) 100% repeat-x; }

This is valid CSS code. sqrt() is part of the specification, but at the time I’m writing this, there is no browser support for it. That means we need a sprinkle of JavaScript or Sass to calculate that value until we get broader sqrt() support.

This is pretty darn cool: all it takes is two gradients to get a cool wave that you can apply to any element using the mask property. No more trial and error — all you need is to update two variables and you’re good to go!

Reversing the wave

What if we want the waves going the other direction, where we’re filling in the “sky” instead of the “water”. Believe it or not, all we have to do is to update two values:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p) * var(--p) + var(--size) * var(--size));    mask:     radial-gradient(var(--R) at 50% calc(100% - (var(--size) + var(--p))), #000 99%, #0000 101%)       calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at 50% calc(100% + var(--p)), #0000 99%, #000 101%)        50% calc(100% - var(--size)) / calc(4 * var(--size)) 100% repeat-x; }

All I did there is add an offset equal to 100%, highlighted above. Here’s the result:

We can consider a more friendly syntax using keyword values to make it even easier:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size) * var(--size));    mask:     radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%)        calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%)        left 50% bottom var(--size) / calc(4 * var(--size)) 100% repeat-x; }

We’re using the left and bottom keywords to specify the sides and the offset. By default, the browser defaults to left and top — that’s why we use 100% to move the element to the bottom. In reality, we are moving it from the top by 100%, so it’s really the same as saying bottom. Much easier to read than math!

With this updated syntax, all we have to do is to swap bottom for top — or vice versa — to change the direction of the wave.

And if you want to get both top and bottom waves, we combine all the gradients in a single declaration:

.wave {   --size: 50px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));    mask:     /* Gradient 1 */     radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%)        left calc(50% - 2*var(--size)) bottom 0 / calc(4 * var(--size)) 51% repeat-x,     /* Gradient 2 */     radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%)        left 50% bottom var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x,     /* Gradient 3 */     radial-gradient(var(--R) at left 50% top calc(var(--size) + var(--p)), #000 99%, #0000 101%)        left calc(50% - 2 * var(--size)) top 0 / calc(4 * var(--size)) 51% repeat-x,     /* Gradient 4 */     radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), #0000 99%, #000 101%)        left 50% top var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x; }

If you check the code, you will see that in addition to combining all the gradients, I have also reduced their height from 100% to 51% so that they both cover half of the element. Yes, 51%. We need that little extra percent for a small overlap that avoid gaps.

What about the left and right sides?

It’s your homework! Take what we did with the top and bottom sides and try to update the values to get the right and left values. Don’t worry, it’s easy and the only thing you need to do is to swap values.

If you have trouble, you can always use the online generator to check the code and visualize the result.

Wavy lines

Earlier, we made our first wave using a red line then filled the bottom portion of the element. How about that wavy line? That’s a wave too! Even better is if we can control its thickness with a variable so we can reuse it. Let’s do it!

We are not going to start from scratch but rather take the previous code and update it. The first thing to do is to update the color stops of the gradients. Both gradients start from a transparent color to an opaque one, or vice versa. To simulate a line or border, we need to start from transparent, go to opaque, then back to transparent again:

#0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%

I think you already guessed that the --b variable is what we’re using to control the line thickness. Let’s apply this to our gradients:

Yeah, the result is far from a wavy line. But looking closely, we can see that one gradient is correctly creating the bottom curvature. So, all we really need to do is rectify the second gradient. Instead of keeping a full circle, let’s make partial one like the other gradient.

Still far, but we have both curvatures we need! If you check the code, you will see that we have two identical gradients. The only difference is their positioning:

.wave {   --size: 50px;   --b: 10px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));    --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;   mask:     radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g))        calc(50% - 2*var(--size)) 0/calc(4*var(--size)) 100%,     radial-gradient(var(--R) at left 50% top    calc(-1*var(--p)), var(--_g))        50% var(--size)/calc(4*var(--size)) 100%; }

Now we need to adjust the size and position for the final shape. We no longer need the gradient to be full-height, so we can replace 100% with this:

/* Size plus thickness */ calc(var(--size) + var(--b))

There is no mathematical logic behind this value. It only needs to be big enough for the curvature. We will see its effect on the pattern in just a bit. In the meantime, let’s also update the position to vertically center the gradients:

.wave {   --size: 50px;   --b: 10px;   --p: 25px;   --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));    --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;     mask:     radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g))        calc(50% - 2*var(--size)) 50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat,     radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), var(--_g)) 50%       50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat; }

Still not quite there:

One gradient needs to move a bit down and the other a bit up. Both need to move by half of their height.

We are almost there! We need a small fix for the radius to have a perfect overlap. Both lines need to offset by half the border (--b) thickness:

We got it! A perfect wavy line that we can easily adjust by controlling a few variables:

.wave {   --size: 50px;   --b: 10px;   --p: 25px;   --R: calc(sqrt(var(--p) * var(--p) + var(--size) * var(--size)) + var(--b) / 2);    --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;   mask:     radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), var(--_g))       calc(50% - 2*var(--size)) calc(50% - var(--size)/2 - var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x,     radial-gradient(var(--R) at left 50% top calc(-1*var(--p)),var(--_g))       50%  calc(50% + var(--size)/2 + var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x; }

I know that the logic takes a bit to grasp. That’s fine and as I said, creating a wavy shape in CSS is not easy, not to mention the tricky math behind it. That’s why the online generator is a lifesaver — you can easily get the final code even if you don’t fully understand the logic behind it.

Wavy patterns

We can make a pattern from the wavy line we just created!

Oh no, the code of the pattern will be even more difficult to understand!

Not at all! We already have the code. All we need to do is to remove repeat-x from what we already have, and tada. 🎉

A nice wavy pattern. Remember the equation I said we’d revisit?

/* Size plus thickness */ calc(var(--size) + var(--b))

Well, this is what controls the distance between the lines in the pattern. We can make a variable out of it, but there’s no need for more complexity. I’m not even using a variable for that in the generator. Maybe I’ll change that later.

Here is the same pattern going in a different direction:

I am providing you with the code in that demo, but I’d for you to dissect it and understand what changes I made to make that happen.

Simplifying the code

In all the previous demos, we always define the --size and --p independently. But do you recall how I mentioned earlier that the online generator evaluates P as equal to m*S, where m controls the curvature of the wave? By defining a fixed multiplier, we can work with one particular wave and the code can become easier. This is what we will need in most cases: a specific wavy shape and a variable to control its size.

Let’s update our code and introduce the m variable:

.wave {   --size: 50px;   --R: calc(var(--size) * sqrt(var(--m) * var(--m) + 1));    mask:     radial-gradient(var(--R) at 50% calc(var(--size) * (1 + var(--m))), #000 99%, #0000 101%)        calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,     radial-gradient(var(--R) at 50% calc(-1 * var(--size) * var(--m)), #0000 99%, #000 101%)        50% var(--size) / calc(4 * var(--size)) 100% repeat-x;   }

As you can see, we no longer need the --p variable. I replaced it with var(--m)*var(--size), and optimized some of the math accordingly. Now, If we want to work with a particular wavy shape, we can omit the --m variable and replace it with a fixed value. Let’s try .8 for example.

--size: 50px; --R: calc(var(--size) * 1.28);  mask:   radial-gradient(var(--R) at 50% calc(1.8 * var(--size)), #000 99%, #0000 101%)      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,   radial-gradient(var(--R) at 50% calc(-.8 * var(--size)), #0000 99%, #000 101%)      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;

See how the code is easier now? Only one variable to control your wave, plus you no more need to rely on sqrt() which has no browser support!

You can apply the same logic to all the demos we saw even for the wavy lines and the pattern. I started with a detailed mathmatical explanation and gave the generic code, but you may find yourself needing easier code in a real use case. This is what I am doing all the time. I rarely use the generic code, but I always consider a simplified version especially that, in most of the cases, I am using some known values that don’t need to be stored as variables. (Spoiler alert: I will be sharing a few examples at the end!)

Limitations to this approach

Mathematically, the code we made should give us perfect wavy shapes and patterns, but in reality, we will face some strange results. So, yes, this method has its limitations. For example, the online generator is capable of producing poor results, especially with wavy lines. Part of the issue is due to a particular combination of values where the result gets scrambled, like using a big value for the border thickness compared to the size:

For the other cases, it’s the issue related to some rounding that will results in misalignment and gaps between the waves:

That said, I still think the method we covered remains a good one because it produces smooth waves in most cases, and we can easily avoid the bad results by playing with different values until we get it perfect.

Wrapping up

I hope that after this article, you will no more to fumble around with trial and error to build a wavy shape or pattern. In addition to the online generator, you have all the math secrets behind creating any kind of wave you want!

The article ends here but now you have a powerful tool to create fancy designs that use wavy shapes. Here’s inspiration to get you started…

What about you? Use my online generator (or write the code manually if you already learned all the math by heart) and show me your creations! Let’s have a good collection in the comment section.


How to Create Wavy Shapes & Patterns in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,

Implicit Grids, Repeatable Layout Patterns, and Danglers

Dave Rupert with some modern CSS magic that tackles one of those classic conundrums: what happens when the CSS for component is unable to handle the content we throw at it?

The specific situation is when a layout grid expects an even number of items, but is supplied with an odd number instead. We’re left with a “dangling” element at the end that throws off the layout. Sounds like what’s needed is some Defensive CSS and Dave accomplishes it.

He reaches for :has() to write a nifty selector that sniffs out the last item in a grid that contains an odd number of items:

.items:has(.item:last-of-type:nth-of-type(odd)) .item:first-of-type { }

Breaking that down:

  • We have a parent container of .items.
  • If the container :has() an .item child that is the last of its type,
  • …and that .item happens to be an odd-numbered instance,
  • …then select the first .item element of that type and style it!

In this case, that last .item can be set to go full-width to prevent holes in the layout.

If… then… CSS has conditional logic powers! We’re only talking about support for Safari TP and Edge/Chrome Canary at the moment, but that’s pretty awesome.

As chance has it, Temani Afif recently shared tricks he learned while experimenting with implicit grids. By taking advantage of CSS Grid’s auto-placement algorithm, we don’t even have to explicitly declare a fixed number of columns and rows for a grid — CSS will create them for us if they’re needed!

No, Temani’s techniques aren’t alternative solutions to Dave’s “dangler” dilemma. But combining Temani’s approach to repeatable grid layout patterns with Dave’s defensive CSS use of :has(), we get a pretty powerful and complex-looking grid that’s lightweight and capable of handling any number of items while maintaining a balanced, repeatable pattern.


Implicit Grids, Repeatable Layout Patterns, and Danglers originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

How stroke-dasharray Patterns Work

Say you have a line in SVG:

<svg>   <line x1="0" y1="30" x2="500" y2="30" stroke-color="#f8a100" /> </svg>

You can use the stroke-dasharray property in CSS to make dashes:

line {   stroke-dasharray: 5; }

That 5 value is a relative unit based on the size of the SVG’s viewBox. We could use any CSS length, really. But what it does is make a pattern of dashes that are 5 units long with 5 unit gaps between them.

So far, so good. We can use two values where the second value individually sets the gap length:

Now we have dashes that are 5 units and gaps that are 10. Let’s try a third value:

See how we’re forming a pattern here? It goes:

  • Dash: 5 units
  • Gap: 10 units
  • Dash: 15 units

You’d think it repeats after that in the exact same cadence. But no! It if did, we’d have dashes bumping into one another:

  • Dash: 5 units
  • Gap: 10 units
  • Dash: 15 units
  • Dash: 5 units
  • Gap: 10 units
  • Dash: 15 units
  • …and so on.

Instead, stroke-dasharray gets all smart and duplicates the pattern if there are an odd number of values So…

stroke-dasharray: 5 10 15;  /* is the same as */ stroke-dasharray: 5 10 15 5 10 15;

That’s actually why a single value works! Earlier, we declared a single 5 value. That’s really the same as saying stroke-dasharray: 5 5. Without the second value, stroke-dasharray implicitly duplicates the first value to get a repeatable pattern. Otherwise, it’d just be a solid line of dashes that are 5 units long, but no gaps between them!

The pattern also depends on the size of the shape itself. Our SVG line is 500 units. Let’s set larger stroke-dasharray values and add them up:

stroke-dasharray: 10 20 30 40 50; /* 150 units */

If the pattern runs four times (150 units ⨉ 4 iterations), we’re dealing with 600 total units. That additional 100 units is lopped off to prevent the pattern from overflowing itself.

That’s all.

🎩 Hat tip to Joshua Dance for calling this out!


How stroke-dasharray Patterns Work originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, ,
[Top]

How to Create Block Theme Patterns in WordPress 6.0

Block patterns, also frequently referred to as sections, were introduced in WordPress 5.5 to allow users to build and share predefined block layouts in the pattern directory. The directory is the home of a wide range of curated patterns designed by the WordPress community. These patterns are available in simple copy and paste format, require no coding knowledge and thus are a big time saver for users.

Despite many articles on patterns, there is a lack of comprehensive and up-to-date articles on pattern creation covering the latest enhanced features. This article aims to fill the gap with a focus on the recent enhanced features like creating patterns without registration and offer an up-to-date step-by-step guide to create and use them in block themes for novices and experienced authors.

Since the launch of WordPress 5.9 and the Twenty Twenty-Two (TT2) default theme, the use of block patterns in block themes has proliferated. I have been a big fan of block patterns and have created and used them in my block themes.

The new WordPress 6.0 offers three major patterns feature enhancements to authors:

  • Allowing pattern registration through /patterns folder (similar to /parts, /templates, and /styles registration).
  • Registering patterns from the public patterns directory using the theme.json.
  • Adding patterns that can be offered to the user when creating a new page.

In an introductory Exploring WordPress 6.0 video, Automattic product liaison Ann McCathy highlights some newly enhanced patterns features (starting at 15:00) and discusses future patterns enhancement plans — which include patterns as sectioning elements, providing options to pick pattern on page creation, integrating pattern directory search, and more.

Prerequisites

The article assumes that readers have basic knowledge of WordPress full site editing (FSE) interface and block themes. The Block Editor Handbook and Full Site Editing website provide the most up-to-date tutorial guides to learn all FSE features, including block themes and patterns discussed in this article.

Section 1: Evolving approaches to creating block patterns

The initial approach to creating block patterns required the use of block pattern API either as a custom plugin or directly registered in the functions.php file to bundle with a block theme. The newly launched WordPress 6.0 introduced several new and enhanced features working with patterns and themes, including pattern registration via a /patterns folder and a page creation pattern modal.

For background, let’s first briefly overview how the pattern registration workflow evolved from using the register pattern API to directly loading without registration.

Use case example 1: Twenty Twenty-One

The default Twenty Twenty-One theme (TT1) and TT1 Blocks theme (a sibling of TT1) showcase how block patterns can be registered in the theme’s functions.php. In the TT1 Blocks experimental-theme, this single block-pattern.php file containing eight block patterns is added to the functions.php as an include as shown here.

A custom block pattern needs to be registered using the register_block_pattern function, which receives two arguments — title (name of the patterns) and properties (an array describing properties of the pattern).

Here is an example of registering a simple “Hello World” paragraph pattern from this Theme Shaper article:

register_block_pattern(     'my-plugin/hello-world',     array(         'title'   => __( 'Hello World', 'my-plugin' ),         'content' => "<!-- wp:paragraph -->n<p>Hello World</p>n<!-- /wp:paragraph -->",     ) );

After registration, the register_block_pattern() function should be called from a handler attached to the init hook as described here.

 function my_plugin_register_my_patterns() {     register_block_pattern( ... );   }    add_action( 'init', 'my_plugin_register_my_patterns' );

Once block patterns are registered they are visible in the block editor. More detailed documentation is found in this Block Pattern Registration.

Block pattern properties

In addition to required title and content properties, the block editor handbook lists the following optional pattern properties:

  • title (required): A human-readable title for the pattern.
  • content (required): Block HTML Markup for the pattern.
  • description (optional): A visually hidden text used to describe the pattern in the inserter. A description is optional but it is strongly encouraged when the title does not fully describe what the pattern does. The description will help users discover the pattern while searching.
  • categories (optional): An array of registered pattern categories used to group block patterns. Block patterns can be shown on multiple categories. A category must be registered separately in order to be used here.
  • keywords (optional): An array of aliases or keywords that help users discover the pattern while searching.
  • viewportWidth (optional): An integer specifying the intended width of the pattern to allow for a scaled preview of the pattern in the inserter.
  • blockTypes (optional): An array of block types that the pattern is intended to be used with. Each value needs to be the declared block’s name.
  • inserter (optional): By default, all patterns will appear in the inserter. To hide a pattern so that it can only be inserted programmatically, set the inserter to false.

The following is an example of a quote pattern plugin code snippets taken from the WordPress blog.

/* Plugin Name: Quote Pattern Example Plugin */  register_block_pattern(     'my-plugin/my-quote-pattern',      array(       'title'       => __( 'Quote with Avatar', 'my-plugin' ),       'categories'  => array( 'text' ),       'description' => _x( 'A big quote with an avatar".', 'Block pattern description', 'my-plugin' ),       'content'     => '<!-- wp:group --><div class="wp-block-group"><div class="wp-block-group__inner-container"><!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator --><!-- wp:image {"align":"center","id":553,"width":150,"height":150,"sizeSlug":"large","linkDestination":"none","className":"is-style-rounded"} --><div class="wp-block-image is-style-rounded"><figure class="aligncenter size-large is-resized"><img src="https://blockpatterndesigns.mystagingwebsite.com/wp-content/uploads/2021/02/StockSnap_HQR8BJFZID-1.jpg" alt="" class="wp-image-553" width="150" height="150"/></figure></div><!-- /wp:image --><!-- wp:quote {"align":"center","className":"is-style-large"} --><blockquote class="wp-block-quote has-text-align-center is-style-large"><p>"Contributing makes me feel like I'm being useful to the planet."</p><cite>— Anna Wong, <em>Volunteer</em></cite></blockquote><!-- /wp:quote --><!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator --></div></div><!-- /wp:group -->',       ) );

Using patterns in a template file

Once patterns are created, they can be used in a theme template file with the following block markup:

<!-- wp:pattern {"slug":"prefix/pattern-slug"} /-->

An example from this GitHub repository shows the use of “footer-with-background” pattern slug with “tt2gopher” prefix in TT2 Gopher blocks theme.

Early adopters of block themes and Gutenberg plugin took advantage of patterns in classic themes as well. The default Twenty Twenty and my favorite Eksell themes (a demo site here) are good examples that showcase how pattern features can be added to classic themes.

Use case example 2: Twenty Twenty-Two

If a theme includes only a few patterns, the development and maintenance are fairly manageable. However, if a block theme includes many patterns, like in TT2 theme, then the pattern.php file becomes very large and hard to read. The default TT2 theme, which bundles more than 60 patterns, showcases a refactored pattern registration workflow structure that is easier to read and maintain.

Taking examples from the TT2 theme, let’s briefly discuss how this simplified workflow works.

2.1: Registering Patterns Categories

For demonstration purposes, I created a TT2 child theme and set it up on my local test site with some dummy content. Following TT2, I registered footer-with-background and added to the following pattern categories array list in its block-patterns.php file.

/** * Registers block patterns and categories. */ function twentytwentytwo_register_block_patterns() { 	$ block_pattern_categories = array( 		'footer'   => array( 'label' => __( 'Footers', 'twentytwentytwo' ) ), 		'header'   => array( 'label' => __( 'Headers', 'twentytwentytwo' ) ), 		'pages'    => array( 'label' => __( 'Pages', 'twentytwentytwo' ) ),                 // ... 	);  	/** 	 * Filters the theme block pattern categories. 	 */ 	$ block_pattern_categories = apply_filters( 'twentytwentytwo_block_pattern_categories', $ block_pattern_categories );  	foreach ( $ block_pattern_categories as $ name => $ properties ) { 		if ( ! WP_Block_Pattern_Categories_Registry::get_instance()->is_registered( $ name ) ) { 			register_block_pattern_category( $ name, $ properties ); 		} 	}  	$ block_patterns = array( 		'footer-default', 		'footer-dark', 		'footer-with-background', 		//... 		'header-default', 		'header-large-dark', 		'header-small-dark', 		'hidden-404', 		'hidden-bird', 		//... 	);  	/** 	 * Filters the theme block patterns. 	 */ 	$ block_patterns = apply_filters( 'twentytwentytwo_block_patterns', $ block_patterns );  	foreach ( $ block_patterns as $ block_pattern ) { 		$ pattern_file = get_theme_file_path( '/inc/patterns/' . $ block_pattern . '.php' );  		register_block_pattern( 			'twentytwentytwo/' . $ block_pattern, 			require $ pattern_file 		); 	} } add_action( 'init', 'twentytwentytwo_register_block_patterns', 9 );

In this code example, each pattern listed in the $ block_patterns = array() is called by foreach() function which requires a patterns directory file with the listed pattern name in the array which we will add in the next step.

2.2: Adding a pattern file to the /inc/patterns folder

Next, we should have all the listed patterns files in the $ block_patterns = array(). Here is an example of one of the pattern files, footer-with-background.php:

/**  * Dark footer wtih title and citation  */ return array( 	'title'      => __( 'Footer with background', 'twentytwentytwo' ), 	'categories' => array( 'footer' ), 	'blockTypes' => array( 'core/template-part/footer' ),   'content'    => '<!-- wp:group {"align":"full","style":{"elements":{"link":{"color":{"text":"var:preset|color|background"}}},"spacing":{"padding":{"top":"var(--wp--custom--spacing--small, 1.25rem)","bottom":"var(--wp--custom--spacing--small, 1.25rem)"}}},"backgroundColor":"background-header","textColor":"background","layout":{"inherit":true}} -->       <div class="wp-block-group alignfull has-background-color has-background-header-background-color has-text-color has-background has-link-color" style="padding-top:var(--wp--custom--spacing--small, 1.25rem);padding-bottom:var(--wp--custom--spacing--small, 1.25rem)"><!-- wp:paragraph {"align":"center"} -->       <p class="has-text-align-center">' .       sprintf(         /* Translators: WordPress link. */         esc_html__( 'Proudly powered by %s', 'twentytwentytwo' ),         '<a href="' . esc_url( __( 'https://wordpress.org', 'twentytwentytwo' ) ) . '" rel="nofollow">WordPress</a> | a modified TT2 theme.'       ) . '</p>       <!-- /wp:paragraph --></div>           <!-- /wp:group -->', );

Let’s reference the pattern in the footer.html template part:

<!-- wp:template-part {"slug":"footer"} /-->

This is similar to adding heading or footer parts in a template file.

The patterns can similarly be added to the parts/footer.html template by modifying it to refer to slug of the theme’s pattern file (footer-with-background):

<!-- wp:pattern {"slug":"twentytwentytwo/footer-with-background"} /-->

Now, if we visit the patterns inserter of the block editor, the Footer with background should be available for our use:

Screenshot of the pattern for Footer with background.

The following screenshot shows the newly created footer with background pattern on the front-end.

Screenshot of the footer background as it appears rendered on the site.

Now that patterns have become the integral part of block theme, many patterns are bundled in block themes — like Quadrat, Seedlet, Mayland, Zoologist, Geologist — following the workflow discussed above. Here is an example of the Quadrat theme /inc/patterns folder with a block-pattern registration file and list of files with content source and required pattern header within return array() function.

Section 2: Creating and loading patterns without registration

Please note that this feature requires the installation of WordPress 6.0 or Gutenberg plugin 13.0 or above in your site.

This new WordPress 6.0 feature allows pattern registration via standard files and folders – /patterns, bringing similar conventions like /parts, /templates, and /styles.

The process, as also described in this WP Tavern article, involves the following three steps:

  • creating a patterns folder at the theme’s root
  • adding plugin style pattern header
  • pattern source content

A typical pattern header markup, taken from the article is shown below:

<?php /** * Title: A Pattern Title * Slug: namespace/slug * Description: A human-friendly description. * Viewport Width: 1024 * Categories: comma, separated, values * Keywords: comma, separated, values * Block Types: comma, separated, values * Inserter: yes|no */ ?>

As described in the previous section, only Title and Slug fields are required and other fields are optional.

Referencing examples from recently released themes, I refactored pattern registration in this TT2 Gopher Blocks demo theme, prepared for a previous article on the CSS-Tricks.

In the following steps, let’s explore how a footer-with-background.php pattern registered with PHP and used in a footer.html template is refactored.

2.1: Create a /patterns folder at the root of the theme

The first step is to create a /patterns folder at TT2 Gopher theme’s root and move the footer-with-background.php pattern file to /patterns folder and refactor.

2.2: Add pattern data to the file header

Next, create the following pattern header registration fields.

<?php /** * Title: Footer with background * Slug: tt2gopher/footer-with-background * Categories: tt2gopher-footer * Viewport Width: 1280 * Block Types: core/parts/footer * Inserter: yes */ ?> <!-- some-block-content /-->

A pattern file has a top title field written as PHP comments. Followed by the block-content written in HTML format.

2.3: Add Pattern Content to the file

For the content section, let’s copy the code snippets within single quotes (e.g., '...') from the content section of the footer-with-background and replace the <!-- some-block-content /-->:

<!-- wp:group {"align":"full","style":{"elements":{"link":{"color":{"text":"var:preset|color|foreground"}}},"spacing":{"padding":{"top":"35px","bottom":"30px"}}},"backgroundColor":"background-header","textColor":"foreground","className":"has-foreground","layout":{"inherit":true}} -->     <div class="wp-block-group alignfull has-foreground has-foreground-color has-background-header-background-color has-text-color has-background has-link-color" style="padding-top:35px;padding-bottom:30px"><!-- wp:paragraph {"align":"center","fontSize":"small"} -->     <p class="has-text-align-center has-small-font-size">Powered by WordPress | TT2 Gopher, a modified TT2 theme</p>     <!-- /wp:paragraph --></div> <!-- /wp:group -->

The entire code snippet of the patterns/footer-with-background.php file can be viewed here on the GitHub.

Please note that the /inc/patterns and block-patterns.php are extras, not required in WordPress 6.0, and included only for demo purposes.

2.4: Referencing the patterns slug in the template

Adding the above refactored footer-with-background.php pattern to footer.html template is exactly the same as described in the previous section (Section 1, 2.2).

Now, if we view the site’s footer part in a block editor or front-end of our site in a browser, the footer section is displayed.

Pattern categories and types Registration (optional)

To categorize block patterns, the pattern categories and types should be registered in theme’s functions.php file.

Let’s consider an example of registering block pattern categories from the TT2 Gopher theme.

After the registration, the patterns are displayed in the pattern inserter together with the core default patterns. To add theme specific category labels in the patterns inserter, we should modify the previous snippets by adding theme namespace:

/** * Registers block categories, and type. */  function tt2gopher_register_block_pattern_categories() {  $ block_pattern_categories = array(   'tt2gopher-header' => array( 'label' => __( 'TT2 Gopher - Headers', 'tt2gopher' ) ),   'tt2gopher-footer' => array( 'label' => __( 'TT2 Gopher - Footers', 'tt2gopher' ) ),   'tt2gopher-page' => array( 'label' => __( 'TT2 Gopher - Page', 'tt2gopher' ) ),   // ... );  /** * Filters the theme block pattern categories. */ $ block_pattern_categories = apply_filters( 'tt2gopher_block_pattern_categories', $ block_pattern_categories );  foreach ( $ block_pattern_categories as $ name => $ properties ) {   if ( ! WP_Block_Pattern_Categories_Registry::get_instance()->is_registered( $ name ) ) {     register_block_pattern_category( $ name, $ properties );   } } add_action( 'init', 'tt2gopher_register_block_pattern_categories', 9 );

The footer-with-background pattern is visible in the patterns inserted with its preview (if selected):

Screenshot showing pattern category displayed in patterns inserter (left panel) and corresponding default footer pattern displayed in the editor (right panel).

This process greatly simplifies creating and displaying block patterns in block themes. It is available in WordPress 6.0 without using the Gutenberg plugin.

Examples of themes without patterns registration

Early adopters have already started using this feature in their block themes. A few examples of the themes, that are available from the theme directory, that load patterns without registration are listed below:

Section 3: Creating and using patterns with low-code

The official patterns directory contains community-contributed creative designs, which can be copied and customized as desired to create content. Using patterns with a block editor has never been so easier!

Any patterns from the ever-growing directory can also be added to block themes just by simple “copy and paste” or include in the theme.json file by referring to their directory pattern slug. Next, I will go through briefly how easily this can be accomplished with very limited coding.

Adding and customizing patterns from patterns directory

3.1: Copy pattern from directory into a page

Here, I am using this footer section pattern by FirstWebGeek from the patterns directory. Copied the pattern by selecting the “Copy Pattern” button and directly pasted it in a new page.

3.2: Make desired customizations

I made only a few changes to the color of the fonts and button background. Then copied the entire code from the code editor over to a clipboard.

Screenshot showing modified pattern (left panel) and corresponding code in code editor (right panel)

If you are not familiar with using the code editor, go to options (with three dots, top right), click the Code editor button, and copy the entire code from here.

3.3: Create a new file in /patterns folder

First, let’s create a new /patterns/footer-pattern-test.php file and add the required pattern header section. Then paste the entire code (step 3, above). The pattern is categorized in the footer area (lines: 5), we can view the newly added in the pattern inserter.

<?php  /**  * Title: Footer pattern from patterns library  * Slug: tt2gopher/footer-pattern-test  * Categories: tt2gopher-footer  * Viewport Width: 1280  * Block Types: core/template-part/footer  * Inserter: yes  */ ?>  <!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"100px","bottom":"70px","right":"30px","left":"30px"}}},"backgroundColor":"black","layout":{"contentSize":"1280px"}} --> <div class="wp-block-group alignfull has-black-background-color has-background" style="padding-top:100px;padding-right:30px;padding-bottom:70px;padding-left:30px"><!-- wp:columns --> <div class="wp-block-columns"><!-- wp:column --> <div class="wp-block-column"><!-- wp:heading {"style":{"typography":{"fontStyle":"normal","fontWeight":"700","textTransform":"uppercase"}},"textColor":"cyan-bluish-gray"} --> <h2 class="has-cyan-bluish-gray-color has-text-color" style="font-style:normal;font-weight:700;text-transform:uppercase">lorem</h2> <!-- /wp:heading -->  <!-- wp:paragraph {"style":{"typography":{"fontSize":"16px"}},"textColor":"cyan-bluish-gray"} --> <p class="has-cyan-bluish-gray-color has-text-color" style="font-size:16px">One of the main benefits of using Lorem Ipsum is that it can be easily generated, and it takes the pressure off designers to create meaningful text. Instead, they can focus on crafting the best website data.</p> <!-- /wp:paragraph -->  <!-- wp:social-links {"iconColor":"vivid-cyan-blue","iconColorValue":"#0693e3","openInNewTab":true,"className":"is-style-logos-only","style":{"spacing":{"blockGap":{"top":"15px","left":"15px"}}}} --> <ul class="wp-block-social-links has-icon-color is-style-logos-only"><!-- wp:social-link {"url":"#","service":"facebook"} /-->  <!-- wp:social-link {"url":"#","service":"twitter"} /-->  <!-- wp:social-link {"url":"#","service":"instagram"} /-->  <!-- wp:social-link {"url":"#","service":"linkedin"} /--></ul> <!-- /wp:social-links --></div> <!-- /wp:column -->  <!-- wp:column --> <div class="wp-block-column"><!-- wp:heading {"level":4,"style":{"typography":{"textTransform":"capitalize","fontStyle":"normal","fontWeight":"700","fontSize":"30px"}},"textColor":"cyan-bluish-gray"} --> <h4 class="has-cyan-bluish-gray-color has-text-color" style="font-size:30px;font-style:normal;font-weight:700;text-transform:capitalize">Contact Us</h4> <!-- /wp:heading -->  <!-- wp:paragraph {"style":{"typography":{"fontSize":"16px","lineHeight":"1.2"}},"textColor":"cyan-bluish-gray"} --> <p class="has-cyan-bluish-gray-color has-text-color" style="font-size:16px;line-height:1.2">123 BD Lorem, Ipsum<br><br>+123-456-7890</p> <!-- /wp:paragraph -->  <!-- wp:paragraph {"style":{"typography":{"fontSize":"16px","lineHeight":"1"}},"textColor":"cyan-bluish-gray"} --> <p class="has-cyan-bluish-gray-color has-text-color" style="font-size:16px;line-height:1">sample@gmail.com</p> <!-- /wp:paragraph -->  <!-- wp:paragraph {"style":{"typography":{"fontSize":"16px","lineHeight":"1"}},"textColor":"cyan-bluish-gray"} --> <p class="has-cyan-bluish-gray-color has-text-color" style="font-size:16px;line-height:1">Opening Hours: 10:00 - 18:00</p> <!-- /wp:paragraph --></div> <!-- /wp:column -->  <!-- wp:column --> <div class="wp-block-column"><!-- wp:heading {"level":4,"style":{"typography":{"fontSize":"30px","fontStyle":"normal","fontWeight":"700","textTransform":"capitalize"}},"textColor":"cyan-bluish-gray"} --> <h4 class="has-cyan-bluish-gray-color has-text-color" style="font-size:30px;font-style:normal;font-weight:700;text-transform:capitalize">Newsletter</h4> <!-- /wp:heading -->  <!-- wp:paragraph {"style":{"typography":{"fontSize":"16px"}},"textColor":"cyan-bluish-gray"} --> <p class="has-cyan-bluish-gray-color has-text-color" style="font-size:16px">Lorem ipsum dolor sit amet, consectetur ut labore et dolore magna aliqua ipsum dolor sit</p> <!-- /wp:paragraph -->  <!-- wp:search {"label":"","placeholder":"Enter Your Email...","buttonText":"Subscribe","buttonPosition":"button-inside","style":{"border":{"width":"1px"}},"borderColor":"tertiary","backgroundColor":"background-header","textColor":"background"} /--></div> <!-- /wp:column --></div> <!-- /wp:columns --></div> <!-- /wp:group -->

3.4: View the new pattern in the inserter

To view the newly added Footer pattern from patterns library pattern, go to any post or page and select the inserter icon (blue plus symbol, top left), and then select “TT2 Gopher – Footer” categories. The newly added pattern is shown on the left panel, together with other footer patterns and its preview on the right (if selected):

Screenshot showing new footer pattern (left panel) and its preview (right panel).

Registering patterns directly in theme.json file

In WordPress 6.0, it is possible to register any desired patterns from the pattern directory with theme.json file with the following syntax. The 6.0 dev note states, “the patterns field is an array of [pattern slugs] from the Pattern Directory. Pattern slugs can be extracted by the [URL] in single pattern view at the Pattern Directory.”

{     "version": 2,     "patterns": ["short-text", "patterns-slug"] }

This short WordPress 6.0 features video demonstrates how patterns are registered in the /patterns folder (at 3:53) and registering the desired patterns from the pattern director in a theme.json file (at 3:13).

Then, the registered pattern is available in the patterns inserter search box, which is then available for use just like theme-bundled patterns library.

{   "version": 2,   "patterns": [ "footer-from-directory", "footer-section-design-with-3-column-description-social-media-contact-and-newsletter" ] }

In this example, the pattern slug footer-section-design-with-3-column-description-social-media-contact-and-newsletter from the earlier example is registered via theme.json.

Page creation pattern model

As part of “building with patterns” initiatives, WordPress 6.0 offers a pattern modal option to theme authors to add page layout patterns into block theme, allowing site users to select page layout patterns (e.g., an about page, a contact page, a team page, etc.) while creating a page. The following is an example taken from the dev note:

register_block_pattern(     'my-plugin/about-page',     array(         'title'      => __( 'About page', 'my-plugin' ),         'blockTypes' => array( 'core/post-content' ),         'content'    => '<!-- wp:paragraph {"backgroundColor":"black","textColor":"white"} -->         <p class="has-white-color has-black-background-color has-text-color has-background">Write you about page here, feel free to use any block</p>         <!-- /wp:paragraph -->',     ) );

This feature is currently limited to Page Post Type only and not for “Posts Post Type”, yet.

The page creation pattern modal can also be disabled completely by removing the post-content block type of all the patterns. An example sample code is available here.

You can follow and participate in GitHub’s discussion from the links listed under the resource section below.

Using patterns directory to build page

Patterns from the directory can also be used to create the desired post or page layout, similar to page builders. The GutenbergHub team has created an experimental online page builder app using patterns directly from the directory (introductory video). Then the codes from the app can be copied and pasted in a site, which greatly simplifies the building complex page layout process without coding.

In this short video, Jamie Marsland demonstrates (at 1:30) how the app can be used to create an entire page layout similar to page builder using desired page sections of the directory.

Wrapping up

Patterns allow users to recreate their commonly used content layout (e.g., hero page, call out, etc.) in any page and lower the barriers to presenting content in styles, which were previously not possible without coding skills. Just like the plugins and themes directories, the new patterns directory offers users options to use a wide range of patterns of their choices from the pattern directory, and write and display content in style.

Indeed, block patterns will change everything and surely this is a game changer feature in the WordPress theme landscape. When the full potential of building with patterns effort becomes available, this is going to change the way we design block themes and create beautiful content even with low-code knowledge. For many creative designers, the patterns directory may also provide an appropriate avenue to showcase their creativity.


Resources

WordPress 6.0

Creating patterns

Patterns enhancement (GitHub)

Blog articles


How to Create Block Theme Patterns in WordPress 6.0 originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Optimizing SVG Patterns to Their Smallest Size

I recently created a brick wall pattern as part of my #PetitePatterns series, a challenge where I create organic-looking patterns or textures in SVG within 560 bytes (or approximately the size of two tweets). To fit this constraint, I have gone through a journey that has taught me some radical ways of optimizing SVG patterns so that they contain as little code as possible without affecting the overall image quality.

I want to walk you through the process and show you how we can take an SVG pattern that starts at 197 bytes all the way down to a mere 44 bytes — a whopping 77.7% reduction!

The SVG pattern

This is what’s called a “running bond” brick pattern. It’s the most common brick pattern out there, and one you’ve surely seen before: each row of bricks is offset by one half the length of a brick, creating a repeating staggered pattern. The arrangement is pretty simple, making SVG’s <pattern> element a perfect fit to reproduce it in code.

The SVG <pattern> element uses a pre-defined graphic object which can be replicated (or “tiled”) at fixed intervals along the horizontal and vertical axes. Essentially, we define a rectangular tile pattern and it gets repeated to paint the fill area.

First, let’s set the dimensions of a brick and the gap between each brick. For the sake of simplicity, let’s use clean, round numbers: a width of 100 and a height of 30 for the brick, and 10 for the horizontal and vertical gaps between them.

Showing a highlighted portion of a brick wall pattern, which is the example we are using for optimizing SVG patterns.

Next, we have to identify our “base” tile. And by “tile” I’m talking about pattern tiles rather than physical tiles, not to be confused with the bricks. Let’s use the highlighted part of the image above as our pattern tile: two whole bricks in the first row, and one whole sandwiched between two half bricks in the second row. Notice how and where the gaps are included, because those need to be included in the repeated pattern tile.

When using <pattern>, we have to define the pattern’s width and height, which correspond to the width and height of the base tile. To get the dimensions, we need a little math:

Tile Width  = 2(Brick Width) + 2(Gap) = 2(100) + 2(10) = 220 Tile Height = 2(Bright Height) + 2(Gap) = 2(30) + 2(10) = 80

Alright, so our pattern tile is 220✕80. We also have to set the patternUnits attribute, where the value userSpaceOnUse essentially means pixels. Finally, adding an id to the pattern is necessary so that it can be referenced when we are painting another element with it.

<pattern id="p" width="220" height="80" patternUnits="userSpaceOnUse">   <!-- pattern content here --> </pattern>

Now that we have established the tile dimensions, the challenge is to create the code for the tile in a way that renders the graphic with the smallest number of bytes possible. This is what we hope to end up with at the very end:

The bricks (in black) and gaps (in white) of the final running bond pattern

Initial markup (197 bytes)

The simplest and most declarative approach to recreate this pattern that comes to my mind is to draw five rectangles. By default, the fill of an SVG element is black and the stroke is transparent. This works well for optimizing SVG patterns, as we don’t have to explicitly declare those in the code.

Each line in the code below defines a rectangle. The width and height are always set, and the x and y positions are only set if a rectangle is offset from the 0 position.

<rect width="100" height="30"/> <rect x="110" width="100" height="30"/> <rect y="40" width="45" height="30"/> <rect x="55" y="40" width="100" height="30"/> <rect x="165" y="40" width="55" height="30"/>

The top row of the tile contained two full-width bricks, the second brick is positioned to x="110" allowing 10 pixels of gap before the brick. Similarly there’s 10 pixels of gap after, because the brick ends at 210 pixels (110 + 100 = 210) on the horizontal axis even though the <pattern> width is 220 pixels. We need that little bit of extra space; otherwise the second brick would merge with the first brick in the adjacent tile.

The bricks in the second (bottom) row are offset so the row contains two half bricks and one whole brick. In this case, we want the half-width bricks to merge so there’s no gap at the start or the end, allowing them to seamlessly flow with the bricks in adjoining pattern tiles. When offsetting these bricks, we also have to include half gaps, thus the x values are 55 and 165, respectively.

Element reuse, (-43B, 154B total)

It seems inefficient to define each brick so explicitly. Isn’t there some way to optimize SVG patterns by reusing the shapes instead?

I don’t think it’s widely known that SVG has a <use> element. You can reference another element with it and render that referenced element wherever <use> is used. This saves quite a few bytes because we can omit specifying the widths and heights of each brick, except for the first one.

That said, <use> does come with a little price. That is, we have to add an id for the element we want to reuse.

<rect id="b" width="100" height="30"/> <use href="#b" x="110"/> <use href="#b" x="-55" y="40"/> <use href="#b" x="55" y="40"/> <use href="#b" x="165" y="40"/>

The shortest id possible is one character, so I chose “b” for brick. The <use> element can be positioned similarly to <rect>, with the x and y attributes as offsets. Since each brick is full-width now that we’ve switched to <use> (remember, we explicitly halved the bricks in the second row of the pattern tile), we have to use a negative x value in the second row, then make sure the last brick overflows from the tile for that seamless connection between bricks. These are okay, though, because anything that falls outside of the pattern tile is automatically cut off.

Can you spot some repeating strings that can be written more efficiently? Let’s work on those next.

Rewriting to path (-54B, 100B total)

<path> is probably the most powerful element in SVG. You can draw just about any shape with “commands” in its d attribute. There are 20 commands available, but we only need the simplest ones for rectangles.

Here’s where I landed with that:

<path d="M0 0h100v30h-100z          M110 0h100v30h-100          M0 40h45v30h-45z          M55 40h100v30h-100z          M165 40h55v30h-55z"/>

I know, super weird numbers and letters! They all have meaning, of course. Here’s what’s happening in this specific case:

  • M{x} {y}: Moves to a point based on coordinates.
  • z: Closes the current segment.
  • h{x}: Draws a horizontal line from the current point, with the length of x in the direction defined by the sign of x. Lowercase x indicates a relative coordinate.
  • v{y}: Draws a vertical line from the current point, with the length of y in the direction defined by the sign of y. Lowercase y indicates a relative coordinate.

This markup is much more terse than the previous one (line breaks and indentation whitespace is only for readability). And, hey, we’ve managed to cut out half of the initial size, arriving at 100 bytes. Still, something makes me feel like this could be smaller…

Tile revision (-38B, 62B total)

Doesn’t our pattern tile have repeating parts? It’s clear that in the first row a whole brick is repeated, but what about the second row? It’s a bit harder to see, but if we cut the middle brick in half it becomes obvious.

The left half preceding the red line is the same as the right side.

Well, the middle brick isn’t exactly cut in half. There’s a slight offset because we also have to account for the gap. Anyways, we just found a simpler base tile pattern, which means fewer bytes! This also means we have to halve the width of our <pattern> element from 220 to 110.

<pattern id="p" width="110" height="80" patternUnits="userSpaceOnUse">   <!-- pattern content here --> </pattern>

Now let’s see how the simplified tile is drawn with <path>:

<path d="M0 0h100v30h-100z          M0 40h45v30h-45z          M55 40h55v30h-55z"/>

The size is reduced to 62 bytes, which is already less than a third of the original size! But why stop here when there’s even more we can do!

Shortening path commands (-9B, 53B total)

It’s worth getting a little deeper into the <path> element because it provides more hints for optimizing SVG patterns. One misconception I’ve had when working with <path> is regarding how the fill attribute works. Having played a lot with MS Paint in my childhood, I’ve learned that any shape I want to fill with a solid color has to be closed, i.e. have no open points. Otherwise, the paint will leak out of the shape and spill over everything.

In SVG, however, this is not true. Let me quote the spec itself:

The fill operation fills open subpaths by performing the fill operation as if an additional “closepath” command were added to the path to connect the last point of the subpath with the first point of the subpath.

This means we can omit the close path commands (z), because the subpaths are considered automatically closed when filled.

Another useful thing to know about path commands is that they come in uppercase and lowercase variations. Lowercase letters mean that relative coordinates are used; uppercase letters mean absolute coordinates are used instead.

It’s a little trickier than that with the H and V commands because they only include one coordinate. Here’s how I would describe these two commands:

  • H{x}: Draws a horizontal line from the current point to coordinate x.
  • V{y}: Draws a vertical line from the current point to coordinate y.

When we are drawing the first brick in the pattern tile, we start from the (0,0) coordinates. We then draw a horizontal line to (100,0) and a vertical line to (100,30), and finally, draw a horizontal line to (0,30). We used the h-100 command in the last line, but it is the equivalent of H0, which is two bytes instead of five. We can replace two similar occurrences and pare the code of our <path> down to this:

<path d="M0 0h100v30H0          M0 40h45v30H0          M55 40h55v30H55"/>

Another 9 bytes shaved off — how much smaller can we go?

Bridging (-5B, 48B total)

The longest commands standing in our way of a fully-optimized SVG pattern are the “move to” commands which take up 4, 5, and 6 bytes, respectively. One constraint we have is that:

A path data segment (if there is one) must begin with a “moveto” command.

But that’s okay. The first one is the shortest anyways. If we swap the rows, we can come up with a path definition where we only have to move either horizontally or vertically between the bricks. What if we could use the h and v commands there instead of M?

The path starts from the red dot in the top-left corner. Red are the path commands supported with arrows, black are the coordinates the arrows point to.

The above diagram shows how the three shapes can be drawn with a single path. Note that we are leveraging the fact that the fill operation automatically closes the open part between (110,0) and (0,0). With this rearrangement, we also moved the gap to the left of the full-width brick in the second row. Here’s how the code looks, still broken into one brick per line:

<path d="M0 0v30h50V0          h10v30h50          v10H10v30h100V0"/>

Surely, we’ve found the absolute smallest solution now that we’re down to 48 bytes, right?! Well…

Digit trimming (-4B, 44B total)

If you can be a bit flexible with the dimensions, there’s another little way we can optimize SVG patterns. We’ve been working with a brick width of 100 pixels, but that’s three bytes. Changing it to 90 means one less byte whenever we need to write it. Similarly, we used a gap of 10 pixels — but if we change it to 8 instead, we save a byte on each of those occurrences.

<path d="M0 0v30h45V0          h8v30h45          v8H8v30h90V0"/>

Of course, this also means we have to adjust the pattern dimensions accordingly. Here’s the final optimized SVG pattern code:

<pattern id="p" width="98" height="76" patternUnits="userSpaceOnUse">   <path d="M0 0v30h45V0h8v30h45v8H8v30h90V0"/> </pattern>

The second line in the above snippet — not counting the indentations — is 44 bytes. We got here from 197 bytes in six iterations. That’s a chunky 77.7% size reduction!

I’m wondering though… is this really the smallest size possible? Have we looked at all possible ways to optimize SVG patterns?

I invite you to try and further minify this code, or even experiment with alternative methods for optimizing SVG patterns. I would love to see if we could find the true global minimum with the wisdom of the crowd!

More on creating and optimizing SVG patterns

If you are interested to learn more about creating and optimizing SVG patterns, read my article about creating patterns with SVG filters. Or, if you want to check out a gallery of 60+ patterns, you can view the PetitePatterns CodePen Collection. Lastly, you’re welcome to watch my tutorials on YouTube to help you get even deeper into SVG patterns.


Optimizing SVG Patterns to Their Smallest Size originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Creating Generative Patterns with The CSS Paint API

The browser has long been a medium for art and design. From Lynn Fisher’s joyful A Single Div creations to Diana Smith’s staggeringly detailed CSS paintings, wildly creative, highly skilled developers have — over the years — continuously pushed web technologies to their limits and crafted innovative, inspiring visuals.

CSS, however, has never really had an API dedicated to… well, just drawing stuff! As demonstrated by the talented folks above, it certainly can render most things, but it’s not always easy, and it’s not always practical for production sites/applications.

Recently, though, CSS was gifted an exciting new set of APIs known as Houdini, and one of them — the Paint API — is specifically designed for rendering 2D graphics. For us web folk, this is incredibly exciting. For the first time, we have a section of CSS that exists for the sole purpose of programmatically creating images. The doors to a mystical new world are well and truly open!

In this tutorial, we will be using the Paint API to create three (hopefully!) beautiful, generative patterns that could be used to add a delicious spoonful of character to a range of websites/applications.

Spellbooks/text editors at the ready, friends, let’s do some magic!

Intended audience

This tutorial is perfect for folks who are comfortable writing HTML, CSS, and JavaScript. A little familiarity with generative art and some knowledge of the Paint API/HTML canvas will be handy but not essential. We will do a quick overview before we get started. Speaking of which…

Before we start

For a comprehensive introduction to both the Paint API and generative art/design, I recommend popping over to the first entry in this series. If you are new to either subject, this will be a great place to start. If you don’t feel like navigating another article, however, here are a couple of key concepts to be familiar with before moving on.

If you are already familiar with the CSS Paint API and generative art/design, feel free to skip ahead to the next section.

What is generative art/design?

Generative art/design is any work created with an element of chance. We define some rules and allow a source of randomness to guide us to an outcome. For example, a rule could be “if a random number is greater than 50, render a red square, if it is less than 50, render a blue square*,”* and, in the browser, a source of randomness could be Math.random().

By taking a generative approach to creating patterns, we can generate near-infinite variations of a single idea — this is both an inspiring addition to the creative process and a fantastic opportunity to delight our users. Instead of showing people the same imagery every time they visit a page, we can display something special and unique for them!

What is the CSS Paint API?

The Paint API gives us low-level access to CSS rendering. Through “paint worklets” (JavaScript classes with a special paint() function), it allows us to dynamically create images using a syntax almost identical to HTML canvas. Worklets can render an image wherever CSS expects one. For example:

.worklet-canvas {   background-image: paint(workletName); }

Paint API worklets are fast, responsive, and play ever so well with existing CSS-based design systems. In short, they are the coolest thing ever. The only thing they are lacking right now is widespread browser support. Here’s a table:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
65 No No 79 No

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
95 No 95 No

A little thin on the ground! That’s OK, though. As the Paint API is almost inherently decorative, we can use it as a progressive enhancement if it’s available and provide a simple, dependable fallback if not.

What we are making

In this tutorial, we will be learning how to create three unique generative patterns. These patterns are quite simple, but will act as a wonderful springboard for further experimentation. Here they are in all their glory!

The demos in this tutorial currently only work in Chrome and Edge.

“Tiny Specks”

“Bauhaus”

“Voronoi Arcs”

Before moving on, take a moment to explore the examples above. Try changing the custom properties and resizing the browser window — watch how the patterns react. Can you guess how they might work without peeking at the JavaScript?

Getting set up

To save time and eliminate the need for any custom build processes, we will be working entirely in CodePen throughout this tutorial. I have even created a “starter Pen” that we can use as a base for each pattern!

I know, it’s not much to look at… yet.

In the starter Pen, we are using the JavaScript section to write the worklet itself. Then, in the HTML section, we load the JavaScript directly using an internal <script> tag. As Paint API worklets are special workers (code that runs on a separate browser thread), their origin must1 exist in a standalone .js file.

Let’s break down the key pieces of code here.

If you have written Paint API worklets before, and are familiar with CodePen, you can skip ahead to the next section.

Defining the worklet class

First things first: Let’s check out the JavaScript tab. Here we define a worklet class with a simple paint() function:

class Worklet {   paint(ctx, geometry, props) {     const { width, height } = geometry;     ctx.fillStyle = "#000";     ctx.fillRect(0, 0, width, height);   } }

I like to think of a worklet’s paint() function as a callback. When the worklet’s target element updates (changes dimensions, modifies custom properties), it re-runs. A worklet’s paint() function automatically has a few parameters passed when it executes. In this tutorial, we are interested in the first three:

  • ctx — a 2D drawing context very similar to that of HTML canvas
  • geometry — an object containing the width/height dimensions of the worklet’s target element
  • props — an array of CSS custom properties that we can “watch” for changes and re-render when they do. These are a great way of passing values to paint worklets.

Our starter worklet renders a black square that covers the entire width/height of its target element. We will completely rewrite this paint() function for each example, but it’s nice to have something defined to check things are working.

Registering the worklet

Once a worklet class is defined, it needs to be registered before we can use it. To do so, we call registerPaint in the worklet file itself:

if (typeof registerPaint !== "undefined") {   registerPaint("workletName", Worklet); }

Followed by CSS.paintWorklet.addModule() in our “main” JavaScript/HTML:

<script id="register-worklet">   if (CSS.paintWorklet) {     CSS.paintWorklet.addModule('https://codepen.io/georgedoescode/pen/bGrMXxm.js');   } </script>

We are checking registerPaint is defined before running it here, as our pen’s JavaScript will always run once on the main browser thread — registerPaint only becomes available once the JavaScript file is loaded into a worklet using CSS.paintWorklet.addModule(...).

Applying the worklet

Once registered, we can use our worklet to generate an image for any CSS property that expects one. In this tutorial, we will focus on background-image:

.worklet-canvas {   background-image: paint(workletName); }

Package imports

You may notice a couple of package imports dangling at the top of the starter pen’s worklet file:

import random from "https://cdn.skypack.dev/random"; import seedrandom from "https://cdn.skypack.dev/seedrandom";
Can you guess what they are?

Random number generators!

All three of the patterns we are creating in this tutorial rely heavily on randomness. Paint API worklets should, however, (almost) always be deterministic. Given the same input properties and dimensions, a worklet’s paint() function should always render the same thing.

Why?

  1. The Paint API may want to use a cached version of a worklet’s paint() output for better performance. Introducing an unpredictable element to a worklet renders this impossible!
  2. A worklet’s paint() function re-runs whenever the element it applies to changes dimensions. When coupled with “pure” randomness, this can result in significant flashes of content — a potential accessibility issue for some folks.

For us, all this renders Math.random() a little useless, as it is entirely unpredictable. As an alternative, we are pulling in random (an excellent library for working with random numbers) and seedrandom (a pseudo-random number generator to use as its base algorithm).

As a quick example, here’s a “random circles” worklet using a pseudo-random number generator:

And here’s a similar worklet using Math.random(). Warning: Resizing the element results in flashing imagery.

There’s a little resize handle in the bottom-right of both of the above patterns. Try resizing both elements. Notice the difference?

Setting up each pattern

Before beginning each of the following patterns, navigate to the starter Pen and click the “Fork” button in the footer. Forking a Pen creates a copy of the original the moment you click the button. From this point, it is yours to do whatever you like.

Once you have forked the starter Pen, there is a critical extra step to complete. The URL passed to CSS.paintWorklet.addModule must be updated to point to the new fork’s JavaScript file. To find the path for your fork’s JavaScript, take a peek at the URL shown in your browser. You want to grab your fork’s URL with all query parameters removed, and append .js — something like this:

Lovely. That’s the ticket! Once you have the URL for your JavaScript, make sure you update it here:

<script id="register-worklet">   if (CSS.paintWorklet) {     // ⚠️ hey friend! update the URL below each time you fork this pen! ⚠️     CSS.paintWorklet.addModule('https://codepen.io/georgedoescode/pen/QWMVdPG.js');   } </script>

When working with this setup, you may occasionally need to manually refresh the Pen in order to see your changes. To do so, hit CMD/CTRL + Shift + 7.

Pattern #1 (Tiny Specks)

OK, we are ready to make our first pattern. Fork the starter Pen, update the .js file reference, and settle in for some generative fun!

As a quick reminder, here’s the finished pattern:

Updating the worklet’s name

Once again, first things first: Let’s update the starter worklet’s name and relevant references:

class TinySpecksPattern {   // ... } if (typeof registerPaint !== "undefined") {   registerPaint("tinySpecksPattern", TinySpecksPattern); }
.worklet-canvas {   /* ... */   background-image: paint(tinySpecksPattern); }

Defining the worklet’s input properties

Our “Tiny Specks” worklet will accept the following input properties:

  • --pattern-seed — a seed value for the pseudo-random number generator
  • --pattern-colors — the available colors for each speck
  • --pattern-speck-count — how many individual specks the worklet should render
  • --pattern-speck-min-size — the minimum size for each speck
  • --pattern-speck-max-size — the maximum size for each speck

As our next step, let’s define the inputProperties our worklet can receive. To do so, we can add a getter to our TinySpecksPattern class:

class TinySpecksPattern {   static get inputProperties() {     return [       "--pattern-seed",       "--pattern-colors",       "--pattern-speck-count",       "--pattern-speck-min-size",       "--pattern-speck-max-size"     ];   }   // ... }

Alongside some custom property definitions in our CSS:

@property --pattern-seed {   syntax: "<number>";   initial-value: 1000;   inherits: true; }  @property --pattern-colors {   syntax: "<color>#";   initial-value: #161511, #dd6d45, #f2f2f2;   inherits: true; }  @property --pattern-speck-count {   syntax: "<number>";   initial-value: 3000;   inherits: true; }  @property --pattern-speck-min-size {   syntax: "<number>";   initial-value: 0;   inherits: true; }  @property --pattern-speck-max-size {   syntax: "<number>";   initial-value: 3;   inherits: true; }

We are using the Properties and Values API here (another member of the Houdini family) to define our custom properties. Doing so affords us two valuable benefits. First, we can define sensible defaults for the input properties our worklet expects. A tasty sprinkle of developer experience! Second, by including a syntax definition for each custom property, our worklet can interpret them intelligently.

For example, we define the syntax <color># for --pattern-colors. In turn, this allows us to pass an array of comma-separated colors to the worklet in any valid CSS color format. When our worklet receives these values, they have been converted to RGB and placed in a neat little array. Without a syntax definition, worklets interpret all props as simple strings.

Like the Paint API, the Properties and Values API also has limited browser support.

The paint() function

Awesome! Here’s the fun bit. We have created our “Tiny Speck” worklet class, registered it, and defined what input properties it can expect to receive. Now, let’s make it do something!

As a first step, let’s clear out the starter Pen’s paint() function, keeping only the width and height definitions:

paint(ctx, geometry, props) {   const { width, height } = geometry; }

Next, let’s store our input properties in some variables:

const seed = props.get("--pattern-seed").value; const colors = props.getAll("--pattern-colors").map((c) => c.toString()); const count = props.get("--pattern-speck-count").value; const minSize = props.get("--pattern-speck-min-size").value; const maxSize = props.get("--pattern-speck-max-size").value;

Next, we should initialize our pseudo-random number generator:

random.use(seedrandom(seed));

Ahhh, predictable randomness! We are re-seeding seedrandom with the same seed value every time paint() runs, resulting in a consistent stream of random numbers across renders.

Finally, let’s paint our specks!

First off, we create a for-loop that iterates count times. In every iteration of this loop, we are creating one individual speck:

for (let i = 0; i < count; i++) { }

As the first action in our for-loop, we define an x and y position for the speck. Somewhere between 0 and the width/height of the worklet’s target element is perfect:

const x = random.float(0, width); const y = random.float(0, height);

Next, we choose a random size (for the radius):

const radius = random.float(minSize, maxSize);

So, we have a position and a size defined for the speck. Let’s choose a random color from our colors to fill it with:

ctx.fillStyle = colors[random.int(0, colors.length - 1)];

Alright. We are all set. Let’s use ctx to render something!

The first thing we need to do at this point is save() the state of our drawing context. Why? We want to rotate each speck, but when working with a 2D drawing context like this, we cannot rotate individual items. To rotate an object, we have to spin the entire drawing space. If we don’t save() and restore() the context, the rotation/translation in every iteration will stack, leaving us with a very messy (or empty) canvas!

ctx.save();

Now that we have saved the drawing context’s state, we can translate to the speck’s center point (defined by our x/y variables) and apply a rotation. Translating to the center point of an object before rotating ensures the object rotates around its center axis:

ctx.translate(x, y); ctx.rotate(((random.float(0, 360) * 180) / Math.PI) * 2); ctx.translate(-x, -y);

After applying our rotation, we translate back to the top-left corner of the drawing space.

We choose a random value between 0 and 360 (degrees) here, then convert it into radians (the rotation format ctx understands).

Awesome! Finally, let’s render an ellipse — this is the shape that defines our specks:

ctx.beginPath(); ctx.ellipse(x, y, radius, radius / 2, 0, Math.PI * 2, 0); ctx.fill();

Here’s a simple pen showing the form of our random specks, a little closer up:

Perfect. Now, all we need to do is restore the drawing context:

ctx.restore();

That’s it! Our first pattern is complete. Let’s also apply a background-color to our worklet canvas to finish off the effect:

.worklet-canvas {   background-color: #90c3a5;   background-image: paint(tinySpecksPattern); }

Next steps

From here, try changing the colors, shapes, and distribution of the specks. There are hundreds of directions you could take this pattern! Here’s an example using little triangles rather than ellipses:

Onwards!

Pattern #2 (Bauhaus)

Nice work! That’s one pattern down. Onto the next one. Once again, fork the starter Pen and update the worklet’s JavaScript reference to get started.

As a quick refresher, here’s the finished pattern we are working toward:

Updating the worklet’s name

Just like we did last time, let’s kick things off by updating the worklet’s name and relevant references:

class BauhausPattern {   // ... }  if (typeof registerPaint !== "undefined") {   registerPaint("bauhausPattern", BauhausPattern); }
.worklet-canvas {   /* ... */   background-image: paint(bauhausPattern); }

Lovely.

Defining the worklet’s input properties

Our “Bauhaus Pattern” worklet expects the following input properties:

  • --pattern-seed — a seed value for the pseudo-random number generator
  • --pattern-colors — the available colors for each shape in the pattern
  • --pattern-size — the value used to define both the width and height of a square pattern area
  • --pattern-detail — the number of columns/rows to divide the square pattern into

Let’s add these input properties to our worklet:

class BahausPattern {   static get inputProperties() {     return [       "--pattern-seed",       "--pattern-colors",       "--pattern-size",       "--pattern-detail"     ];   }   // ... }

…and define them in our CSS, again, using the Properties and Values API:

@property --pattern-seed {   syntax: "<number>";   initial-value: 1000;   inherits: true; }  @property --pattern-colors {   syntax: "<color>#";   initial-value: #2d58b5, #f43914, #f9c50e, #ffecdc;   inherits: true; }  @property --pattern-size {   syntax: "<number>";   initial-value: 1024;   inherits: true; }  @property --pattern-detail {   syntax: "<number>";   initial-value: 12;   inherits: true; }

Excellent. Let’s paint!

The paint() function

Again, let’s clear out the starter worklet’s paint function, leaving only the width and height definition:

paint(ctx, geometry, props) {   const { width, height } = geometry; }

Next, let’s store our input properties in some variables:

const patternSize = props.get("--pattern-size").value; const patternDetail = props.get("--pattern-detail").value; const seed = props.get("--pattern-seed").value; const colors = props.getAll("--pattern-colors").map((c) => c.toString());

Now, we can seed our pseudo-random number generator just like before:

random.use(seedrandom(seed));

Awesome! As you might have noticed, the setup for Paint API worklets is always somewhat similar. It’s not the most exciting process, but it’s an excellent opportunity to reflect on the architecture of your worklet and how other developers may use it.

So, with this worklet, we create a fixed-dimension square pattern filled with shapes. This fixed-dimension pattern is then scaled up or down to cover the worklet’s target element. Think of this behavior a bit like background-size: cover in CSS!

Here’s a diagram:

To achieve this behavior in our code, let’s add a scaleContext function to our worklet class:

scaleCtx(ctx, width, height, elementWidth, elementHeight) {   const ratio = Math.max(elementWidth / width, elementHeight / height);   const centerShiftX = (elementWidth - width * ratio) / 2;   const centerShiftY = (elementHeight - height * ratio) / 2;   ctx.setTransform(ratio, 0, 0, ratio, centerShiftX, centerShiftY); }

And call it in our paint() function:

this.scaleCtx(ctx, patternSize, patternSize, width, height);

Now, we can work to a set of fixed dimensions and have our worklet’s drawing context automatically scale everything for us — a handy function for lots of use cases.

Next up, we are going to create a 2D grid of cells. To do so, we define a cellSize variable (the size of the pattern area divided by the number of columns/rows we would like):

const cellSize = patternSize / patternDetail;

Then, we can use the cellSize variable to “step-through” the grid, creating equally-spaced, equally-sized cells to add random shapes to:

for (let x = 0; x < patternSize; x += cellSize) {   for (let y = 0; y < patternSize; y += cellSize) {   } }

Within the second nested loop, we can begin to render stuff!

First off, let’s choose a random color for the current shape:

const color = colors[random.int(0, colors.length - 1)];  ctx.fillStyle = color;

Next, let’s store a reference to the current cell’s center x and y position:

const cx = x + cellSize / 2; const cy = y + cellSize / 2;

In this worklet, we are positioning all of our shapes relative to their center point. While we are here, let’s add some utility functions to our worklet file to help us quickly render center-aligned shape objects. These can live outside of the Worklet class:

function circle(ctx, cx, cy, radius) {   ctx.beginPath();   ctx.arc(cx, cy, radius, 0, Math.PI * 2);   ctx.closePath(); }  function arc(ctx, cx, cy, radius) {   ctx.beginPath();   ctx.arc(cx, cy, radius, 0, Math.PI * 1);   ctx.closePath(); }  function rectangle(ctx, cx, cy, size) {   ctx.beginPath();   ctx.rect(cx - size / 2, cy - size / 2, size, size);   ctx.closePath(); }  function triangle(ctx, cx, cy, size) {   const originX = cx - size / 2;   const originY = cy - size / 2;   ctx.beginPath();   ctx.moveTo(originX, originY);   ctx.lineTo(originX + size, originY + size);   ctx.lineTo(originX, originY + size);   ctx.closePath(); }

I won’t go into too much detail here, but here’s a diagram visualizing how each of these functions work:

If you get stuck on the graphics rendering part of any of the worklets in this tutorial, look at the MDN docs on HTML canvas. The syntax/usage is almost identical to the 2D graphics context available in Paint API worklets.

Cool! Let’s head back over to our paint() function’s nested loop. The next thing we need to do is choose what shape to render. To do so, we can pick a random string from an array of possibilities:

const shapeChoice = ["circle", "arc", "rectangle", "triangle"][   random.int(0, 3) ];

We can also pick a random rotation amount in a very similar way:

const rotationDegrees = [0, 90, 180][random.int(0, 2)];

Perfect. We are ready to render!

To start, let’s save our drawing context’s state, just like in the previous worklet:

ctx.save();

Next, we can translate to the center point of the current cell and rotate the canvas using the random value we just chose:

ctx.translate(cx, cy); ctx.rotate((rotationDegrees * Math.PI) / 180); ctx.translate(-cx, -cy);

Now we can render the shape itself! Let’s pass our shapeChoice variable to a switch statement and use it to decide which shape rendering function to run:

switch (shapeChoice) {   case "circle":     circle(ctx, cx, cy, cellSize / 2);     break;   case "arc":     arc(ctx, cx, cy, cellSize / 2);     break;   case "rectangle":     rectangle(ctx, cx, cy, cellSize);     break;   case "triangle":     triangle(ctx, cx, cy, cellSize);     break; }  ctx.fill();

Finally, all we need to do is restore() our drawing context ready for the next shape:

ctx.restore();

With that, our Bauhaus Grids worklet is complete!

Next steps

There are so many directions you could take this worklet. How could you parameterize it further? Could you add a “bias” for specific shapes/colors? Could you add more shape types?

Always experiment — following along with the examples we are creating together is an excellent start, but the best way to learn is to make your own stuff! If you are stuck for inspiration, take a peek at some patterns on Dribbble, look to your favorite artists, the architecture around you, nature, you name it!

As a simple example, here’s the same worklet, in an entirely different color scheme:

Pattern #3 (Voronoi Arcs)

So far, we have created both a chaotic pattern and one that aligns strictly to a grid. For our last example, let’s build one that sits somewhere between the two.

As one last reminder, here’s the finished pattern:

Before we jump in and write any code, let’s take a look at how this worklet… works.

A brief introduction to Voronoi tessellations

As suggested by the name, this worklet uses something called a Voronoi tessellation to calculate its layout. A Voronoi tessellation (or diagram) is, in short, a way to partition a space into non-overlapping polygons.

We add a collection of points to a 2D space. Then for each point, calculate a polygon that contains only it and no other points. Once calculated, the polygons can be used as a kind of “grid” to position anything.

Here’s an animated example:

The fascinating thing about Voronoi-based layouts is that they are responsive in a rather unusual way. As the points in a Voronoi tessellation move around, the polygons automatically re-arrange themselves to fill the space!

Try resizing the element below and watch what happens!

Cool, right?

If you would like to learn more about all things Voronoi, I have an article that goes in-depth. For now, though, this is all we need.

Updating the worklet’s name

Alright, folks, we know the deal here. Let’s fork the starter Pen, update the JavaScript import, and change the worklet’s name and references:

class VoronoiPattern {   // ... }  if (typeof registerPaint !== "undefined") {   registerPaint("voronoiPattern", VoronoiPattern); }
.worklet-canvas {   /* ... */   background-image: paint(voronoiPattern); }

Defining the worklet’s input properties

Our VoronoiPattern worklet expects the following input properties:

  • --pattern-seed — a seed value for the pseudo-random number generator
  • --pattern-colors — the available colors for each arc/circle in the pattern
  • --pattern-background — the pattern’s background color

Let’s add these input properties to our worklet:

class VoronoiPattern {   static get inputProperties() {     return ["--pattern-seed", "--pattern-colors", "--pattern-background"];   }   // ... }

…and register them in our CSS:

@property --pattern-seed {   syntax: "<number>";   initial-value: 123456;   inherits: true; }  @property --pattern-background {   syntax: "<color>";   inherits: false;   initial-value: #141b3d; }  @property --pattern-colors {   syntax: "<color>#";   initial-value: #e9edeb, #66aac6, #e63890;   inherits: true; }

Nice! We are all set. Overalls on, friends — let us paint.

The paint() function

First, let’s clear out the starter worklet’s paint() function, retaining only the width and height definitions. We can then create some variables using our input properties, and seed our pseudo-random number generator, too. Just like in our previous examples:

paint(ctx, geometry, props) {   const { width, height } = geometry;    const seed = props.get("--pattern-seed").value;   const background = props.get("--pattern-background").toString();   const colors = props.getAll("--pattern-colors").map((c) => c.toString());    random.use(seedrandom(seed)); }

Before we do anything else, let’s paint a quick background color:

ctx.fillStyle = background; ctx.fillRect(0, 0, width, height);

Next, let’s import a helper function that will allow us to quickly cook up a Voronoi tessellation:

import { createVoronoiTessellation } from "https://cdn.skypack.dev/@georgedoescode/generative-utils";

This function is essentially a wrapper around d3-delaunay and is part of my generative-utils repository. You can view the source code on GitHub. With “classic” data structures/algorithms such as Voronoi tessellations, there is no need to reinvent the wheel — unless you want to, of course!

Now that we have our createVoronoiTessellation function available, let’s add it to paint():

const { cells } = createVoronoiTessellation({   width,   height,   points: [...Array(24)].map(() => ({     x: random.float(0, width),     y: random.float(0, height)   })) });

Here, we create a Voronoi Tessellation at the width and height of the worklet’s target element, with 24 controlling points.

Awesome. Time to render our shapes! Lots of this code should be familiar to us, thanks to the previous two examples.

First, we loop through each cell in the tessellation:

cells.forEach((cell) => { });

For each cell, the first thing we do is choose a color:

ctx.fillStyle = colors[random.int(0, colors.length - 1)];

Next, we store a reference to the center x and y values of the cell:

const cx = cell.centroid.x; const cy = cell.centroid.y;

Next, we save the context’s current state and rotate the canvas around the cell’s center point:

ctx.save();  ctx.translate(cx, cy); ctx.rotate((random.float(0, 360) / 180) * Math.PI); ctx.translate(-cx, -cy);

Cool! Now, we can render something. Let’s draw an arc with an end angle of either PI or PI * 2. To me and you, a semi-circle or a circle:

ctx.beginPath(); ctx.arc(   cell.centroid.x,   cell.centroid.y,   cell.innerCircleRadius * 0.75,   0,   Math.PI * random.int(1, 2) ); ctx.fill();

Our createVoronoiTessellation function attaches a special innerCircleRadius to each cell — this is the largest possible circle that can fit at its center without touching any edges. Think of it as a handy guide for scaling objects to the bounds of a cell. In the snippet above, we are using innerCircleRadius to determine the size of our arcs.

Here’s a simple pen highlighting what’s happening here:

Now that we have added a “primary” arc to each cell, let’s add another one, 25% of the time. This time, however, we can set the arc’s fill color to our worklets background color. Doing so gives us the effect of a little hole in the middle of some of the shapes!

if (random.float(0, 1) > 0.25) {   ctx.fillStyle = background;   ctx.beginPath();   ctx.arc(     cell.centroid.x,     cell.centroid.y,     (cell.innerCircleRadius * 0.75) / 2,     0,     Math.PI * 2   );   ctx.fill(); }

Great! All we need to do now is restore the drawing context:

ctx.restore();

And, that’s it!

Next steps

The beautiful thing about Voronoi tessellations is that you can use them to position anything at all. In our example, we used arcs, but you could render rectangles, lines, triangles, whatever! Perhaps you could even render the outlines of the cells themselves?

Here’s a version of our VoronoiPattern worklet that renders lots of small lines, rather than circles and semicircles:

Randomizing patterns

You may have noticed that up until this point, all of our patterns have received a static --pattern-seed value. This is fine, but what if we would like our patterns to be random each time they display? Well, lucky for us, all we need to do is set the --pattern-seed variable when the page loads to be a random number. Something like this:

document.documentElement.style.setProperty('--pattern-seed', Math.random() * 10000);

We touched on this briefly earlier, but this is a lovely way to make sure a webpage is a tiny bit different for everyone that sees it.

Until next time

Well, friends, what a trip!

We have created three beautiful patterns together, learned lots of handy Paint API tricks, and (hopefully!) had some fun, too. From here, I hope you feel inspired to make some more generative art/design with CSS Houdini! I’m not sure about you, but I feel like my portfolio site needs a new coat of paint…

Until next time, fellow CSS magicians!

Oh! Before you go, I have a challenge for you. There is a generative Paint API worklet running on this very page! Can you spot it?

  1. There are certainly ways around this rule, but they can be complex and not entirely suitable for this tutorial.

The post Creating Generative Patterns with The CSS Paint API appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Hexagons and Beyond: Flexible, Responsive Grid Patterns, Sans Media Queries

A little while back, Chris shared this nice hexagonal grid. And true to its name, it’s using —wait for it — CSS Grid to form that layout. It’s a neat trick! Combining grid columns, grid gaps, and creative clipping churns out the final result.

A similar thing could be accomplished with flexbox, too. But I’m here to resurrect our old friend float to create the same sort of complex and responsive layout — but with less complexity and without a single media query.

I know, it’s hard to believe. So let’s start with a working demo:

This is a fully responsive hexagon grid made without media queries, JavaScript, or a ton of hacky CSS. Resize the demo screen and see the magic. In addition to being responsive, the grid also scales. For example, we can chuck more hexagons in there by adding more divs, and control both the sizing and spacing using CSS variables.

Cool, right? And this is only one example among many grids we will build in the same manner.

Making a grid of hexagons

First, we create our hexagon shape. This task is fairly easy using clip-path. We will consider a variable S that will define the dimension of our element. Bennett Feely’s Clippy is a great online generator for clip paths.

Creating a hexagonal shape using clip-path

Each hexagon is an inline-block element. The markup can go something like this:

<div class="main">   <div class="container">     <div></div>     <div></div>     <div></div>     <!--etc. -->   </div> </div>

…and the CSS:

.main {   display: flex; /* we will talk about this later ... */   --s: 100px;  /* size  */   --m: 4px;   /* margin */ }  .container {   font-size: 0; /* disable white space between inline block element */ }  .container div {   width: var(--s);   margin: var(--m);   height: calc(var(--s) * 1.1547);   display: inline-block;   font-size: initial; /* we reset the font-size if we want to add some content */   clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%); }

Nothing complex so far. We have a main element that holds a container which, in turn, holds the hexagons. Since we are dealing with inline-block, we need to fight the common white space issue (using the font-size trick) and we consider some margin (defined with the variable M) to control the space.

Toggling the font-size of the first demo to illustrate the white space issue

Here’s the result so far:

Every other row needs some negative offset so the rows overlap rather than stack directly on top of each other. That offset will be equal to 25% of the element height (see Figure 1). We apply that offset to margin-bottom to get the following:

.container div {   width: var(--s);   margin: var(--m);   height: calc(var(--s) * 1.1547);   display: inline-block;   font-size: initial;   clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);   margin-bottom: calc(var(--m) - var(--s) * 0.2886); /* some negative margin to create overlap */ }

…and the result becomes:

Now the real trick is how we can shift the second row to get a perfect hexagon grid. We’ve already scrunched things to the point where the rows overlap each other vertically, but what we need is to push every other row toward the right so the hexagons stagger rather than overlap. Here’s where float and shape-outside come into play.

Did you wonder why we have a .main element wrapping our container and having display: flex ? That div is also a part of the trick. In a previous article, I used float and I needed that flexbox container in order to be able to use height: 100%. I will be doing the same thing here.

.container::before {   content: "";   width: calc(var(--s)/2 + var(--m));   float: left;   height: 100%; }

I am using the container::before pseudo-element to create a float element that take up all the height at the left of the grid, and that has a width equal to half a hexagon (plus its margin). We get the following result:

The yellow area is our.container::before pseudo-element.

Now, we can reach for shape-outside. Let’s take a quick refresher on what it does. Robin defines it nicely in the CSS-Tricks Almanac. MDN describes it nicely as well:

The shape-outside CSS property defines a shape—which may be non-rectangular—around which adjacent inline content should wrap. By default, inline content wraps around its margin box; shape-outside provides a way to customize this wrapping, making it possible to wrap text around complex objects rather than simple boxes.

Emphasis mine

Notice “inline content” in the definition. This explains exactly why the hexagons need to be inline-block elements. But to understand what kind of shape we need, let’s zoom into the pattern.

What’s cool about shape-inside is that it actually works with gradients. But what kind of gradient fits our situation?

If, for example, we have 10 rows of hexagons, we only need to shift means every even row. Seen differently, we need to shift every second row so we need a kind of repetition — perfect for a repeating gradient!

We’ll create a gradient with two colors:

  • A transparent one to create the “free space” while allowing the first row to stay in place (illustrated by the blue arrow above).
  • An opaque color to shift the second row to the right so the hexagons aren’t directly stacked on top of one another (illustrated by the green arrow).

Our shape-outside value will look like this:

shape-outside: repeating-linear-gradient(#0000 0 A, #000 0 B); /* #0000 = transparent */

Now, let’s find the value of A and B. B will simply be equal to the height of two rows since our logic need to repeat each two rows.

The height of two rows is equal to the height of two hexagons (including their margins), minus twice the overlap (2*Height + 4*M - 2*Height*25% = 1.5*Height + 4*M ). Or, expressed in CSS with calc():

calc(1.732 * var(--s) + 4 * var(--m))

That’s a lot! So, let’s hold all of this in a CSS custom property, F.

The value of A (defined by the blue arrow in the previous figure) needs to be at least equal to the size of one hexagon, but it can also be bigger. In order to push the second row over to the right, we need few pixel of opaque color so A can simply be equal to B - Xpx, where X is a small value.

We end up with something like this:

shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f));

And the following result:

shape-outside is applied to the floated element, creating a floated area with a predating linear gradient.

See that? Our repeating linear gradient’s shape is pushing every other row to the right by one half the width of a hexagon to offset the pattern.

Let’s put that all together:

.main {   display:flex;   --s: 100px;  /* size  */   --m: 4px;    /* margin */   --f: calc(var(--s) * 1.732 + 4 * var(--m) - 1px);  }  .container {   font-size: 0; /* disable white space between inline block element */ }  .container div {   width: var(--s);   margin: var(--m);   height: calc(var(--s) * 1.1547);   display: inline-block;   font-size:initial;   clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);   margin-bottom: calc(var(--m) - var(--s) * 0.2885); }  .container::before {   content: "";   width: calc(var(--s) / 2 + var(--m));   float: left;   height: 120%;    shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px), #000 0 var(--f)); }

That’s it! With no more than 15 CSS declarations, we have a responsive grid that fit nicely into all the screen sizes and we can easily adjust things by simply controling two variables.

You may have noticed that I am adding -1px to the variable F. Since we are dealing with calculation that involve decimals, the rounding may give us bad results. To avoid this we add or remove few pixels. I am also using 120% instead of 100% for the height of the floated element for similar reasons. There is no particular logic with theses values; we simply adjust them to make sure to cover most of the cases without any misaligning our shapes.

Want more shapes?

We can do more than hexagons with this approach! Let’s create a “rhombus” grid instead. Again, we start with our clip-path to create the shape:

Rhombus shape using clip-path

The code is basically the same. What’s changing are the calculations and values. Find below a table that will illustrate the changes.

Hexagon grid Rhombus grid
height calc(var(--s)*1.1547) var(--s)
clip-path polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%) polygon(50% 0, 100% 50%, 50% 100%, 0 50%)
margin-bottom calc(var(--m) - var(--s)*0.2885) calc(var(--m) - var(--s)*0.5)
--f calc(var(--s)*1.7324 + 4*var(--m)) calc(var(--s) + 4*var(--m))

And we’re done! A mere four changes to our code gets us a completely new grid but with a different shape.

Just how flexible is this?

We saw how we were able to make the hexagon and rhombus grids using the exact same code structure, but different variables. Let me blow your mind with another idea: What about making that calculation a variable so that we can easily switch between different grids without changing the code? We can certainly do that!

We’ll use an octagonal shape because it’s more of a generic shape from that we can use to create other shapes (a hexagon, a rhombus, a rectangle, etc.) simply by changing a few values.

The points on this octagon shape are defined in the clip-path property.

Our octagon is defined with four variables:

  • S: the width.
  • R: the ratio that will help us defines the height based on the width.
  • hc and vc : both of these will control our clip-path values and the shape we want to get. hc will be based on the width while vc on the height

I know it looks hefty, but the clip-path is defined using eight points (like shown in the figure). Adding some CSS variables, we get this:

clip-path: polygon(    var(--hc) 0, calc(100% - var(--hc)) 0, /* 2 points at the top */    100% var(--vc),100% calc(100% - var(--vc)), /* 2 points at the right */    calc(100% - var(--hc)) 100%, var(--hc) 100%, /* 2 points at the bottom */    0 calc(100% - var(--vc)),0 var(--vc) /* 2 points at the left */ );

This is what we’re aiming for:

Let’s zoom in to identify the different values:

The overlap between each row (illustrated by the red arrow) can be expressed using the vc variable which gives us a margin-bottom equal to M - vc (where M is our margin).

In addition to the margin we applied between our element, we also need an additional horizontal margin (illustrated by the yellow arrow) equal to S - 2*hc. Let’s define another variable for the horizontal margin (MH) that is equal to M + (S - 2*hc)/2.

The height of two rows is equal to twice the size of a shape (plus the margin), minus twice the overlap, or 2*(S + 2*M) - 2*vc.

Let’s update our table of values to see how we’re calculating things between the different grids:

Hexagon grid Rhombus grid Octagon grid
height calc(var(--s)*1.1547) var(--s) calc(var(--s)*var(--r)))
clip-path polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%) polygon(50% 0, 100% 50%, 50% 100%, 0 50%) polygon(var(--hc) 0, calc(100% - var(--hc)) 0,100% var(--vc),100% calc(100% - var(--vc)), calc(100% - var(--hc)) 100%,var(--hc) 100%,0 calc(100% - var(--vc)),0 var(--vc))
--mh calc(var(--m) + (var(--s) - 2*var(--hc))/2)
margin var(--m) var(--m) var(--m) var(--mh)
margin-bottom calc(var(--m) - var(--s)*0.2885) calc(var(--m) - var(--s)*0.5) calc(var(--m) - var(--vc))
--f calc(var(--s)*1.7324 + 4*var(--m)) calc(var(--s) + 4*var(--m)) calc(2*var(--s) + 4*var(--m) - 2*var(--vc))

Alright, let’s update our CSS with those adjustments:

.main {   display: flex;   --s: 100px;  /* size  */   --r: 1; /* ratio */    /* clip-path parameter */   --hc: 20px;    --vc: 30px;    --m: 4px; /* vertical margin */   --mh: calc(var(--m) + (var(--s) - 2*var(--hc))/2); /* horizontal margin */   --f: calc(2*var(--s) + 4*var(--m) - 2*var(--vc) - 2px); }  .container {   font-size: 0; /* disable white space between inline block element */ }  .container div {   width: var(--s);   margin: var(--m) var(--mh);   height: calc(var(--s)*var(--r));   display: inline-block;   font-size: initial;   clip-path: polygon( ... );   margin-bottom: calc(var(--m) - var(--vc)); }  .container::before {   content: "";   width: calc(var(--s)/2 + var(--mh));   float: left;   height: 120%;    shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f)); }

As we can see, the code structure is the same. We simply added more variable to control the shape and extend the margin property.

And below a working example. Adjust the different variables to control the shape while having a fully responsive grid:

An interactive demo, you say? You bet!

To make things easier, I am expressing the vc and hc as percetange of the width and height so we can easily scale our elements without breaking the clip-path

From the above we can easily get the initial hexagonal grid:

The rhombus grid:

And yet another hexagon grid:

A masonry-like grid:

And a checkerboard while we are at it:

A lot of possibilities to create a responsive grid with any kind of shape! All we have to do is adjust few variables.

Fixing the alignment

Let’s try to control the alignment of our shapes. Since we are dealing with inline-block elements, we’re dealing with default left alignment and some empty space at the end, depending on viewport width.

Notice that we alternate between two kind of grids based on the screen width:

Grid #1: A different number of items per row (NN-1,NN-1, etc.)
Grid #2: The same number of items per row (NNNN, etc.)

It would be good to always have one of the grid all the time (either #1 or #2) and center everything so that the free space is equally divided on both sides.

In order to get the first grid in the figure above, the container width needs to be a multiplier of the size of one shape, plus its margin, or N*(S + 2*MH), where N is an integer value.

This may sound impossible with CSS, but it’s indeed possible. I made it using CSS grid:

.main {   display: grid;   grid-template-columns: repeat(auto-fit, calc(var(--s) + 2*var(--mh)));   justify-content: center; }  .container {   grid-column: 1/-1; }

.main is now a grid container. Using grid-template-columns, I define the column width (as previously explained) and use the auto-fit value to get as many columns as possible into the available space. Then, the .container spans all of the grid columns using 1/-1 — which means that the width of our container will be a mutiplier of one column size.

All it takes to center things is justify-content: center.

Yes, CSS is magic!

Resize the demo and notice that not only do we have the first grid from the figure, but everything is perfectly centered as well.

But wait, we removed display: flex and swapped in display: grid… so how is the percentage-based height of the float still working? I had said that using a flex container was the key for that, no?

Well, turns out CSS grid sports that feature too. From the specification:

Once the size of each grid area is thus established, the grid items are laid out into their respective containing blocks. The grid area’s width and height are considered definite for this purpose.

Note: Since formulas calculated using only definite sizes, such as the stretch fit formula, are also definite, the size of a grid item which is stretched is also considered definite.

A grid item has a stretch alignment by default, so its height is definite, meaning using a percentage as a height inside it is perfectly valid.

Let’s say we instead want the second grid in the figure — we simply add an extra column with a width equal to half the width of the other columns:

.main {   display: grid;   grid-template-columns: repeat(auto-fit,calc(var(--s) + 2*var(--mh))) calc(var(--s)/2 + var(--mh));   justify-content :center; }

Now, in addition to a fully responsive grid that is flexible enough to take custom shapes, everything is perfectly centred!

Fighting the overflow

The use of negative margin-bottom on the last items and the float element pushing our items will create some unwanted overflow that may affect the content placed after our grid.

If you resize the demo, you will notice an overflow equal to the negative offset and sometimes it’s bigger. The fix is to add some padding-bottom to our container. I will make the padding equal to the height of one shape:

I have to admit that there isn’t a perfect solution to fight that overflow and to control the space below our grid. That space depends on a lot of factors and we may have to use a different padding value for each case. The safest solution is to consider a big value that covers most of the cases.

Wait, one more: a pyramidal grid

Let’s take everything we’ve learned and build another amazing grid. This time, we’ll transform the grid we just made into a pyramidal one.

Consider that, unlike the grid we’ve made so far, the number of elements is important especially for the responsive part. It’s required to know the number of elements and more precesily the number of rows.

Different pyramidal grid based on the number of items

It doesn’t mean we need a bunch of hardcoded values; rather we use an extra variable to adjust things based on the number of rows.

The logic is based on the number of rows because different numbers of elements may give us the same number of rows. For example, there are five rows when we have between 11 and 15 elements, even if the last row is not fully occupied. Having between 16 and 21 elements gives us six rows, and so on. The number of rows is our new variable.

Before digging into the geometry and the math here is a working demo:

Notice that most of the code is the same as what we’ve done in the previous examples. So let’s focus on the new properties that we’ve added:

.main {   --nr: 5;  /* number of rows */ }  .container {   max-width: calc(var(--nr)*(var(--s) + 2*var(--mh)));   margin: 0 auto; }  .container::before , .container i {   content: "";   width: calc(50% - var(--mh) - var(--s)/2);   float: left;   height: calc(var(--f)*(var(--nr) - 1)/2);   shape-outside: linear-gradient(to bottom right, #000 50%, #0000 0); }  .container i {   float:right;   shape-outside: linear-gradient(to bottom left, #000 50%, #0000 0); }

NR is our variable for the number of rows. The width of the container needs to be equal to the last row of the pyramid to make sure it hold all the elements. If you check the previous figure, you’ll see that the number of the items contained in the last row is simply equal to the number of rows, which means the formula is: NR* (S + 2*MH).

You may have also noticed that we also added an <i> element in there. We did that because we need two floating elements where we will apply shape-outside.

To understand why we need two floating elements let’s see what is done behind the scenes:

A pyramid grid of octagon shapes. The octagons alternate between green and red. There are 5 rows of octagons.
Pyramidal grid

The blue elements are our floating elements. Each one is having a width equal to half the container size, minus half a shape size, plus margin. The height is equal to four rows in our case, and to NR - 1 in a more generic case. Earlier, we defined the height of two rows, F, so the height of one row is F/2. That’s how we landed at height: calc(var(--f)*(var(--nr) - 1)/2.

Now that we have the size of our elements, we need to apply a gradient to our shape-outside.

The purple coloration in the figure above is the restricted area for our elements (it need to be an opaque color). The remaining area is the free space where the elements can flow (it need to be a transparent color). This can be done using a diagonal gradient:

shape-outside: linear-gradient(to bottom right, #000 50%, #0000 0); 

We simply change right with left for the other floated element. You have probably noticed that this is not responsive. In fact, go ahead and adjust the viewport width of the demo and see just how unresponsive this is.

We have a couple of options to get responsive:

  1. We can fall back to the first grid when the container width is smaller than the viewport width. It’s a bit tricky to code, but it allows us to preserve the same size for our elements.
  2. We can reduce the size of our elements in order to keep the pyramidal grid. This is easier to code using the percentage-based value trick, but that could result in super tiny elements on smaller screen sizes.

Let’s go with the first solution. We like a good challenge, right?

To get the pyramidal grid, we needed two floated element. The initial grid needed just one floated element. Luckily, our structure allows us to have three floated elements without needing to add more elements to the markup, thanks to pseudo-elements. We will use container::before, i::before, i::after:

/* Same as before... */  /* The initial grid */ .container::before {   content: "";   width: calc(var(--s)/2 + var(--mh));   float: left;   height: 120%;    shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f)); }  /* The pyramidal grid */ .container i::before , .container i::after {   content: "";   width: calc(50% - var(--mh) - var(--s)/2);   float: left;   height: calc(var(--f)*(var(--nr) - 1)/2);   shape-outside: linear-gradient(to bottom right,#000 50%,#0000 0); }  .container i::after {   float:right;   shape-outside: linear-gradient(to bottom left,#000 50%,#0000 0); }

Now we need a trick that lets us use either the first floated element or the other two, but not all of them at the same time. This condition should be based on the width of our container:

  • If the container width is bigger than the width of the last row, we can have our pyramid and use the floated elements inside of <i>.
  • If the container width is smaller than the width of the last row, we switch to the other grid and use the first floated element.

We can use clamp() for this! It’s sort of like a conditional function that sets a minimum and maximum range and, within that range, we provide it an “ideal” value to use between those points. This way, we can “switch” between grids using our formulas as clamped values, and still avoid using media queries.

Our code will look like this:

.main {   /* the other variables won't change*/   --lw: calc(var(--nr)*(var(--s) + 2*var(--mh))); /* width of last row */ }  .container {   max-width: var(--lw); }  /* The initial grid */ .container::before {   width: clamp(0px, (var(--lw) - 100%)*1000, calc(var(--s)/2 + var(--mh))); }  /* The pyramidal grid */ .container i::before, .container i::after {   width: clamp(0px, (100% - var(--lw) + 1px)*1000, calc(50% - var(--mh) - var(--s)/2)); }

On larger screens, the width of the container (LW) is now equal to its max-width, so 100% == LW. That means that the width of .container::before is equal to 0px (and results in this floated element becoming disabled).

For the other floating elements, we clamp the width:

width: clamp(0px, (100% - var(--lw) + 1px)*1000, calc(50% - var(--mh) - var(--s)/2));

…where the middle value ((100% - LW + 1px)*1000) is equal to (0 + 1px)*1000 = 1000px (an intentionally large, but arbitrary value). It gets clamped to calc(50% - var(--mh) - var(--s)/2). In other words, these floated elements are enabled with the correct width (the one we defined previously)

Voilà! we have a pyramidal shape on large screen.

Now, when the container width get smaller, LW is going to be greater than 100%. So, (LW - 100%) will be positive. Multiplied by a big value, it’s clamped to calc(var(--s)/2 + var(--mh)), which enables the first floated element. For the other float elements, (100% - LW + 1px) resolves to a negative value and is clamped to 0px, which disables the float elements.

Resize the below demo and see how we switch between both grids

Let’s try adding more elements:

See that? Things are scaling perfectly. Let’s toss more elements at it just for kicks:

Still great. Notice that the last row isn’t even full. Just shows that this approach covers a bunch of cases. We can also combine this with the CSS grid alignment trick we used earlier:

Do you think “float” is such a bad thing now?

Want invert the pyramid?

Like illustrated with the above figure, two changes to the previous code can invert our pyramid:

  • I change the direction of the gradient from to bottom left|right to to top left|right,
  • I add a margin-top equal to the height of one row.

And, hey, we can swap between both pyramid easily:

Isn’t this beautiful? We have a responsive pyramidal grid with custom shapes that we can easily invert and that fallback to another responsive grid on small screen while everything is perfectly centred. All this without a single media query or JavaScript, but instead using the often overlooked float property.

You will probably notice some missalignment in some particular cases. Yes, it’s again some rounding issue related to the calculation we are doing and the fact that we are trying to make this generic with the interactive demos. To rectify this, we simply adjust few values manually (epsecially the percentage of the gradient) until we get back a perfect alignment.

That’s a float wrap!

There we have it: combining float with shape-outside can help us make complex, flexible and responsive layouts — long live float!

The article ends here but this is only the beginning. I provided you with the layout and now you can easily put any content inside the divs, apply a background, shadows, animations, etc.


The post Hexagons and Beyond: Flexible, Responsive Grid Patterns, Sans Media Queries appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , , ,
[Top]

Creating Patterns With SVG Filters

For years, my pain has been not being able to create a somewhat natural-looking pattern in CSS. I mean, sometimes all I need is a wood texture. The only production-friendly solution I knew of was to use an external image, but external images are an additional dependency and they introduce a new complexity.

I know now that a good portion of these problems could be solved with a few lines of SVG.

Tl;dr: Take me to the gallery!

There is a Filter Primitive in SVG called <feTurbulence>. It’s special in the sense that it doesn’t need any input image — the filter primitive itself generates an image. It produces so-called Perlin noise which is a type of noise gradient. Perlin noise is heavily used in computer generated graphics for creating all sorts of textures. <feTurbulence> comes with options to create multiple types of noise textures and millions of variations per type.

All of these are generated with <feTurbulence>.

“So what?” you might ask. These textures are certainly noisy, but they also contain hidden patterns which we can uncover by pairing them with other filters! That’s what we’re about to jump into.

Creating SVG filters

A custom filter typically consists of multiple filter primitives chained together to achieve a desired outcome. In SVG, we can describe these in a declarative way with the <filter> element and a number of <fe{PrimitiveName}> elements. A declared filter can then be applied on a renderable element — like <rect>, <circle>, <path>, <text>, etc. — by referencing the filter’s id. The following snippet shows an empty filter identified as coolEffect, and applied on a full width and height <rect>.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="coolEffect">     <!-- Filter primitives will be written here -->   </filter>   <rect width="100%" height="100%" filter="url(#coolEffect)"/> </svg>

SVG offers more than a dozen different filter primitives but let’s start with a relatively easy one, <feFlood> . It does exactly what it says: it floods a target area. (This also doesn’t need an input image.) The target area is technically the filter primitive’s sub-region within the <filter> region.

Both the filter region and the filter primitive’s sub-region can be customized. However, we will use the defaults throughout this article, which is practically the full area of our rectangle. The following snippet makes our rectangle red and semi-transparent by setting the flood-color (red) and flood-opacity (0.5) attributes.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="coolEffect">     <feFlood flood-color="red" flood-opacity="0.5"/>   </filter>   <rect width="100%" height="100%" filter="url(#coolEffect)"/> </svg>
A solid light red rectangle that is wider what it is tall.
A semi-transparent red rectangle looks light red on a white background and dark red on a black background because the opacity is set to  0.5.

Now let’s look at the <feBlend> primitive. It’s used for blending multiple inputs. One of our inputs can be SourceGraphic, a keyword that represents the original graphic on which the filter is applied.

Our original graphic is a black rectangle — that’s because we haven’t specified the fill on the <rect> and the default fill color is black. Our other input is the result of the <feFlood> primitive. As you can see below we’ve added the result attribute to <feFlood> to name its output. We are referencing this output in <feBlend> with the in attribute, and the SourceGraphic with the in2 attribute.

The default blend mode is normal and the input order matters. I would describe our blending operation as putting the semi-transparent red rectangle on top of the black rectangle.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="coolEffect">     <feFlood flood-color="red" flood-opacity="0.5" result="flood"/>     <feBlend in="flood" in2="SourceGraphic"/>   </filter>   <rect width="100%" height="100%" filter="url(#coolEffect)"/> </svg>
A solid dark red rectangle that is wider what it is tall.
Now, our rectangle is dark red, no matter what color the background is behind it. That’s because we stacked our semi-transparent red <feFlood> on top of the black <rect> and blended the two together with <feBlend>, we chained the result of <feFlood> to <feBlend>.

Chaining filter primitives is a pretty frequent operation and, luckily, it has useful defaults that have been standardized. In our example above, we could have omitted the result attribute in <feFlood> as well as the in attribute in <feBlend>, because any subsequent filter will use the result of the previous filter as its input. We will use this shortcut quite often throughout this article.

Generating random patterns with feTurbulence

<feTurbulence> has a few attributes that determine the noise pattern it produces. Let’s walk through these, one by one.

baseFrequency

This is the most important attribute because it is required in order to create a pattern. It accepts one or two numeric values. Specifying two numbers defines the frequency along the x- and y-axis, respectively. If only one number is provided, then it defines the frequency along both axes. A reasonable interval for the values is between 0.001 and 1, where a low value results in large “features” and a high value results in smaller “features.” The greater the difference between the x and y frequencies, the more “stretched” the pattern becomes.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="coolEffect">     <feTurbulence baseFrequency="0.001 1"/>   </filter>   <rect width="100%" height="100%" filter="url(#coolEffect)"/> </svg>
The baseFrequency values in the top row, from left to right: 0.010.11.  Bottom row: 0.01 0.1, 0.1 0.010.001 1.

type

The type attribute takes one of two values: turbulence (the default) or fractalNoise, which is what I typically use. fractalNoise produces the same kind of pattern across the red, green, blue and alpha (RGBA) channels, whereas turbulence in the alpha channel is different from those in RGB. I find it tough to describe the difference, but it’s much easier to see when comparing the visual results.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="coolEffect">     <feTurbulence baseFrequency="0.1" type="fractalNoise"/>   </filter>   <rect width="100%" height="100%" filter="url(#coolEffect)"/> </svg>
Two squares side-by-side filled with pastel patterns. The left square is sharper than the right square, which is blurry.
The turbulence type (left) compared to the fractalNoise type (right)

numOctaves

The concept of octaves might be familiar to you from music or physics. A high octave doubles the frequency. And, for <feTurbulence> in SVG, the numOctaves attribute defines the number of octaves to render over the baseFrequency.

The default numOctaves value is 1, which means it renders noise at the base frequency. Any additional octave doubles the frequency and halves the amplitude. The higher this number goes, the less visible its effect will be. Also, more octaves mean more calculation, possibly hurting performance. I typically use values between 1-5 and only use it to refine a pattern.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="coolEffect">     <feTurbulence baseFrequency="0.1" type="fractalNoise" numOctaves="2"/>   </filter>   <rect width="100%" height="100%" filter="url(#coolEffect)"/> </svg>
numOctaves values compared: 1 (left), 2 (center), and 5 (right)

seed

The seed attribute creates different instances of noise, and serves as the the starting number for the noise generator, which produces pseudo-random numbers under the hood. If the seed value is defined, a different instance of noise will appear, but with the same qualities. Its default value is 0 and positive integers are interpreted (although 0 and 1 are considered to be the same seed). Floats are truncated.

This attribute is best for adding a unique touch to a pattern. For example, a random seed can be generated on a visit to a page so that every visitor will get a slightly different pattern. A practical interval for generating random seeds is from 0 to 9999999 due to some technical details and single precision floats. But still, that’s 10 million different instances, which hopefully covers most cases.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="coolEffect">     <feTurbulence baseFrequency="0.1" type="fractalNoise" numOctaves="2" seed="7329663"/>   </filter>   <rect width="100%" height="100%" filter="url(#coolEffect)"/> </svg>
seed values compared: 1 (left), 2 (center), and 7329663 (right)

stitchTiles

We can tile a pattern the same sort of way we can use background-repeat: repeat in CSS! All we need is the stitchTiles attribute, which accepts one of two keyword values: noStitch and stitch, where noStitch is the default value. stitch repeats the pattern seamlessly along both axes.

Two long rectangles with blurry color patterns stacked one on top of the other. The top rectangle is split into six smaller rectangles, carrying the same pattern. The bottom rectangle is a single pattern.
Comparing noStitch (top) to stitch (bottom)

Note that <feTurbulence> also produces noise in the Alpha channel, meaning the images are semi-transparent, rather than fully opaque.

Let’s look at a bunch of awesome patterns made with SVG filters and figure out how they work!

Starry Sky

This pattern consists of two chained filter effects on a full width and height rectangle. <feTurbulence> is the first filter, responsible for generating noise. <feColorMatrix> is the second filter effect, and it alters the input image, pixel by pixel. We can tell specifically what each output channel value should be based on a constant and all the input channel values within a pixel. The formula per channel looks like this:

O =w_RR_i+w_GG_i+w_BB_i+w_AA_i+w_C

  • O is the output channel value
  • R_i, G_i, B_i, A_i are the input channel values
  • w_R, w_G, w_B, w_A, w_C are the weights

So, for example, we can write a formula for the Red channel that only considers the Green channel by setting w_G to 1, and setting the other weights to 0. We can write similar formulas for the Green and Blue channels that only consider the Blue and Red channels, respectively. For the Alpha channel, we can set w_C (the constant) to 1 and the other weights to 0 to create a fully opaque image. These four formulas perform a hue rotation.

The formulas can also be written as matrix multiplication, which is the origin of the name <feColorMatrix>. Though <feColorMatrix> can be used without understanding matrix operations, we need to keep in mind that our 4×5 matrix are the 4×5 weights of the four formulas.

\begin{bmatrix} w_{RR} & w_{GR} & w_{BR} & w_{AR} & w_{CR}\\ w_{RG} & w_{GG} & w_{BG} & w_{AG} & w_{CG} \\ w_{RB} & w_{GB} & w_{BB} & w_{AB} & w_{CB} \\ w_{RA} & w_{GA} & w_{BA} & w_{AA} & w_{CA} \end{bmatrix}

  • w_{RR} is the weight of Red channel’s contribution to the Red channel.
  • w_{RG} is the weight of Red channel’s contribution to the Green channel.
  • w_{GR} is the weight of Green channel’s contribution to the Red channel.
  • w_{GG} is the weight of Green channel’s contribution to the Green channel.
  • The description of the remaining 16 weights are omitted for the sake ofbrevity

The hue rotation mentioned above is written like this:

\begin{bmatrix} 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & \end{bmatrix}

It’s important to note that the RGBA values are floats ranging from 0 to 1, inclusive (rather than integers ranging from 0 to 255 as you might expect). The weights can be any float, although at the end of the calculations any result below 0 is clamped to 0, and anything above 1 is clamped to 1. The starry sky pattern relies on this clamping, since it’s matrix is this:

\begin{bmatrix} 0 & 0 & 0 & 9 & -4 \\ 0 & 0 & 0 & 9 & -4 \\ 0 & 0 & 0 & 9 & -4 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix}

The transfer function described by <feColorMatrix> for the R, G, and B channels. The input is always Alpha.

We are using the same formula for the RGB channels which means we are producing a grayscale image. The formula is multiplying the value from the Alpha channel by nine, then removing four from it. Remember, even Alpha values vary in the output of <feTurbulence>. Most resulting values will not be within the 0 to 1 range; thus they will be clamped. So, our image is mostly either black or white — black being the sky, and white being the brightest stars; the remaining few in-between values are dim stars. We are setting the Alpha channel to a constant of 1 in the fourth row, meaning the image is fully opaque.

Pine Wood

This code is not much different from what we just saw in Starry Sky. It’s really just some noise generation and color matrix transformation. A typical wooden pattern has features that are longer in one dimension than the other. To mimic this effect, we are creating “stretched” noise with <feTurbulence> by setting baseFrequency="0.1 0.01". Furthermore, we are setting type="fractalNoise".

With <feColorMatrix>, we are simply recoloring our longer pattern. And, once again, the Alpha channel is used as an input for variance. This time, however, we are offsetting the RGB channels by constant weights that are greater than the weights applied on the Alpha input. This ensures that all the pixels of the image remain within a certain color range. Finding the best color range requires a little bit of playing around with the values.

\begin{bmatrix} 0 & 0 & 0 & .11 & .69 \\ 0 & 0 & 0 & .09 & .38 \\ 0 & 0 & 0 & .08 & .14 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix}

Mapping Alpha values to colors with <feColorMatrix> While it’s extremely subtle, the second bar is a gradient.

It’s essential to understand that the matrix operates in the linearized RGB color space by default. The color purple (#800080), for example, is represented by values R=0.216, G=0, and B=0.216. It might look odd at first, but there’s a good reason for using linearized RGB for some transformations. This article provides a good answer for the why, and this article is great for diving into the how.

At the end of the day, all it means is that we need to convert our usual #RRGGBB values to the linearized RGB space. I used this color space tool to do that. Input the RGB values in the first line then use the values from the third line. In the case of our purple example, we would input R=128, G=0, B=128 in the first line and hit the sRGB8 button to get the linearized values in the third line.

If we pick our values right and perform the conversions correctly, we end up with something that resembles the colors of pine wood.

Dalmatian Spots

This example spices things up a bit by introducing <feComponentTransfer> filter. This effect allows us to define custom transfer functions per color channel (also known as color component). We’re only defining one custom transfer function in this demo for the Alpha channel and leave the other channels undefined (which means identity function will be applied). We use the discrete type to set a step function. The steps are described by space-separated numeric values in the tableValues attribute. tableValues control the number of steps and the height of each step.

Let’s consider examples where we play around with the tableValues value. Our goal is to create a “spotty” pattern out of the noise. Here’s what we know:

  • tableValues="1" transfers each value to 1.
  • tableValues="0" transfers each value to 0.
  • tableValues="0 1" transfers values below 0.5 to 0 and values from 0.5 to 1.
  • tableValues="1 0" transfers values below 0.5 to 1 and values from 0.5 to 0.
Three simple step functions. The third (right) shows what is used in Dalmatian Spots.

It’s worth playing around with this attribute to better understand its capabilities and the quality of our noise. After some experimenting we arrive at tableValues="0 1 0" which translates mid-range values to 1 and others to 0.

The last filter effect in this example is <feColorMatrix> which is used to recolor the pattern. Specifically, it makes the transparent parts (Alpha = 0) black and the opaque parts (Alpha = 1) white.

Finally, we fine-tune the pattern with <feTurbulence>. Setting numOctaves="2" helps make the spots a little more “jagged” and reduces elongated spots. The baseFrequency="0.06" basically sets a zoom level which I think is best for this pattern.

ERDL Camouflage

The ERDL pattern was developed for disguising of military personnel, equipment, and installation. In recent decades, it found its way into clothing. The pattern consists of four colors: a darker green for the backdrop, brown for the shapes shapes, a yellowish-green for patches, and black sprinkled in as little blobs.

Similarly to the Dalmatian Spots example we looked at, we are chaining <feComponentTransfer> to the noise — although this time the discrete functions are defined for the RGB channels.

Imagine that the RGBA channels are four layers of the image. We create blobs in three layers by defining single-step functions. The step starts at different positions for each function, producing a different number of blobs on each layer. The cuts for Red, Green and Blue are 66.67%, 60% and 50%, respectively..

<feFuncR type="discrete" tableValues="0 0 0 0 1 1"/> <feFuncG type="discrete" tableValues="0 0 0 1 1"/> <feFuncB type="discrete" tableValues="0 1"/>

At this point, the blobs on each layers overlap in some places, resulting colors we don’t want. These other colors make it more difficult to transform our pattern into an ERDL camouflage, so let’s eliminate them:

  • For Red, we define the identity function.
  • For Green, our starting point is the identity function but we subtract the Red from it.
  • For Blue, our starting point is the identity function as well, but we subtract the Red and Green from it.

\begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ -1 & 1 & 0 & 0 & 0 \\ -1 & -1 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix}

These rules mean Red remains where Red and Green and/or Blue once overlapped; Green remains where Green and Blue overlapped. The resulting image contains four types of pixels: Red, Green, Blue, or Black.

The second chained <feColorMatrix> recolors everything:

  • The black parts are made dark green with the constant weights.
  • The red parts are made black by negating the constant weights.
  • The green parts are made that yellow-green color by the additional weights from the Green channel.
  • The blue parts are made brown by the additional weights from the Blue channel.

Island Group

This example is basically a heightmap. It’s pretty easy to produce a realistic looking heightmap with <feTurbulence> — we only need to focus on one color channel and we already have it. Let’s focus on the Red channel. With the help of a <feColorMatrix>, we turn the colorful noise into a grayscale heightmap by overwriting the Green and Blue channels with the value of the Red channel.

\begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 \end{bmatrix}

Now we can rely on the same value for each color channel per pixel. This makes it easy to recolor our image level-by-level with the help of <feComponentTransfer>, although, this time, we use a table type of function. table is similar to discrete, but each step is a ramp to the next step. This allows for a much smoother transition between levels.

The RGB transfer functions defined in <feComponentTransfer>

The number of tableValues determine how many ramps are in the transfer function. We have to consider two things to find the optimal number of ramps. One is the distribution of intensity in the image. The different intensities are unevenly distributed, although the ramp widths are always equal. The other thing to consider is the number of levels we would like to see. And, of course, we also need to remember that we are in linearized RGB space. We could get into the maths of all these, but it’s much easier to just play around and feel out the right values.

Mapping grayscale to colors with <feComponentTransfer>

We use deep blue and aqua color values from the lowest intensities to somewhere in the middle to represent the water. Then, we use a few flavors of yellow for the sandy parts. Finally, green and dark green at the highest intensities create the forest.


We haven’t seen the seed attribute in any these examples, but I invite you to try it out by adding it in there. Think of a random number between 1 and 10 million, then use that number as the seed attribute value in <feTurbulence>, like <feTurbulence seed="3761593" ... >

Now you have your own variation of the pattern!

Production use

So far, what we’ve done is look at a bunch of cool SVG patterns and how they’re made. A lot of what we’ve seen is great proof-of-concept, but the real benefit is being able to use the patterns in production in a responsible way.

The way I see it, there are three fundamental paths to choose from.

Method 1: Using an inline data URI in CSS or HTML

My favorite way to use SVGs is to inline them, provided they are small enough. For me, “small enough” means a few kilobytes or less, but it really depends on the particular use case. The upside of inlining is that the image is guaranteed to be there in your CSS or HTML file, meaning there is no need to wait until it is downloaded.

The downside is having to encode the markup. Fortunately, there are some great tools made just for this purpose. Yoksel’s URL-encoder for SVG is one such tool that provides a copy-paste way UI to generate the code. If you’re looking for a programmatic approach — like as part of a build process — I suggest looking into mini-svg-data-uri. I haven’t used it personally, but it seems quite popular.

Regardless of the approach, the encoded data URI goes right in your CSS or HTML (or even JavaScript). CSS is better because of its reusability, but HTML has minimal delivery time. If you are using some sort of server-side rendering technique, you can also slip a randomized seed value in <feTurbulence> within the data URI to show a unique variation for each user.

Here’s the Starry Sky example used as a background image with its inline data URI in CSS:

.your-selector {   background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='filter'%3E%3CfeTurbulence baseFrequency='0.2'/%3E%3CfeColorMatrix values='0 0 0 9 -4 0 0 0 9 -4 0 0 0 9 -4 0 0 0 0 1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23filter)'/%3E%3C/svg%3E%0A"); }

And this is how it looks used as an <img> in HTML:

<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='filter'%3E%3CfeTurbulence baseFrequency='0.2'/%3E%3CfeColorMatrix values='0 0 0 9 -4 0 0 0 9 -4 0 0 0 9 -4 0 0 0 0 1'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23filter)'/%3E%3C/svg%3E%0A"/>

Method 2: Using SVG markup in HTML

We can simply put the SVG code itself into HTML. Here’s Starry Sky’s markup, which can be dropped into an HTML file:

<div>   <svg xmlns="http://www.w3.org/2000/svg">     <filter id="filter">       <feTurbulence baseFrequency="0.2"/>       <feColorMatrix values="0 0 0 9 -4                              0 0 0 9 -4                              0 0 0 9 -4                              0 0 0 0 1"/>     </filter>     <rect width="100%" height="100%" filter="url(#filter)"/>   </svg> </div>

It’s a super simple approach, but carries a huge drawback — especially with the examples we’ve seen.

That drawback? ID collision.

Notice that the SVG markup uses a #filter ID. Imagine adding the other examples in the same HTML file. If they also use a #filter ID, then that would cause the IDs to collide where the first instance overrides the others.

Personally, I would only use this technique in hand-crafted pages where the scope is small enough to be aware of all the included SVGs and their IDs. There’s the option of generating unique IDs during a build, but that’s a whole other story.

Method 3: Using a standalone SVG

This is the “classic” way to do SVG. In fact, it’s just like using any other image file. Drop the SVG file on a server, then use the URL in an HTML <img> tag, or somewhere in CSS like a background image.

So, going back to the Starry Sky example. Here’s the contents of the SVG file again, but this time the file itself goes on the server.

<svg xmlns="http://www.w3.org/2000/svg">   <filter id="filter">     <feTurbulence baseFrequency="0.2"/>     <feColorMatrix values="0 0 0 9 -4                            0 0 0 9 -4                            0 0 0 9 -4                            0 0 0 0 1"/>   </filter>   <rect width="100%" height="100%" filter="url(#filter)"/> </svg>

Now we can use it in HTML, say as as image:

<img src="https://example.com/starry-sky.svg"/>

And it’s just as convenient to use the URL in CSS, like a background image:

.your-selector {   background-image: url("https://example.com/starry-sky.svg"); }

Considering today’s HTTP2 support and how relatively small SVG files are compared to raster images, this isn’t a bad solution at all. Alternatively, the file can be placed on a CDN for even better delivery. The benefit of having SVGs as separate files is that they can be cached in multiple layers.

Caveats

While I really enjoy crafting these little patterns, I also have to acknowledge some of their imperfections.

The most important imperfection is that they can pretty quickly create a computationally heavy “monster” filter chain. The individual filter effects are very similar to one-off operations in photo editing software. We are basically “photoshopping” with code, and every time the browser displays this sort of SVG, it has to render each operation. So, if you end up having a long filter chain, you might be better off capturing and serving your result as a JPEG or PNG to save users CPU time. Taylor Hunt’s “Improving SVG Runtime Performance” has a lot of other great tips for getting the most performance out of SVG.

Secondly, we’ve got to talk about browser support. Generally speaking, SVG is well-supported, especially in modern browsers. However, I came across one issue with Safari when working with these patterns. I tried creating a repeating circular pattern using <radialGradient> with spreadMethod="repeat". It worked well in Chrome and Firefox, but Safari wasn’t happy with it. Safari displays the radial gradient as if its spreadMethod was set to pad. You can verify it right in MDN’s documentation.

You may get different browsers rendering the same SVG differently. Considering all the complexities of rendering SVG, it’s pretty hard to achieve perfect consistency. That said, I have only found one difference between the browsers that’s worth mentioning and it’s when switching to “full screen” view. When Firefox goes full screen, it doesn’t render the SVG in the extended part of the viewport. Chrome and Safari are good though. You can verify it by opening this pen in your browser of choice, then going into full screen mode. It’s a rare edge-case that could probably be worked around with some JavaScript and the Fullscreen API.

Thanks!

Phew, that’s a wrap! We not only got to look at some cool patterns, but we learned a ton about <feTurbulence> in SVG, including the various filters it takes and how to manipulate them in interesting ways.

Like most things on the web, there are downsides and potential drawbacks to the concepts we covered together, but hopefully you now have a sense of what’s possible and what to watch for. You have the power to create some awesome patterns in SVG!


The post Creating Patterns With SVG Filters appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

On Type Patterns and Style Guides

Over the last six years or so, I’ve been using these things I’ve been calling “type patterns” in my web design work, and they’ve worked out pretty well for me. I’ll dig into what they are and how they can make their way into CSS, and maybe the idea will click with you too and help with your day-to-day typography needs.

If you’ve used print design desktop software like QuarkXPress, Adobe InDesign, or CorelDraw, then imagine this idea is an HTML/CSS translation of “paragraph styles.”

When designing a book (that spans hundreds of pages), you might want to change something about the heading typography across the whole book (on the fly). You would define how a certain piece of typography behaves in one central place to be applied across the entire project (a book, in our example). You need control of the patterns.

Most programs use this naming style, but their user interfaces vary a little.

When you pull up the pane, there’s usually a “base” paragraph style that all default text belongs to. From there, you can create as many as you want. Paragraph styles are for “block” level-like elements, and character styles are for “inline” level-like elements, such as bold or unique spans.

The user interface specifics shouldn’t matter — but you can see that there are a lot of controls to define how this text behaves visually. Under the hood, it’s just key: value pairs, which is just like CSS property: value pairs

h1 {   font-family: "Helvetica Neue", sans-serif;    font-size: 20px;   font-weight: bold;   color: fuchsia; }

Once defined, the styles can be applied to any piece of text. The little + (next to the paragraph style name below) in this case, means that the style definition has changed.

If you want to apply those changes to everything with that paragraph style, then you can “redefine” the style and it will apply project-wide.

When I say it like that, it might make you think: that’s what a CSS class is.

But things are a little more complicated for a website. You never know what size your website will be displayed at (it could be small, like on a mobile device, or giant, like on a desktop monitor, or even on monochrome tablet, who knows), so we need to be contextual with those classes and have then change based on their context.

Styles change as the context changes.

The bare minimum of typographic control

In your very earliest days as a developer, you may have been introduced to semantic HTML, like this:

<h1>Here's some HTML stuff. I'm a heading level 1</h1> <p>And some more. I'm a paragraph.</p>  <h2>This is a heading level 2</h2> <p>And some more pragraph stuff.</p>

And that pairs with CSS that targets those elements and applies styles, like this:

h1 {   font-size: 50px; /* key: value pairs */   color: #ff0066; }  h2 {   font-size: 32px;   color: rgba(0,0,0,.8); }  p {   font-size: 16px;   color: deepskyblue;   line-height: 1.5; }

This works!

You can write rules that target the headers and style them in descending order, so they are biggest > big > medium, and so on.

Headers also already have some styles that we accept as normal, thanks to User Agent styles (i.e. default styles applied to HTML by the browser itself). They are meant to be helpful. They add things like font-weight and margin to the headers, as well as collapsing margins. That way — without any CSS at all — we can rest assured we get at least some basic styling in place to establish a hierarchy. This is beginner-friendly, fallback-friendly… and a good thing!

As you build more complex sites, things get more complicated

You add more pages. More modules. More components. Things start to get more complex. You might start out by adding unique classes and styles for every single little thing, but it’ll catch up to you.

First, you start having classes for special situations:

<h1 class="page-title">   Be <span class='super-ultra-magic-rainbow'>excellent</span> to each other </h1>  <p class="special-mantra">Party on, <em>dudes</em>.</p>  <p>And yeah. I'm just a regular paragraph.</p>

Then, you start having classes everywhere (most CSS methodologies even encourage this):

<header class="site-header">   <h1 class="page-title">     Be <span class='ultra-magic-rainbow'>excellent</span> to each other   </h1> </header>  <main class="page-content">   <section class="welcome">     <h2 class="special-mantra">Party on <em>dudes</em></h2>      <p class="regular-paragraph">And yeah. I'm just regular</p>   </section> </main>

Newcomers will try and work around the default font sizes and collapsing margins if they don’t feel confident resetting them.

This is where people start trying out margin-top: -20px… and then stuff gets whacky. As you keep writing more rules to wrangle things in, it will feel like you are “fixing” things instead of declaring how you actually want them to work. It can quickly feel like you are “fighting” the CSS cascade when you’re unaware of the browser’s User Agent styles.

A real-world example

Imagine you’re at your real job and your boss (or the visual designer) gives you this “pixel perfect” Adobe Photoshop document. There are a bunch of colors, layout, and typography.

You open up Photoshop and start to poke around, but there are so many pages and so many different type styles that you’ll need to take inventory and gather what styles can be combined or used in combination.

Would you believe that there are 12 different sizes of type on this page? There’s possibly even more if you also take the line-height into consideration.

It feels great to finish a visual layout and hand it off. However, I can’t tell you how many full days I’ve spent trying to figure out what the heck is happening in a Photoshop document. For example, sometimes small-screens aren’t taken into consideration at all; and when they are, the patterns you find aren’t always shared by each group as they change for different screen types. Some fonts start at 16px and go up to 18px, while others go up to 19px and become italic. How can spot context changes in a static mockup?

Sometimes this is with fervent purpose; other times the visual designer is just going on feel and is happy to round things up and down to create reusable patterns. You’ll have to talk to them about it. But this article is advocating that we talk about it much earlier in the process.

You might get a style guide to reference. But even that might not be specific enough to identify contexts.

Let’s zoom in on one of those guidelines:

We get more content direction than we do behavior of the content in different contexts.

You may even get a formal, but generic, style guide with no pixel sizes or notes on varying screen sizes at all!

Don’t get me wrong: this sort of thing is defintely a nice thought, and it might even be useful for others, like in some client meeting or something. But, as far as front-end development goes, it’s more confusing than helpful. I’ve received very thorough style guides that looked nice and gave lots of excellent guidelines for things like font sizes, but are completely mismatched with the accompanying Photoshop document. On the other end of the spectrum, there are style guides that have unholy amounts of specifics for every type of heading and combination you could ever imagine — and more.

It’s hard to parse this stuff, even with the best of intentions!

Early in your development career, you’d probably assume it’s your job to “figure it out” and get to work, writing down all the pixels and trying your best to make sense of it. Go getem!

But, as you start coding all the specifics, things can get a little overwhelming with the amount of duplication going on. Just look at all the repeated properties going on here:

.blog article p {   font-family: 'Georgia', serif;   font-size: 17px;   line-height: 1.4;   letter-spacing: 0.02em;   margin-bottom: 10px; }  .welcome .main-message {   font-family: 'Georgia', serif;   font-size: 17px;   line-height: 1.4;   letter-spacing: 0.02em;   margin-bottom: 20px; }  @media (min-width; 700px) {   .welcome .main-message {     font-size: 18px;   } }  .welcome .other-thing {   font-family: 'Georgia', serif;   font-size: 17px;   line-height: 1.4;   letter-spacing: 0.02em;   margin-bottom: 20px; }  .site-footer .link list a {   font-family: 'Georgia', serif;   font-size: 17px;   line-height: 1.4;   letter-spacing: 0.02em;   margin-bottom: 20px; }

You might take the common declarations and apply them to the body instead. In smaller projects, that might even be a good way to go. There are ways to use the cascade to your advantage, and others that seem to tie too many things together. Just like in an Object Oriented programming language, you don’t necessarily want everything inheriting everything.

body {   font-family: 'Georgia', serif;   font-size: 17px;   line-height: 1.4;   letter-spacing: 0.02em; }

Things will work out OK. Most of the web was built like this. We’re just looking for something even better.

Dealing with design revisions

One day, there will be a meeting. In that meeting, you’ll find out that the client and the visual designer decided to change a bunch of the typography. Now you need to go back and change it in 293 places in the CSS file. If you get paid by the hour, that might be great!

As you begin to adjust the rules, things will start to conflict. That rule that worked for two things now only works for the one. Or you’ll notice patterns that could now be used in many more places than before. You may even be tempted to just totally delete the CSS and start over! Yikes!

I won’t write it out here, but you’ll try a bunch of different things over time, and people usually come to the conclusion that you can create a class — and add it to the element instead of duplicating rules/declarations for every element. You’ll go even further to try and pull patterns out of the visual designer’s documents. (You might even round a few 19px down to 18px without telling them…)

.standard-text { /* or something */   font-family: serif;   font-size: 16px; /* px: up for debate */   line-height: 1.4; /* no unit: so it's relative to the font-size */   letter-spacing: 0.02em; /* em: so it's relative to the font-size */ }  .heading-1 {   font-family: sans-Serif;   font-size: 30px;   line-height: 1.5;   letter-spacing: 0.03em; }  .medium-heading {   font-family: sans-Serif;   font-size: 24px;   line-height: 1.3;   letter-spacing: 0.04em; }

Then you’d apply the class to anything that needs it.

<header class="site-header">   <h1 class="page-title heading-1">     Be <mark>excellent</mark> to each other   </h1> </header>  <main class="page-content">   <section class="welcome">     <h2 class="medium-heading">Party on <em>dudes</em></h2>      <p class="standard-text">And yeah. I'm just regular</p>   </section> </main>

This way of doing things can be really helpful for teams that have people of all skill levels changing the HTML. You can plug and play with these CSS classes to get the style you want, even if you’re the new intern.

It’s really helpful to separate the idea of “layout” elements (structure/parents) and the idea of “modules” or “components.” In this way, we’re treating the pieces of text as lower level components.

The key is to keep the typography separate from the layout. You don’t want all .medium-heading elements to have the same margins or colors. It’s going to depend on where they are. This way you are styling based on context. You aren’t ‘using’ the cascade necessarily, but you also aren’t fighting it because you keep the techniques separate.

.site-header {   padding: 20px 0; }  .welcome .medium-heading { /* the context — not the type-pattern */   margin-bottom: 10px; }

This is keeping things reasonable and tidy. This technique is used all over the web.

Working with a CMS

Great, but what about situations where you can’t change the HTML?

You might just be typing up a quick CodePen or a business-card site. In that case, these concerns are going to seem like overkill. On the other hand, you might be working with a CMS where you aren’t sure what is going to happen. You might need to plan for anything and everything that could get thrown at you. In those cases, you’re unable to simply add classes to individual elements. You’re likely to get a dump of HTML from some templating language.

<?php echo getContent()?> <?=getContent()?> $ {data.content} {{model.cmsContent}}

So, if you can’t work with the HTML what can you do?

<article class="post cms-blog-dump">   <h1>Talking type-patterns on CSS-tricks</h1>   <p>Intoduction paragraph - and we'd like to style this with a slightly different size font then the next (normal) paragraphs</p>   <h2>Some headings</h2>   <h2>And maybe someone accidentally puts 2 headings in a row</h2>   <ol>     <li>and some <strong>list</strong></li>     <li>and here</li>   </ol>    <p>Or if a blog post is too boring - then think of a list of bands on an event site. You never know how many there will be or which ones are headlining, so you have to write rules that will handle whatever happens. </article>

You don’t have any control over this markup, so you won’t be able to add classes, meaning that the cool plug-and-play classes you made aren’t going to work! You might just copy and paste them into some larger .article { } class that defines the rules for a kitchen sink. That could work.

What other tools are there to play with?

Mixins

If you could create some reusable concept of a “type pattern” with Sass, then you could apply those in a similar way to how the classes work.

@mixin my-useful-rule { /* define the mixin */   background-color: blue;   color: lime; }  .thing {   @include my-useful-rule(); /* employ the mixin */ }  /* This compiles to: */ .thing {   background-color: blue;   color: lime; } /* (just so that we're on the same page) */

Less, Sass, Stylus and other CSS preprocessors all have their own syntax for this. I’m going to use Sass/SCSS in these examples because it is the most common at the time of writing.

@mixin standard-type() { /* define the mixin */   font-family: Serif;   font-size: 16px;   line-height: 1.4;   letter-spacing: 0.02em; }  .context .thing {   @include standard-type(); /* employ it in context */ }

You can use heading-1() and heading-2() and a lot of big-name style guides do this. But what if you want to use those type styles on something that isn’t a “heading”? I personally don’t end up connecting the headings with “size” and I use the patterns in all sorts of different places. Sometimes my headings are “mean” and “stout.” They can be piercing red and uppercase with the same x-height as the paragraph text.

Instead, I define the visual styles in association with how I want the “voice” of the content to come across. This also helps the team discuss “tone” and other content strategy stuff across disciplines.

For example, in Jason Santa Maria’s book, On Web Typography, he talks about “type for a moment” and “type to live with.” There’s type to grab your attention and break up the page, and then those paragraphs to settle into. Instead of .standard-text or .normal-font, I’ve been enjoying the idea of organizing styles by voice. This is all that type that a user should spend time consuming. It’s what I’d likely use for most paragraphs and lists, and I won’t set it on the body.

@mixin calm-voice() { /* define the mixin */   font-family: Serif;   font-size: 16px;   line-height: 1.4;   letter-spacing: 0.02em; }  @mixin loud-voice() {   font-family: Sans-Serif;   font-size: 30px;   line-height: 1.5;   letter-spacing: 0.03em; }  @mixin attention-voice() {   font-family: Sans-Serif;   font-size: 24px;   line-height: 1.3;   letter-spacing: 0.04em; }

This idea of “voices” helps me keep things meaningful because it requires you to name things by the context. The name heading-1b, for example, doesn’t help me connect to the content to any sort of storytelling or to the other people on the team.

Now to style the mystery article. I’ll be using SCSS syntax here:

article {   padding: 20px 0;   h1 {     @include loud-voice();     margin-bottom: 20px;   }   h2 {     @include attention-voice();     margin-bottom: 20px;   }   p {     @include calm-voice();     margin-bottom: 10px;   } }

Pretty, right?

But it’s not that easy, is it? No. It’s a little more complicated because you don’t know what might be above or below each other — or what might be left out, because articles aren’t always structured or organized the same. Those CMS authors can put whatever they want in there! Three <h3> elements in a row? You have to prepare for lots of possible outcomes.

/* Styles */ article {   padding: 20px 0;    h1 {     @include loud-voice();   }    h2 {     @include attention-voice();   }    p {     @include calm-voice();      &:first-of-type {       background: lightgreen;       padding: 1em;     }   }    ol {     @include styled-ordered-list();   }    * + * {     margin-top: 1em    } } 

To see the regular CSS you can always “View Compiled” in CodePen.

Some people are really happy with the lobotomized owl approach (* + *) but I usually end up writing explicit rules for “any <h2> that comes after a paragraph” and getting really detailed. After all, it’s the written content that everyone wants to read… and I really only need to dial it in one time in one place.

/* Slightly more filled out pattern */ @mixin calm-voice() {   font-family: serif;   font-size: 16px;   line-height: 1.4;   letter-spacing: 0.02em;   max-width: 80ch;    strong {     font-weight: 700;   }    em {     font-style: italic;   }    mark {     background-color: wheat;   }    sup {     /* maybe? */   }    a {     text-decoration: underline;     color: $ highlight;   }    @media (min-width: 600px) {     font-size: 17px;   } }

Stylus

It’s nice to think about the “ideal” workflow. What could browsers implement that would make this fun and play well with their future systems?

Here’s an example of the most stripped down preprocessor syntax:

calm-voice()   font-family: serif   font-size: 16px   line-height: 1.4   letter-spacing: 0.02em  article   h1     loud-voice()   h2     attention-voice()   p     calm-voice() 

I’ll be honest… I love Stylus. I love writing it, and I love using it for examples. It keeps people on their toes. It’s so fast to type in CodePen! If you already have your own little framework of styles like this, it’s insane how fast you can build UI. But! The tooling has fallen behind and at this point, I can’t recommend that anyone use it.

I only add it here because it’s important to dream. We have what we have, but what if you could invent a new way of writing it? That’s a crucial part of the conversation too. If you can’t describe what you want, you wont get it.

We’re here: Type Patterns

Can you see where all of this is going?

You can use these “patterns” or “mixins’ or “whatever” you want to call them and plug and play. It’s fun. And you can totally combine it with the utility class idea too (if you must).

.calm-voice {   @include calm-voice(); }
<p class="calm-voice">Welcome to this code snippet!</p>

Style guides

If you can start to share a common language and break down the barriers between “creatives” and “coders,” then everyone can work with these type patterns in mind from the start.

Sometimes you can simply publish a style guide as a “brand” subdomain or directly on the site, like at /style-guide. There are tons of these floating around on the web. The point is that some style guides are standalone, and others are built into the site itself. Wherever they go, they are considered “live” and they allow you to develop things in one place that take effect globally, and use the guide itself as a sort of artifact.

By building live style guides with type patterns as a core and shared concept, everyone will have more fun and save time trying to figure out what each other means. It’s not just for big companies either.

Just be mindful when looking at style guides for other organizations. Style guides serve different purposes depending on who is using them and how, so simply diving into someone else’s work could actually contribute more confusion.

Known unknowns

Sometimes you don’t know what the content will be. That means CMS stuff, but also logic. What if you have a list of bands and events and you are building a module full of conditional components? There might be one band… or five… or two co-headliners. The event might be cancelled!

When I was trying to work out some templating ideas for Ticketfly, I separated the concerns of layout and type patterns.

Variable sized font patterns

Some patterns change sizes at various breakpoints.

@mixin attention-voice() {   font-family: Serif;   font-size: 20px;   line-height: 1.4;   letter-spacing: 0.02em;   @media (min-width: 700px) {     font-size: 24px;   }   @media (min-width: 1100px) {     font-size: 30px;   } }

I used to do something like this and it had a few yucky side effects. For example, what if you plug and play based on the breakpoints and there are sizes that conflict or slip through?

clamp() and vmin units to the rescue!

@mixin attention-voice() {   font-family: Serif;   font-size: clamp(18px, 10vmin, 100px);   line-height: 1.4;   letter-spacing: 0.02em; }

Now, in theory, you could even jump between the patterns with no rub.

.context {   @include calm-voice();   @media (min-width: 840px) {     @include some-other-voice();   } }

But now you have to make sure that they both have the same properties and that they don’t conflict with or bleed through to others! And think about the new variable font options too. Sometimes you’ll want your heading to be a little less heavy on smaller screens or a little taller to work with the space.

Aren’t you duplicating style rules all over the place?

Yikes! You caught me. I care way more about making the authoring and maintaining process pleasurable than I care about CSS byte size. I’m conflicted though. I get it. But I also think that the solution should happen somewhere else in the pipeline. There’s a reason that we don’t write machine code by hand.

Sometimes you have to examine a programming pattern closely and really poke at it, trying it in every place you can to prove how useful it is. Humor me for a movement and look at how you’d use this type pattern and other mixins to style a common “card” interface.

In a way, type patterns are like the utility class style of Bootstrap or Tailwind. But these are human readable. The patterns are added in the CSS instead of the HTML. The concept is the same. I like to think that anyone could look at the living style guide and jump into a component and style it. What do you think?

It is creating more CSS though. Those kilobytes are stacking up. But I think we should work towards something ideal instead of just “possible.” We can probably build something that works this out at build time. I’ll have to learn more about the CSSOM and there are many new things coming down the pipeline that could make this possible without a preprocessor.

It’s bigger than just the CSS. It’s about the people.

Having a set of patterns for type in your project allows visual designers to focus on their magic. More and more, we are building fast and in the browser. Visual designers focus on feel and typography and color with simple frameworks, like Style Tiles. Then developers can organize the data, resource structures and layouts, and everyone can work at the same time. We can have clearer communication and shared understanding of the entire process. We are all UX designers.

When living style guides are created as a team, there’s a lot less need for pixel-pushing. Visual designers can actually spend more time thinking and trying ideas in prototypes, and less time mocking out unnecessary production art. This allows you to work on your brand as a whole and have one single source of truth. It even helps with really small sites, like this.

Gold Collective style guide

InDesign and Illustrator have always had “paragraph styles” and “character styles” for this reason, but they don’t take into account variable screen sizes.

Toss in a few padding type sizes/ratios, some colors and some line widths. It doesn’t have to really be “pixel perfect” but more of a collection of patterns that work together. Colors as variables and maybe some $ thick, $ thin, or $ pad*2 type conventions can help streamline design patterns.

You can find your inspiration in the graphics program, then jump straight to the live style guide. People of all backgrounds can start playing with the styles in a CodePen and dial them in across devices.

In the end, you’ll decide the details on real devices — together, as a team.


The post On Type Patterns and Style Guides appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

CSS Background Patterns

Nice little tool from Jim Raptis: CSS Background Patterns. A bunch of easy-to-customize and copy-and-paste backgrounds that use hard stop CSS gradients to make classy patterns. Not quite as flexible as SVG backgrounds, but just as lightweight.

Like this:

Speaking of cool background gradient tricks, check out our Complete Guide to CSS Gradients that just went out today!

Direct Link to ArticlePermalink


The post CSS Background Patterns appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

,
[Top]