Tag: Shapes

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

, , ,

CSS Grid and Custom Shapes, Part 2

Alright, so the last time we checked in, we were using CSS Grid and combining them with CSS clip-path and mask techniques to create grids with fancy shapes.

Here’s just one of the fantastic grids we made together:

Ready for the second round? We are still working with CSS Grid, clip-path, and mask, but by the end of this article, we’ll end up with different ways to arrange images on the grid, including some rad hover effects that make for an authentic, interactive experience to view pictures.

And guess what? We’re using the same markup that we used last time. Here’s that again:

<div class="gallery">   <img src="..." alt="...">   <img src="..." alt="...">   <img src="..." alt="...">   <img src="..." alt="...">   <!-- as many times as we want --> </div>

Like the previous article, we only need a container with images inside. Nothing more!

Nested Image Grid

Last time, our grids were, well, typical image grids. Other than the neat shapes we masked them with, they were pretty standard symmetrical grids as far as how we positioned the images inside.

Let’s try nesting an image in the center of the grid:

We start by setting a 2✕2 grid for four images:

.gallery {   --s: 200px; /* controls the image size */   --g: 10px; /* controls the gap between images */    display: grid;   gap: var(--g);   grid-template-columns: repeat(2, auto); } .gallery > img {   width: var(--s);   aspect-ratio: 1;   object-fit: cover; }

Nothing complex yet. The next step is to cut the corner of our images to create the space for the nested image. I already have a detailed article on how to cut corners using clip-path and mask. You can also use my online generator to get the CSS for masking corners.

What we need here is to cut out the corners at an angle equal to 90deg. We can use the same conic-gradient technique from that article to do that:

.gallery > img {    mask: conic-gradient(from var(--_a), #0000 90deg, #000 0); } .gallery > img:nth-child(1) { --_a: 90deg; } .gallery > img:nth-child(2) { --_a: 180deg; } .gallery > img:nth-child(3) { --_a: 0deg; } .gallery > img:nth-child(4) { --_a:-90deg; }

We could use the clip-path method for cutting corners from that same article, but masking with gradients is more suitable here because we have the same configuration for all the images — all we need is a rotation (defined with the variable --_a) get the effect, so we’re masking from the inside instead of the outside edges.

Two by two grid of images with a white square stacked on top in the center.

Now we can place the nested image inside the masked space. First, let’s make sure we have a fifth image element in the HTML:

<div class="gallery">   <img src="..." alt="...">   <img src="..." alt="...">   <img src="..." alt="...">   <img src="..." alt="...">   <img src="..." alt="..."> </div>

We are going to rely on the good ol’ absolute positioning to place it in there:

.gallery > img:nth-child(5) {   position: absolute;   inset: calc(50% - .5*var(--s));   clip-path: inset(calc(var(--g) / 4)); }

The inset property allows us to place the image at the center using a single declaration. We know the size of the image (defined with the variable --s), and we know that the container’s size equals 100%. We do some math, and the distance from each edge should be equal to (100% - var(--s))/2.

Diagram of the widths needed to complete the design.

You might be wondering why we’re using clip-path at all here. We’re using it with the nested image to have a consistent gap. If we were to remove it, you would notice that we don’t have the same gap between all the images. This way, we’re cutting a little bit from the fifth image to get the proper spacing around it.

The complete code again:

.gallery {   --s: 200px; /* controls the image size */   --g: 10px;  /* controls the gap between images */      display: grid;   gap: var(--g);   grid-template-columns: repeat(2, auto);   position: relative; }  .gallery > img {   width: var(--s);   aspect-ratio: 1;   object-fit: cover;   mask: conic-gradient(from var(--_a), #0000 90deg, #000 0); }  .gallery > img:nth-child(1) {--_a: 90deg} .gallery > img:nth-child(2) {--_a:180deg} .gallery > img:nth-child(3) {--_a:  0deg} .gallery > img:nth-child(4) {--_a:-90deg} .gallery > img:nth-child(5) {   position: absolute;   inset: calc(50% - .5*var(--s));   clip-path: inset(calc(var(--g) / 4)); }

Now, many of you might also be wondering: why all the complex stuff when we can place the last image on the top and add a border to it? That would hide the images underneath the nested image without a mask, right?

That’s true, and we will get the following:

No mask, no clip-path. Yes, the code is easy to understand, but there is a little drawback: the border color needs to be the same as the main background to make the illusion perfect. This little drawback is enough for me to make the code more complex in exchange for real transparency independent of the background. I am not saying a border approach is bad or wrong. I would recommend it in most cases where the background is known. But we are here to explore new stuff and, most important, build components that don’t depend on their environment.

Let’s try another shape this time:

This time, we made the nested image a circle instead of a square. That’s an easy task with border-radius But we need to use a circular cut-out for the other images. This time, though, we will rely on a radial-gradient() instead of a conic-gradient() to get that nice rounded look.

.gallery > img {   mask:      radial-gradient(farthest-side at var(--_a),       #0000 calc(50% + var(--g)/2), #000 calc(51% + var(--g)/2)); } .gallery > img:nth-child(1) { --_a: calc(100% + var(--g)/2) calc(100% + var(--g)/2); } .gallery > img:nth-child(2) { --_a: calc(0%   - var(--g)/2) calc(100% + var(--g)/2); } .gallery > img:nth-child(3) { --_a: calc(100% + var(--g)/2) calc(0%   - var(--g)/2); } .gallery > img:nth-child(4) { --_a: calc(0%   - var(--g)/2) calc(0%   - var(--g)/2); }

All the images use the same configuration as the previous example, but we update the center point each time.

Diagram showing the center values for each quadrant of the grid.

The above figure illustrates the center point for each circle. Still, in the actual code, you will notice that I am also accounting for the gap to ensure all the points are at the same position (the center of the grid) to get a continuous circle if we combine them.

Now that we have our layout let’s talk about the hover effect. In case you didn’t notice, a cool hover effect increases the size of the nested image and adjusts everything else accordingly. Increasing the size is a relatively easy task, but updating the gradient is more complicated since, by default, gradients cannot be animated. To overcome this, I will use a font-size hack to be able to animate the radial gradient.

If you check the code of the gradient, you can see that I am adding 1em:

mask:      radial-gradient(farthest-side at var(--_a),       #0000 calc(50% + var(--g)/2 + 1em), #000 calc(51% + var(--g)/2 + 1em));

It’s known that em units are relative to the parent element’s font-size, so changing the font-size of the .gallery will also change the computed em value — this is the trick we are using. We are animating the font-size from a value of 0 to a given value and, as a result, the gradient is animated, making the cut-out part larger, following the size of the nested image that is getting bigger.

Here is the code that highlights the parts involved in the hover effect:

.gallery {   --s: 200px; /* controls the image size */   --g: 10px; /* controls the gaps between images */    font-size: 0; /* initially we have 1em = 0 */   transition: .5s; } /* we increase the cut-out by 1em */ .gallery > img {   mask:      radial-gradient(farthest-side at var(--_a),       #0000 calc(50% + var(--g)/2 + 1em), #000 calc(51% + var(--g)/2 + 1em)); } /* we increase the size by 2em */ .gallery > img:nth-child(5) {   width: calc(var(--s) + 2em); } /* on hover 1em = S/5 */ .gallery:hover {   font-size: calc(var(--s) / 5); }

The font-size trick is helpful if we want to animate gradients or other properties that cannot be animated. Custom properties defined with @property can solve such a problem, but support for it is still lacking at the time of writing.

I discovered the font-size trick from @SelenIT2 while trying to solve a challenge on Twitter.

Another shape? Let’s go!

This time we clipped the nested image into the shape of a rhombus. I’ll let you dissect the code as an exercise to figure out how we got here. You will notice that the structure is the same as in our examples. The only differences are how we’re using the gradient to create the shape. Dig in and learn!

Circular Image Grid

We can combine what we’ve learned here and in previous articles to make an even more exciting image grid. This time, let’s make all the images in our grid circular and, on hover, expand an image to reveal the entire thing as it covers the rest of the photos.

The HTML and CSS structure of the grid is nothing new from before, so let’s skip that part and focus instead on the circular shape and hover effect we want.

We are going to use clip-path and its circle() function to — you guessed it! — cut a circle out of the images.

Showing the two states of an image, the natural state on the left, and the hovered state on the right, including the clip-path values to create them.

That figure illustrates the clip-path used for the first image. The left side shows the image’s initial state, while the right shows the hovered state. You can use this online tool to play and visualize the clip-path values.

For the other images, we can update the center of the circle (70% 70%) to get the following code:

.gallery > img:hover {   --_c: 50%; /* same as "50% at 50% 50%" */ } .gallery > img:nth-child(1) {   clip-path: circle(var(--_c, 55% at 70% 70%)); } .gallery > img:nth-child(2) {   clip-path: circle(var(--_c, 55% at 30% 70%)); } .gallery > img:nth-child(3) {   clip-path: circle(var(--_c, 55% at 70% 30%)); } .gallery > img:nth-child(4) {   clip-path: circle(var(--_c, 55% at 30% 30%)); }

Note how we are defining the clip-path values as a fallback inside var(). This way allows us to more easily update the value on hover by setting the value of the --_c variable. When using circle(), the default position of the center point is 50% 50%, so we get to omit that for more concise code. That’s why you see that we are only setting 50% instead of 50% at 50% 50%.

Then we increase the size of our image on hover to the overall size of the grid so we can cover the other images. We also ensure the z-index has a higher value on the hovered image, so it is the top one in our stacking context.

.gallery {   --s: 200px; /* controls the image size */   --g: 8px;   /* controls the gap between images */    display: grid;   grid: auto-flow var(--s) / repeat(2, var(--s));   gap: var(--g); }  .gallery > img {   width: 100%;    aspect-ratio: 1;   cursor: pointer;   z-index: 0;   transition: .25s, z-index 0s .25s; } .gallery > img:hover {   --_c: 50%; /* change the center point on hover */   width: calc(200% + var(--g));   z-index: 1;   transition: .4s, z-index 0s; }  .gallery > img:nth-child(1){   clip-path: circle(var(--_c, 55% at 70% 70%));   place-self: start; } .gallery > img:nth-child(2){   clip-path: circle(var(--_c, 55% at 30% 70%));   place-self: start end; } .gallery > img:nth-child(3){   clip-path: circle(var(--_c, 55% at 70% 30%));   place-self: end start; } .gallery > img:nth-child(4){   clip-path: circle(var(--_c, 55% at 30% 30%));   place-self: end; }

What’s going on with the place-self property? Why do we need it and why does each image have a specific value?

Do you remember the issue we had in the previous article when creating the grid of puzzle pieces? We increased the size of the images to create an overflow, but the overflow of some images was incorrect. We fixed them using the place-self property.

Same issue here. We are increasing the size of the images so each one overflows its grid cells. But if we do nothing, all of them will overflow on the right and bottom sides of the grid. What we need is:

  1. the first image to overflow the bottom-right edge (the default behavior),
  2. the second image to overflow the bottom-left edge,
  3. the third image to overflow the top-right edge, and
  4. the fourth image to overflow the top-left edge.

To get that, we need to place each image correctly using the place-self property.

Diagram showing the place-self property values for each quadrant of the grid.

In case you are not familiar with place-self, it’s the shorthand for justify-self and align-self to place the element horizontally and vertically. When it takes one value, both alignments use that same value.

Expanding Image Panels

In a previous article, I created a cool zoom effect that applies to a grid of images where we can control everything: number of rows, number of columns, sizes, scale factor, etc.

A particular case was the classic expanding panels, where we only have one row and a full-width container.

We will take this example and combine it with shapes!

Before we continue, I highly recommend reading my other article to understand how the tricks we’re about to cover work. Check that out, and we’ll continue here to focus on creating the panel shapes.

First, let’s start by simplifying the code and removing some variables

We only need one row and the number of columns should adjust based on the number of images. That means we no longer need variables for the number of rows (--n) and columns (--m ) but we need to use grid-auto-flow: column, allowing the grid to auto-generate columns as we add new images. We will consider a fixed height for our container; by default, it will be full-width.

Let’s clip the images into a slanted shape:

A headshot of a calm red wolf looking downward with vertices overlayed showing the clip-path property points.
clip-path: polygon(S 0%, 100% 0%, (100% - S) 100%, 0% 100%);

Once again, each image is contained in its grid cell, so there’s more space between the images than we’d like:

A six-panel grid of slanted images of various wild animals showing the grid lines and gaps.

We need to increase the width of the images to create an overlap. We replace min-width: 100% with min-width: calc(100% + var(--s)), where --s is a new variable that controls the shape.

Now we need to fix the first and last images, so they sort of bleed off the page without gaps. In other words, we can remove the slant from the left side of the first image and the slant from the right side of the last image. We need a new clip-path specifically for those two images.

We also need to rectify the overflow. By default, all the images will overflow on both sides, but for the first one, we need an overflow on the right side while we need a left overflow for the last image.

.gallery > img:first-child {   min-width: calc(100% + var(--s)/2);   place-self: start;   clip-path: polygon(0 0,100% 0,calc(100% - var(--s)) 100%,0 100%); } .gallery > img:last-child {   min-width: calc(100% + var(--s)/2);   place-self: end;   clip-path: polygon(var(--s) 0,100% 0,100% 100%,0 100%); }

The final result is a nice expanding panel of slanted images!

We can add as many images as you want, and the grid will adjust automatically. Plus, we only need to control one value to control the shape!

We could have made this same layout with flexbox since we are dealing with a single row of elements. Here is my implementation.

Sure, slanted images are cool, but what about a zig-zag pattern? I already teased this one at the end of the last article.

All I’m doing here is replacing clip-path with mask… and guess what? I already have a detailed article on creating that zig-zag shape — not to mention an online generator to get the code. See how all everything comes together?

The trickiest part here is to make sure the zig-zags are perfectly aligned, and for this, we need to add an offset for every :nth-child(odd) image element.

.gallery > img {   mask:      conic-gradient(from -135deg at right, #0000, #000 1deg 89deg, #0000 90deg)        100% calc(50% + var(--_p, 0%))/51% calc(2*var(--s)) repeat-y,     conic-gradient(from   45deg at left,  #0000, #000 1deg 89deg, #0000 90deg)        0%   calc(50% + var(--_p, 0%))/51% calc(2*var(--s)) repeat-y; } /* we add an offset to the odd elements */ .gallery > img:nth-child(odd) {   --_p: var(--s); } .gallery > img:first-child {   mask:      conic-gradient(from -135deg at right, #0000, #000 1deg 89deg, #0000 90deg)        0 calc(50% + var(--_p, 0%))/100% calc(2*var(--s)); } .gallery > img:last-child {   mask:      conic-gradient(from 45deg at left, #0000, #000 1deg 89deg, #0000 90deg)        0 calc(50% + var(--_p, 0%)) /100% calc(2*var(--s)); }

Note the use of the --_p variable, which will fall back to 0% but will be equal to --_s for the odd images.

Here is a demo that illustrates the issue. Hover to see how the offset — defined by --_p — is fixing the alignment.

Also, notice how we use a different mask for the first and last image as we did in the previous example. We only need a zig-zag on the right side of the first image and the left side of the last image.

And why not rounded sides? Let’s do it!

I know that the code may look scary and tough to understand, but all that’s going on is a combination of different tricks we’ve covered in this and other articles I’ve already shared. In this case, I use the same code structure as the zig-zag and the slanted shapes. Compare it with those examples, and you will find no difference! Those are the same tricks in my previous article about the zoom effect. Then, I am using my other writing and my online generator to get the code for the mask that creates those rounded shapes.

If you recall what we did for the zig-zag, we had used the same mask for all the images but then had to add an offset to the odd images to create a perfect overlap. In this case, we need a different mask for the odd-numbered images.

The first mask:

mask:    linear-gradient(-90deg,#0000 calc(2*var(--s)),#000 0) var(--s),   radial-gradient(var(--s),#000 98%,#0000) 50% / calc(2*var(--s)) calc(1.8*var(--s)) space repeat;

The second one:

mask:   radial-gradient(calc(var(--s) + var(--g)) at calc(var(--s) + var(--g)) 50%,#0000 98% ,#000)    calc(50% - var(--s) - var(--g)) / 100% calc(1.8*var(--s))

The only effort I did here is update the second mask to include the gap variable (--g) to create that space between the images.

The final touch is to fix the first and last image. Like all the previous examples, the first image needs a straight left edge while the last one needs a straight right edge.

For the first image, we always know the mask it needs to have, which is the following:

.gallery > img:first-child {   mask:      radial-gradient(calc(var(--s) + var(--g)) at right, #0000 98%, #000) 50% / 100% calc(1.8 * var(--s)); }
A brown bear headshot with a wavy pattern for the right border.

For the last image, it depends on the number of elements, so it matters if that element is :nth-child(odd) or :nth-child(even).

The complete grid of wild animal photos with all of the correct borders and gaps between images.
.gallery > img:last-child:nth-child(even) {   mask:      linear-gradient(to right,#0000 var(--s),#000 0),     radial-gradient(var(--s),#000 98%,#0000) left / calc(2*var(--s)) calc(1.8*var(--s)) repeat-y }
A single-row grid of three wild animal photos with wavy borders where the last image is an odd-numbered element.
.gallery > img:last-child:nth-child(odd) {   mask:      radial-gradient(calc(var(--s) + var(--g)) at left,#0000 98%,#000) 50% / 100% calc(1.8*var(--s)) }

That’s all! Three different layouts but the same CSS tricks each time:

  • the code structure to create the zoom effect
  • a mask or clip-path to create the shapes
  • a separate configuration for the odd elements in some cases to make sure we have a perfect overlap
  • a specific configuration for the first and last image to keep the shape on only one side.

And here is a big demo with all of them together. All you need is to add a class to activate the layout you want to see.

And here is the one with the Flexbox implementation

Wrapping up

Oof, we are done! I know there are many CSS tricks and examples between this article and the last one, not to mention all of the other tricks I’ve referenced here from other articles I’ve written. It took me time to put everything together, and you don’t have to understand everything at once. One reading will give you a good overview of all the layouts, but you may need to read the article more than once and focus on each example to grasp all the tricks.

Did you notice that we didn’t touch the HTML at all other than perhaps the number of images in the markup? All the layouts we made share the same HTML code, which is nothing but a list of images.

Before I end, I will leave you with one last example. It’s a “versus” between two anime characters with a cool hover effect.

What about you? Can you create something based on what you have learned? It doesn’t need to be complex — imagine something cool or funny like I did with that anime matchup. It can be a good exercise for you, and we may end with an excellent collection in the comment section.


CSS Grid and Custom Shapes, Part 2 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,
[Top]

CSS Grid and Custom Shapes, Part 1

In a previous article, I looked at CSS Grid’s ability to create complex layouts using its auto-placement powers. I took that one step further in another article that added a zooming hover effect to images in a grid layout. This time, I want to dive into another type of grid, one that works with shapes.

Like, what if the images aren’t perfectly square but instead are shaped like hexagons or rhombuses? Spoiler alert: we can do it. In fact, we’re going to combine CSS Grid techniques we’ve looked at and drop in some CSS clip-path and mask magic to create fancy grids of images for just about any shape you can imagine!

Let’s start with some markup

Most of the layouts we are going to look at may look easy to achieve at first glance, but the challenging part is to achieve them with the same HTML markup. We can use a lot of wrappers, divs, and whatnot, but the goal of this post is to use the same and smallest amount of HTML code and still get all the different grids we want. After all, what’s CSS but a way to separate styling and markup? Our styling should not depend on the markup, and vice versa.

This said, let’s start with this:

<div class="gallery">   <img src="..." alt="...">   <img src="..." alt="...">   <img src="..." alt="...">   <img src="..." alt="...">   <!-- as many times as we want --> </div>

A container with images is all that we need here. Nothing more!

CSS Grid of Hexagons

This is also sometimes referred to as a “honeycomb” grid.

There are already plenty of other blog posts out there that show how to make this. Heck, I wrote one here on CSS-Tricks! That article is still good and goes way deep on making a responsive layout. But for this specific case, we are going to rely on a much simpler CSS approach.

First, let’s use clip-path on the images to create the hexagon shape and we place all of them in the same grid area so they overlap.

.gallery {   --s: 150px; /* controls the size */   display: grid; }  .gallery > img {   grid-area: 1/1;   width: var(--s);   aspect-ratio: 1.15;   object-fit: cover;   clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0 50%); }
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0 50%)

Nothing fancy yet. All the images are hexagons and above each other. So it looks like all we have is a single hexagon-shaped image element, but there are really seven.

The next step is to apply a translation to the images to correctly place them on the grid.

Notice that we still want one of the images to remain in the center. The rest are placed around it using CSS translate and good ol’ fashioned geometry. Here’s are the mock formulas I came up with for each image in the grid:

translate((height + gap)*sin(0deg), (height + gap)*cos(0)) translate((height + gap)*sin(60deg), (height + gap)*cos(60deg)) translate((height + gap)*sin(120deg), (height + gap)*cos(120deg)) translate((height + gap)*sin(180deg), (height + gap)*cos(180deg)) translate((height + gap)*sin(240deg), (height + gap)*cos(240deg)) translate((height + gap)*sin(300deg), (height + gap)*cos(300deg))

A few calculations and optimization later (let’s skip that boring part, right?) we get the following CSS:

.gallery {   --s: 150px; /* control the size */   --g: 10px;  /* control the gap */   display: grid; } .gallery > img {   grid-area: 1/1;   width: var(--s);   aspect-ratio: 1.15;   object-fit: cover;   clip-path: polygon(25% 0%, 75% 0%, 100% 50% ,75% 100%, 25% 100%, 0 50%);   transform: translate(var(--_x,0), var(--_y,0)); } .gallery > img:nth-child(1) { --_y: calc(-100% - var(--g)); } .gallery > img:nth-child(7) { --_y: calc( 100% + var(--g)); } .gallery > img:nth-child(3), .gallery > img:nth-child(5) { --_x: calc(-75% - .87*var(--g)); } .gallery > img:nth-child(4), .gallery > img:nth-child(6) { --_x: calc( 75% + .87*var(--g)); } .gallery > img:nth-child(3), .gallery > img:nth-child(4) { --_y: calc(-50% - .5*var(--g)); } .gallery > img:nth-child(5),  .gallery > img:nth-child(6) { --_y: calc( 50% + .5*var(--g)); }

Maybe that’ll be easier when we get real trigonometry functions in CSS!

Each image is translated by the --_x and --_y variables that are based on those formulas. Only the second image (nth-child(2)) is undefined in any selector because it’s the one in the center. It can be any image if you decide to use a different order. Here’s the order I’m using:

With only a few lines of code, we get a cool grid of images. To this, I added a little hover effect to the images to make things fancier.

Guess what? We can get another hexagon grid by simply updating a few values.

If you check the code and compare it with the previous one you will notice that I have simply swapped the values inside clip-path and I switched between --x and --y. That’s all!

CSS Grid of Rhombuses

Rhombus is such a fancy word for a square that’s rotated 45 degrees.

Same HTML, remember? We first start by defining a 2×2 grid of images in CSS:

.gallery {   --s: 150px; /* controls the size */    display: grid;   gap: 10px;   grid: auto-flow var(--s) / repeat(2, var(--s));   place-items: center; } .gallery > img {   width: 100%;    aspect-ratio: 1;   object-fit: cover; }

The first thing that might catch your eye is the grid property. It’s pretty uncommonly used but is super helpful in that it’s a shorthand that lets you define a complete grid in one declaration. It’s not the most intuitive — and not to mention readable — property, but we are here to learn and discover new things, so let’s use it rather than writing out all of the individual grid properties.

grid: auto-flow var(--s) / repeat(2,var(--s));  /* is equivalent to this: */ grid-template-columns: repeat(2, var(--s)); grid-auto-rows: var(--s);

This defines two columns equal to the --s variable and sets the height of all the rows to --s as well. Since we have four images, we will automatically get a 2×2 grid.

Here’s another way we could have written it:

grid-template-columns: repeat(2, var(--s)); grid-template-rows: repeat(2, var(--s));

…which can be reduced with the grid shorthand:

grid: repeat(2,var(--s)) / repeat(2,var(--s));

After setting the grid, we rotate it and the images with CSS transforms and we get this:

Note how I rotate them both by 45deg, but in the opposite direction.

.gallery {   /* etc. */   transform: rotate(45deg); } .gallery > img {   /* etc. */   transform: rotate(-45deg); }

Rotating the images in the negative direction prevents them from getting rotated with the grid so they stay straight. Now, we apply a clip-path to clip a rhombus shape out of them.

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

We are almost done! We need to rectify the size of the image to make them fit together. Otherwise, they’re spaced far apart to the point where it doesn’t look like a grid of images.

The image is within the boundary of the green circle, which is the inscribed circle of the grid area where the image is placed. What we want is to make the image bigger to fit inside the red circle, which is the circumscribed circle of the grid area.

Don’t worry, I won’t introduce any more boring geometry. All you need to know is that the relationship between the radius of each circle is the square root of 2 (sqrt(2)). This is the value we need to increase the size of our images to fill the area. We will use 100%*sqrt(2) = 141% and be done!

.gallery {   --s: 150px; /* control the size */    display: grid;   grid: auto-flow var(--s) / repeat(2,var(--s));   gap: 10px;   place-items: center;   transform: rotate(45deg); } .gallery > img {   width: 141%; /* 100%*sqrt(2) = 141% */   aspect-ratio: 1;   object-fit: cover;   transform: rotate(-45deg);   clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }

Like the hexagon grid, we can make things fancier with that nice zooming hover effect:

CSS Grid of Triangular Shapes

You probably know by now that the big trick is figuring out the clip-path to get the shapes we want. For this grid, each element has its own clip-path value whereas the last two grids worked with a consistent shape. So, this time around, it’s like we’re working with a few different triangular shapes that come together to form a rectangular grid of images.

The three images at the top
The three images at the bottom

We place them inside a 3×2 grid with the following CSS:

.gallery {   display: grid;   gap: 10px;    grid-template-columns: auto auto auto; /* 3 columns */   place-items: center; } .gallery > img {   width: 200px; /* controls the size */   aspect-ratio: 1;   object-fit: cover; } /* the clip-path values */ .gallery > img:nth-child(1) { clip-path: polygon(0 0, 50% 0, 100% 100% ,0 100%); } .gallery > img:nth-child(2) { clip-path: polygon(0 0, 100% 0, 50% 100%); } .gallery > img:nth-child(3) { clip-path: polygon(50% 0, 100% 0, 100% 100%, 0 100%); } .gallery > img:nth-child(4) { clip-path: polygon(0 0, 100% 0, 50% 100%, 0 100%); } .gallery > img:nth-child(5) { clip-path: polygon(50% 0, 100% 100%, 0% 100%); } .gallery > img:nth-child(6) { clip-path: polygon(0 0, 100% 0 ,100% 100%, 50% 100%); } }

Here’s what we get:

The final touch is to make the width of the middle column equal 0 to get rid of the spaces between the images. The same sort of spacing problem we had with the rhombus grid, but with a different approach for the shapes we’re using:

grid-template-columns: auto 0 auto;

I had to fiddle with the clip-path values to make sure they would all appear to fit together nicely like a puzzle. The original images overlap when the middle column has zero width, but after slicing the images, the illusion is perfect:

CSS Pizza Pie Grid

Guess what? We can get another cool grid by simply adding border-radius and overflow to our grid or triangular shapes. 🎉

CSS Grid of Puzzle Pieces

This time we are going to play with the CSS mask property to make the images look like pieces of a puzzle.

If you haven’t used mask with CSS gradients, I highly recommend this other article I wrote on the topic because it’ll help with what comes next. Why gradients? Because that’s what we’re using to get the round notches in the puzzle piece shapes.

Setting up the grid should be a cinch by now, so let’s focus instead on the mask part.

As illustrated in the above demo, we need two gradients to create the final shape. One gradient creates a circle (the green part) and the other creates the right curve while filling in the top part.

--g: 6px; /* controls the gap */ --r: 42px;  /* control the circular shapes */  background:    radial-gradient(var(--r) at left 50% bottom var(--r), green 95%, #0000),   radial-gradient(calc(var(--r) + var(--g)) at calc(100% + var(--g)) 50%, #0000 95%, red)   top/100% calc(100% - var(--r)) no-repeat;

Two variables control the shape. The --g variable is nothing but the grid gap. We need to account for the gap to correctly place our circles so they overlap perfectly when the whole puzzle is assembled. The --r variable controls the size of circular parts of the puzzle shape.

Now we take the same CSS and update a few values in it to create the three other shapes:

We have the shapes, but not the overlapping edges we need to make them fit together. Each image is limited to the grid cell it’s in, so it makes sense why the shapes are sort of jumbled at the moment:

We need to create an overflow by increasing the height/width of the images. From the above figure, we have to increase the height of the first and fourth images while we increase the width of the second and third ones. You have probably already guessed that we need to increase them using the --r variable.

.gallery > img:is(:nth-child(1),:nth-child(4)) {   width: 100%;   height: calc(100% + var(--r)); } .gallery > img:is(:nth-child(2),:nth-child(3)) {   height: 100%;   width: calc(100% + var(--r)); }

We are getting closer!

We created the overlap but, by default, our images either overlap on the right (if we increase the width) or the bottom (if we increase the height). But that’s not what we want for the second and fourth images. The fix is to use place-self: end on those two images and our full code becomes this:

Here is another example where I am using a conic gradient instead of a radial gradient. This gives us triangular puzzle pieces while keeping the same underlying HTML and CSS.

A last one! This time I am using clip-path and since it’s a property we can animate, we get a cool hover by simply updating the custom property that controls the shape.

Wrapping up

That’s all for this first part! By combining the things we’ve already learned about CSS Grid with some added clip-path and mask magic, we were able to make grid layouts featuring different kinds of shapes. And we used the same HTML markup each time! And the markup itself is nothing more than a container with a handful of image elements!

In the second part, we are going to explore more complex-looking grids with more fancy shapes and hover effects.

I’m planning to take the demo of expanding image panels we made together in this other article:

…and transform it into a zig-zag image panels! And this is only one example among the many we will discover in the next article.


CSS Grid and Custom Shapes, Part 1 originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , ,
[Top]

Exploring the CSS Paint API: Rounding Shapes

Adding borders to complex shapes is a pain, but rounding the corner of complex shapes is a nightmare! Luckily, the CSS Paint API is here to the rescue! That’s what we’re going to look at as part of this “Exploring the CSS Paint API” series.

Exploring the CSS Paint API series:


Here’s what we’re aiming for. Like everything else we’ve looked at in this series, note that only Chrome and Edge support this for now.

Live Demo

You may have noticed a pattern forming if you’ve followed along with the rest of the articles. In general, when we work with the CSS Paint API:

  • We write some basic CSS that we can easily adjust.
  • All the complex logic is done behind the scene inside the paint() function.

We can actually do this without the Paint API

There are probably a lot of ways to put rounded corners on complex shapes, but I will share with you three methods I’ve used in my own work.

I already hear you saying: If you already know three methods, then why are you using the Paint API? Good question. I’m using it because the three methods I’m aware of are difficult, and two of them are specifically related to SVG. I have nothing against SVG, but a CSS-only solution makes thing easier to maintain, plus it’s easier for someone to walk into and understand when grokking the code.

Onto those three methods…

Using clip-path: path()

If you are an SVG guru, this method is for you. the clip-path property accepts SVG paths. That means we can easily pass in the path for a complex rounded shape and be done. This approach is super easy if you already have the shape you want, but it’s unsuitable if you want an adjustable shape where, for example, you want to adjust the radius.

Below an example of a rounded hexagon shape. Good luck trying to adjust the curvature and the shape size! You’re gonna have to edit that crazy-looking path to do it.

I suppose you could refer to this illustrated guide to SVG paths that Chris put together. But it’s still going to be a lot of work to plot the points and curves just how you want it, even referencing that guide.

Using an SVG filter

I discovered this technique from Lucas Bebber’s post about creating a gooey effect. You can find all the technical details there, but the idea is to apply an SVG filter to any element to round its corners.

We simply use clip-path to create the shape we want then apply the SVG filter on a parent element. To control the radius, we adjust the stdDeviation variable.

This is a good technique, but again, it requires a deep level of SVG know-how to make adjustments on the spot.

Using Ana Tudor’s CSS-only approach

Yes, Ana Tudor found a CSS-only technique for a gooey effect that we can use to round the corner of complex shapes. She’s probably writing an article about it right now. Until then, you can refer to the slides she made where she explain how it works.

Below a demo where I am replacing the SVG filter with her technique:

Again, another neat trick! But as far as being easy to work with? Not so much here, either, especially if we’re considering more complex situations where we need transparency, images, etc. It’s work finding the correct combination of filter, mix-blend-mode and other properties to get things just right.

Using the CSS Paint API instead

Unless you have a killer CSS-only way to put rounded borders on complex shapes that you’re keeping from me (share it already!), you can probably see why I decided to reach for the CSS Paint API.

The logic behind this relies on the same code structure I used in the article covering the polygon border. I’m using the --path variable that defines our shape, the cc() function to convert our points, and a few other tricks we’ll cover along the way. I highly recommend reading that article to better understand what we’re doing here.

First, the CSS setup

We first start with a classic rectangular element and define our shape inside the --path variable (shape 2 above). The --path variable behaves the same way as the path we define inside clip-path: polygon()Use Clippy to generate it. 

.box {   display: inline-block;   height: 200px;   width: 200px;    --path: 50% 0,100% 100%,0 100%;   --radius: 20px;   -webkit-mask: paint(rounded-shape); }

Nothing complex so far. We apply the custom mask and we define both the --path and a --radius variable. The latter will be used to control the curvature.

Next, the JavaScript setup

In addition to the points defined by the path variable (pictured as red points above), we’re adding even more points (pictured as green points above) that are simply the midpoints of each segment of the shape. Then we use the arcTo() function to build the final shape (shape 4 above).

Adding the midpoints is pretty easy, but using arcTo() is a bit tricky because we have to understand how it works. According to MDN:

[It] adds a circular arc to the current sub-path, using the given control points and radius. The arc is automatically connected to the path’s latest point with a straight line, if necessary for the specified parameters.

This method is commonly used for making rounded corners.

The fact that this method requires control points is the main reason for the extra midpoints points. It also require a radius (which we are defining as a variable called --radius).

If we continue reading MDN’s documentation:

One way to think about arcTo() is to imagine two straight segments: one from the starting point to a first control point, and another from there to a second control point. Without arcTo(), these two segments would form a sharp corner: arcTo() creates a circular arc that fits this corner and smooths is out. In other words, the arc is tangential to both segments.

Each arc/corner is built using three points. If you check the figure above, notice that for each corner we have one red point and two green points on each side. Each red-green combination creates one segment to get the two segments detailed above.

Let’s zoom into one corner to better understand what is happening:

We have both segments illustrated in black.
The circle in blue illustrates the radius.

Now imagine that we have a path that goes from the first green point to the next green point, moving around that circle. We do this for each corner and we have our rounded shape.

Here’s how that looks in code:

// We first read the variables for the path and the radius. const points = properties.get('--path').toString().split(','); const r = parseFloat(properties.get('--radius').value);  var Ppoints = []; var Cpoints = []; const w = size.width; const h = size.height; var N = points.length; var i; // Then we loop through the points to create two arrays. for (i = 0; i < N; i++) {   var j = i-1;   if(j<0) j=N-1;      var p = points[i].trim().split(/(?!(.*)s(?![^(]*?))/g);   // One defines the red points (Ppoints)   p = cc(p[0],p[1]);   Ppoints.push([p[0],p[1]]);   var pj = points[j].trim().split(/(?!(.*)s(?![^(]*?))/g);   pj = cc(pj[0],pj[1]);   // The other defines the green points (Cpoints)   Cpoints.push([p[0]-((p[0]-pj[0])/2),p[1]-((p[1]-pj[1])/2)]); }  /* ... */  // Using the arcTo() function to create the shape ctx.beginPath(); ctx.moveTo(Cpoints[0][0],Cpoints[0][1]); for (i = 0; i < (Cpoints.length - 1); i++) {   ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[i+1][0],Cpoints[i+1][1], r); } ctx.arcTo(Ppoints[i][0], Ppoints[i][1], Cpoints[0][0],Cpoints[0][1], r); ctx.closePath();  /* ... */  ctx.fillStyle = '#000'; ctx.fill();

The last step is to fill our shape with a solid color. Now we have our rounded shape and we can use it as a mask on any element.

That’s it! Now all we have to do is to build our shape and control the radius like we want — a radius that we can animate, thanks to @property which will make things more interesting!

Live Demo

Are there any drawbacks with this method?

Yes, there are drawbacks, and you probably noticed them in the last example. The first drawback is related to the hover-able area. Since we are using mask, we can still interact with the initial rectangular shape. Remember, we faced the same issue with the polygon border and we used clip-path to fix it. Unfortunately, clip-path does not help here because it also affects the rounded corner.

Let’s take the last example and add clip-path. Notice how we are losing the “inward” curvature.

There’s no issue with the hexagon and triangle shapes, but the others are missing some curves. It could be an interesting feature to keep only the outward curvature — thanks to clip-path— and at the same time we fix the hover-able area. But we cannot keep all the curvatures and reduce the hover-able area at the same time.

The second issue? It’s related to the use of a big radius value. Hover over the shapes below and see the crazy results we get:

It’s actually not a “major” drawback since we have control over the radius, but it sure would be good to avoid such a situation in case we wrongly use an overly large radius value. We could fix this by limiting the value of the radius to within a range that caps it at a maximum value. For each corner, we calculate the radius that allows us to have the biggest arc without any overflow. I won’t dig into the math logic behind this (😱), but here is the final code to cap the radius value:

var angle =  Math.atan2(Cpoints[i+1][1] - Ppoints[i][1], Cpoints[i+1][0] - Ppoints[i][0]) - Math.atan2(Cpoints[i][1]   - Ppoints[i][1], Cpoints[i][0]   - Ppoints[i][0]); if (angle < 0) {   angle += (2*Math.PI) } if (angle > Math.PI) {   angle = 2*Math.PI - angle } var distance = Math.min(   Math.sqrt(     (Cpoints[i+1][1] - Ppoints[i][1]) ** 2 +      (Cpoints[i+1][0] - Ppoints[i][0]) ** 2),   Math.sqrt(     (Cpoints[i][1] - Ppoints[i][1]) ** 2 +      (Cpoints[i][0] - Ppoints[i][0]) ** 2)   ); var rr = Math.min(distance * Math.tan(angle/2),r);

r is the radius we are defining and rr is the radius we’re actually using. It equal either to r or the maximum value allowed without overflow.

If you hover the shapes in that demo, we no longer get strange shapes but the “maximum rounded shape” (I just coined this) instead. Notice that the regular polygons (like the triangle and hexagon) logically have a circle as their “maximum rounded shape” so we can have cool transitions or animations between different shapes.

Can we have borders?

Yes! All we have to do is to use stroke() instead of fill() inside our paint() function. So, instead of using:

ctx.fillStyle = '#000'; ctx.fill();

…we use this:

ctx.lineWidth = b; ctx.strokeStyle = '#000'; ctx.stroke();

This introduces another variable, b, that controls the border’s thickness.

Did you notice that we have some strange overflow? We faced the same issue in the previous article, and that due to how stroke() works. I quoted MDN in that article and will do it again here as well:

Strokes are aligned to the center of a path; in other words, half of the stroke is drawn on the inner side, and half on the outer side.

Again, it’s that “half inner side, half outer side” that’s getting us! In order to fix it, we need to hide the outer side using another mask, the first one where we use the fill(). First, we need to introduce a conditional variable to the paint() function in order to choose if we want to draw the shape or only its border.

Here’s what we have:

if(t==0) {   ctx.fillStyle = '#000';   ctx.fill(); } else {   ctx.lineWidth = 2*b;   ctx.strokeStyle = '#000';   ctx.stroke(); }

Next, we apply the first type of mask (t=0) on the main element, and the second type (t=1) on a pseudo-element. The mask applied on the pseudo-element produces the border (the one with the overflow issue). The mask applied on the main element addresses the overflow issue by hiding the outer part of the border. And if you’re wondering, that’s why we are adding twice the border thickness to lineWidth.

Live Demo

See that? We have perfect rounded shapes as outlines and we can adjust the radius on hover. And can use any kind of background on the shape.

And we did it all with a bit of CSS:

div {   --radius: 5px; /* Defines the radius */   --border: 6px; /* Defines the border thickness */   --path: /* Define your shape here */;   --t: 0; /* The first mask on the main element */      -webkit-mask: paint(rounded-shape);   transition: --radius 1s; } div::before {   content: "";    background: ..; /* Use any background you want */   --t: 1; /* The second mask on the pseudo-element */   -webkit-mask: paint(rounded-shape); /* Remove this if you want the full shape */ } div[class]:hover {   --radius: 80px; /* Transition on hover */ }

Let’s not forget that we can easily introduce dashes using setLineDash() the same way we did in the previous article.

Live Demo

Controlling the radius

In all the examples we’ve looked at, we always consider one radius applied to all the corners of each shape. It would be interesting if we could control the radius of each corner individually, the same way the border-radius property takes up to four values. So let’s extend the --path variable to consider more parameters.

Actually, our path can be expressed as a list of [x y] values. We’ll make a list of [x y r] values where we introduce a third value for the radius. This value isn’t mandatory; if omitted, it falls back to the main radius.

.box {   display: inline-block;   height: 200px;   width: 200px;    --path: 50% 0 10px,100% 100% 5px,0 100%;   --radius: 20px;   -webkit-mask: paint(rounded-shape); }

Above, we have a 10px radius for the first corner, 5px for the second, and since we didn’t specify a value for the third corner, it inherits the 20px defined by the --radius variable.

Here’s our JavaScript for the values:

var Radius = []; // ... var p = points[i].trim().split(/(?!(.*)s(?![^(]*?))/g); if(p[2])   Radius.push(parseInt(p[2])); else   Radius.push(r);

This defines an array that stores the radius of each corner. Then, after splitting the value of each point, we test whether we have a third value (p[2]). If it’s defined, we use it; if not, we use the default radius. Later on, we’re using Radius[i] instead of r.

Live Demo

This minor addition is a nice feature for when we want to disable the radius for a specific corner of the shape. In fact, let’s look at a few different examples next.

More examples!

I made a series of demos using this trick. I recommend setting the radius to 0 to better see the shape and understand how the path is created. Remember that the --path variable behaves the same way as the path we define inside clip-path: polygon(). If you’re looking for a path to play with, try using Clippy to generate one for you.

Example 1: CSS shapes

A lot of fancy shapes can be created using this technique. Here are a few of them done without any extra elements, pseudo-elements, or hack-y code.

Example 2: Speech bubble

In the previous article, we added border to a speech bubble element. Now we can improve it and round the corners using this new method.

If you compare with this example with the original implementation, you may notice the exact same code. I simply made two or three changes to the CSS to use the new Worklet.

Example 3: Frames

Find below some cool frames for your content. No more headaches when we need gradient borders!

Simply play with the --path variable to create your own responsive frame with any coloration your want.

Example 4: Section divider

SVG is no longer needed to create those wavy section dividers that are popular these days.

Notice that the CSS is light and relatively simple. I only updated the path to generate new instances of the divider.

Example 5: Navigation menu

Here’s a classic design pattern that I’m sure many of us have bumped into at some time: How the heck do we invert the radius? You’ve likely seen it in navigation designs.

A slightly different take on it:

Example 6: Gooey effect

If we play with the path values we can reach for some fancy animation.
Below an idea where I am applying a transition to only one value of the path and yet we get a pretty cool effect

This one’s inspired by Ana Tudor’s demo.

Another idea with a different animation

Another example with a more complex animation:

What about a bouncing ball

Example 7: Shape morphing

Playing with big radius values allows us to create cool transitions between different shapes, especially between a circle and a regular polygon.

If we add some border animation, we get “breathing” shapes!

Let’s round this thing up

I hope you’ve enjoyed getting nerdy with the CSS Paint API. Throughout this series, we’ve applied paint() to a bunch of real-life examples where having the API allows us to manipulate elements in a way we’ve never been able to do with CSS — or without resorting to hacks or crazy magic numbers and whatnot. I truly believe the CSS Paint API makes seemingly complicated problems a lot easier to solve in a straightforward way and will be a feature we reach for time and again. That is, when browser support catches up to it.

If you’ve followed along with this series, or even just stumbled into this one article, I’d love to know what you think of the CSS Paint API and how you imagine using it in your work. Are there any current design trends that would benefit from it, like the wavy section dividers? Or blobby designs? Experiment and have fun!

This one’s taken from my previous article


Exploring the CSS Paint API series:


The post Exploring the CSS Paint API: Rounding Shapes appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Using CSS Shapes for Interesting User Controls and Navigation

Straight across or down, that’s the proverbial order for user controls on a screen. Like a list of menu items. But what if we change that to a more fluid layout with bends, curves, and nooks? We can pull it off with just a few lines of code. In the age of modern minimalistic designs, curved layouts for the user controls add just the right amount of pep to a web design.

And coding them couldn’t be more easier, thanks to CSS Shapes.

CSS Shapes (notably, the shape-outside property) is a standard that assigns geometric shapes to float elements. The content then wraps around the floated element along the boundaries of those shapes.

The use cases for this standard are usually showcased as designs for textual, editorial content — where plain text flow along the shapes floating at their sides. However, in this post, in place of just plain text, we use user controls to see how these shapes can breathe some fluid silhouettes into their layouts.

For the first demo, here’s a design that can be used in product pages, where any product-related action controls can be aligned along the shape of the product itself.

<img src="bottle.png"> <div>   <input type="radio" name=blue checked> <br>   <input type="radio" name=blue> <br>   <input type="radio" name=blue> <br>   <input type="radio" name=blue> </div>
img {   height: 600px;   float: left;   shape-outside: url("bottle.png");   filter: brightness(1.5); } input {   -webkit-appearance: none;   appearance: none;   width: 25px;   height: 25px;   margin-left: 20px;   box-sizing: content-box;   border: 10px solid #231714;   border-radius: 50%;   background: linear-gradient(45deg, pink, beige);   cursor: pointer; }

The image of the bottle is floated left and given a shape boundary using the shape-outside property. The image itself is referenced for the shape.

Note: Only images with transparent backgrounds can produce shapes according to the silhouettes of the images.

The default style of the radio buttons is replaced with a custom style. Once the browser applies the shape to the floated image, the radio buttons automatically align themselves along the shape of the bottle.

Like this, we don’t have to bother with individually assigning positions for each radio button to create such a design. Any buttons later added will automatically be aligned with the buttons before them according to the shape of the bottle.

Here’s another example, inspired by the Wikipedia homepage. This is a perfect example of the sort of unconventional main menu layouts we’re looking at.

Screenshot of the Wikipedia home page, displaying the site logo above a world globe made out of puzzle pieces. Links to various languages float around the globe's edge, like English, Spanish, German, in blue. Each link has a light grey count of how many articles are available in each language.

It’s not too crazy to make with shape-outside:

<div>   <img src="earth.png">   <div class=l>     <a href="#">Formation</a><br>     <a href="#">Atmosphere</a><br>     <a href="#">Heat</a><br>     <a href="#">Gravitation</a>   </div> </div> <div>   <img src="earth.png">   <div class=r>     <a href="#">Moon</a><br>     <a href="#">Climate</a><br>     <a href="#">Rotation</a><br>     <a href="#">Orbit</a>   </div> </div>
img {   height: 250px;   float: left;   shape-outside: circle(40%); }  /* stack both sets of menus on the same grid cell */ main > div { grid-area: 1/1; }   /* one set of menus is flipped and moved sideways over the other */ .r { transform: rotatey(180deg) translatex(250px); }  /* links inside the flipped set of menus are rotated back */ .r > a {    display: inline-block;    transform: rotateY(180deg) translateX(-40px);  }  /* hide one of the images */ main > div:nth-of-type(2) img { visibility: hidden; }

An element only ever floats left or right. There’s no center floating element where content wraps around both the sides. In order to achieve the design where links wrap on both the sides of the image, I made two sets of links and flipped one of the sets horizontally. I used the same image with a circle() CSS shape value in both the sets so the shapes match even after the rotation. The text of the links of the flipped set will appear upside down sideways, so it’s rotated back.

Although both the images can sit on top of each other with no visible overflow, it’s best to hide one of them with either opacity or visibility property.

The third example is a bit lively thanks to the use of one of the dynamic HTML elements, <details>. This demo is a good example for designs to show extra information on products and such which by default are hidden to the users.

<img src="diamond.png"> <details>   <summary>Click to know more!</summary>   <ul>     <li>The diamond is now known as the Sancy     <li>It comprises two back-to-back crowns     <li>It's likely of Indian origin   </ul> </details>
img {   height: 200px;   float: left;   shape-outside: url("diamond.png");   shape-margin: 20px; } summary {   background: red;   color: white;   cursor: pointer;   font-weight: bold;   width: 80%;    height: 30px;   line-height: 30px; }

The image is floated left and is given a CSS shape that’s same as the image. The shape-margin property adds margin space around the shape assigned to the floated element. When the <summary> element is clicked, the parent <details> element reveals its content that automatically wraps along the shape of the floated diamond image.

The content of the <details> element doesn’t necessarily have to be a list, like in the demo. Any inline content would wrap along the floated image’s shape.

The final example works with a polygon shape instead of images or simple shapes like circle and ellipse. Polygons give us more angular geometric shapes that can easily be bent by adding just another coordinate in the shape.

<img src="nasa.png"> <div><!-- triangle --></div> <ul>   <li><a href="#">Home</a>   <li><a href="#">Projects</a>   <li><a href="#">Shop</a>   <li><a href="#">Contact</a>   <li><a href="#">Media</a> </ul>
div {   width: 0;   height: 0;   --d:  200px;   /* red triangle */   border-right: var(--d) solid transparent;   border-bottom: var(--d) solid transparent;   border-left: var(--d) solid red;   float: left;   /* triangle CSS shape */   shape-outside: polygon(0 0, var(--d) 0, 0 var(--d)); } ul {   list-style: none;   padding-top: 25px; }

A left-floated red triangle is created using border properties. To create a triangular CSS shape that matches the red triangle, we’re using the polygon function as a value for the shape-outside property. The value for the polygon() function is the three coordinates of the triangle, separated by commas. The links automatically align around the floated triangle, forming a slanted menu layout down the triangle’s hypotenuse.

As you can see, even for a simple diagonal layout of user controls, CSS Shapes do a nice job adding a little pizzazz to a design. Using CSS Shapes is a much better option than rotating a line of user controls — the alignment of the individual controls and text also rotate, creating layout weirdness. By contrast, a CSS Shape simply lays out the individual controls along the provided shape’s boundary.


The post Using CSS Shapes for Interesting User Controls and Navigation appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

How to Create CSS Charts With Interesting Shapes, Glyphs and Emoji

Let’s forego the usual circles and bars we typically see used in charts for more eccentric shapes. With online presentations more and more common today, a quick way to spruce up your web slides and make them stand out is to give the charts a shapely makeover 🪄

I’ll show you how to create charts with interesting shapes using glyphs, CSS shapes, and emojis with minimal effort.

Let’s start with a simple example.

Using glyphs

<div id="chart">   <div id="chart-shape">⬠</div>   <div id="chart-value"></div>  </div>
#chart {   width: 300px;    height: 300px;   display: grid;   background: white; } #chart * {   height: inherit;   grid-area: 1 / 1; }

We first give the chart some dimensions and stack the two div inside it by assigning them to the same grid cell. They can be stacked up using any other way, too — with position property, for instance.

Look at the HTML above one more time. One of the divs has a pentagon symbol — the chart shape we want. I added that symbol using the “Emoji and Symbols” keyboard, though it can also be done with the HTML entity value for pentagon, &#x2B20;, inside the div.

The div with the symbol is then styled with CSS font properties as well as a desired chart color. It’s large enough and centered.

#chart-shape {   font: 300px/300px serif;   text-align: center;    color: limegreen; }

To the second div in the HTML contains a conic gradient background image. The percentage of the gradient represents the visual value of the chart. The same div also has mix-blend-mode: screen;.

#chart-value {   background: conic-gradient(transparent 75%, darkseagreen 75%);   mix-blend-mode: screen; }

The mix-blend-mode property blends colors inside an element with its backdrop. The screen blend mode value causes a lighter blend to come through. A lighter green shows through the portion where the darkseagreen colored part of the conic gradient overlaps with the limegreen colored pentagram, while the rest of the darskseagreen gradient disappears against the white backdrop of the chart.

An alternative to adding the chart shape in the HTML is to add it as another background layer in CSS and use background-blend-mode instead of mix-blend-mode. However, the code for a chart shape inside the CSS can be less legible for a quick glance. So it’s up to you to see where it’ll be easier for you to add the chart shape in: HTML or CSS. You’ve both options.

#chart {   width: 300px;    height: 300px;   background:   url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject width='300px' height='100%'><div xmlns='http://www.w3.org/1999/xhtml' style='font:300px/300px serif;color:limegreen;text-align: center;background:white'>⬠</div></foreignObject></svg>"),   conic-gradient(transparent 75%, darkseagreen 75%);   background-blend-mode: screen; }

The pentagon symbol is added as a background image in addition to the conic gradient. Then, finally, the property-value pair background-blend-mode: screen; kicks in and the result looks same as the previous demo.

The pentagon background image is created by embedding the HTML with the pentagon symbol () into an SVG that is embedded into a data URL.

<!-- Unwrapped SVG code from the Data URL --> <svg xmlns='http://www.w3.org/2000/svg'>   <foreignObject width='300px' height='100%'>     <div xmlns='http://www.w3.org/1999/xhtml'           style='           font:300px/300px serif;           color:limegreen;           text-align: center;           background:white;'>           ⬠     </div>   </foreignObject> </svg>

Which becomes this in CSS:

background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject width='300px' height='100%'><div xmlns='http://www.w3.org/1999/xhtml' style='font:300px/300px serif;color:limegreen;text-align: center;background:white'>⬠</div></foreignObject></svg>");

Using CSS shapes

Next, let’s use CSS shapes in place of symbols. CSS shapes are primarily created with the use of border properties. We have a collection of CSS shapes in our archive for your reference.

Here’s a set of properties that can create a simple triangle shape in an element we’ll later add to the SVG, replacing the symbol:

border: 150px solid white;  border-bottom: 300px solid lime;  border-top: unset;

When combined with the conic gradient and the background blend, the result is:

<div id="chart"></div>
#chart {   width: 300px;   height: 300px;   background:   url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject width='300px' height='100%'><html xmlns='http://www.w3.org/1999/xhtml'><div style='border:150px solid white; border-bottom:300px solid lime; border-top:unset'></div><div style='border:150px solid transparent; border-bottom:300px solid white; border-top:unset; transform:scale(0.8) translateY(-360px);'></div></html></foreignObject></svg>"),   conic-gradient(transparent 75%, darkseagreen 75%);   background-blend-mode: screen; }

To restrict the design to the border, a smaller white triangle was added to the design.

<!-- Unwrapped SVG code from the Data URL --> <svg xmlns='http://www.w3.org/2000/svg'>   <foreignObject width='300px' height='100%'>    <html xmlns='http://www.w3.org/1999/xhtml'>     /* green triangle */     <div style='          border: 150px solid white;           border-bottom: 300px solid lime;           border-top: unset'></div>     /* smaller white triangle */     <div style='          border: 150px solid transparent;           border-bottom: 300px solid white;           border-top: unset;           transform: scale(0.8) translateY(-360px);'></div>    </html>   </foreignObject> </svg>

Which, again, becomes this in CSS:

background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject width='300px' height='100%'><html xmlns='http://www.w3.org/1999/xhtml'><div style='border:150px solid white; border-bottom:300px solid lime; border-top:unset'></div><div style='border:150px solid transparent; border-bottom:300px solid white; border-top:unset; transform:scale(0.8) translateY(-360px);'></div></html></foreignObject></svg>");

Using emojis

Will emojis work with this approach to charts? You bet it will! 🥳

A block-colored emoji is fed into the SVG image the same way the HTML symbols are. The block color of the emoji is created by giving it a transparent color value, followed by adding a desired color as text-shadow. I covered this technique in another post.

<div id="chart"></div>
#chart {   width: 300px;    height: 300px;   background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject width='300px' height='300px'><body style='margin:0;text-align:center;color:transparent;' xmlns='http://www.w3.org/1999/xhtml'><div style='text-shadow: 0 0 limegreen;font:200px/300px serif;background:white;'>🍏</div><div style='text-shadow:0 0 white;font:170px/300px serif;position:relative;top:-300px;'>🍏</div></body></foreignObject></svg>"),   conic-gradient(transparent 64%, darkseagreen 64%);   background-blend-mode: screen; }

Just as with the last demo, a smaller white apple shape is added at the center to create the border design.

<!-- Unwrapped SVG code from the Data URL --> <svg xmlns='http://www.w3.org/2000/svg'>   <foreignObject width='300px' height='300px'>     <body xmlns='http://www.w3.org/1999/xhtml' style='           margin: 0;           text-align: center;           color:transparent;'>        /* green apple shape */        <div style='             text-shadow: 0 0 limegreen;              font-size: 200px;              background: white;'>🍏</div>        /* smaller white apple shape */        <div style='             text-shadow:0 0 white;              font-size: 170px;              position: relative;              top: -300px;'>🍏</div>     </body>   </foreignObject> </svg>

I added the two divs inside the <body> element so the repeating style properties of the divs are declared only once in the body element. The divs will then automatically inherit those properties.

Chris had the idea to animate the conic gradient — its percent value to be specific — using the CSS @property (supported in Chrome at the time of writing this article), and it just has the most beautiful effect on the design. @property is a CSS at-rule that explicitly defines a custom CSS property. In supported browsers, when a custom property is defined using @property it can be animated.

@property --n {   syntax: '<percentage>';   inherits: true;   initial-value: 30%; } #chart {   width: 300px;    height: 300px;   --n: 30%;  /*declaration for browsers with no @property support */   background:      url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject width='300px' height='300px'><body style='margin:0;text-align:center;color:transparent;' xmlns='http://www.w3.org/1999/xhtml'><div style='text-shadow: 0 0 limegreen;font:200px/300px serif;background:white;'>🍏</div><div style='text-shadow:0 0 white;font:170px/300px serif;position:relative;top:-300px;'>🍏</div></body></foreignObject></svg>"),     conic-gradient(transparent var(--n), darkseagreen var(--n));   background-blend-mode: screen;   transition: --n 2s ease-in-out	 } #chart:hover { --n: 70%; }

The chart above will change its value on hover. In Chrome, the change will look animated.

And although it won’t be as smooth as CSS animation, you can also try animating the gradient using JavaScript. The following JavaScript will cause a somewhat similar animating effect as the above when the cursor moves over the chart.

const chart = document.querySelector('#chart') chart.onpointerover = ()=>{   var i = 30,       timer = setInterval(()=> {         if (i < 70)           chart.style.setProperty('--n', i++ + '%')         else clearInterval(timer)       }, 10) } chart.onpointerout = ()=>{   var i = 70,       timer = setInterval(()=> {         if (i >= 30)            chart.style.setProperty('--n', i-- + '%')         else clearInterval(timer)       }, 10) }

When trying your own designs, keep in mind how the different blend modes work. I used the screen blend mode in all my demos just to keep things simple. But with different blend modes, and different backdrop colors, you’ll get varying results. So, I recommend going deeper into blend modes if you haven’t already.

Also, if you want to exclude an element’s color from the final result, try isolation: isolate; on the element — the browser will ignore that backdrop color when applying the blend.

And even though there are all kinds of unusual and quirky shapes we can use in any wild colors we want, always be mindful of the legibility by making the chart value large and clear enough to read.


The post How to Create CSS Charts With Interesting Shapes, Glyphs and Emoji appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , ,
[Top]

How to Add a Double Border to SVG Shapes

Let’s say someone asks you to add a double border to some random geometric SVG shapes. For some reason, you can’t use any graphic editor — they need to be generated at runtime — so you have to solve it with CSS or within the SVG syntax.

Your first question might be: Is there anything like stroke-style: double in SVG? Well, the answer is not yet and it’s not that easy. But I’ll attempt it anyway to see what methods I can uncover. I’ll explore the possibilities of three different basic shapes: circle, rectangle, and polygon. Pointing the ones that can keep a transparent color in the middle of the two lines.

Spoiler alert: all the results have their downsides, at least with CSS and SVG, but let me walk you through my intents.

The simple solutions

These don’t work with all shapes, but they are the easiest of the solutions.

outline and box-shadow

The CSS properties outline and box-shadow only apply to the bounding box of the shape or SVG, and so both are great solutions only for squares and rectangles. They also allow flexible colors using custom properties.

It only takes two lines of CSS with outline, plus it keeps the background color visible through the shape.

  • 🙁 Solution only for one shape.
  • ✅ Simple code
  • ✅ Borders are smooth
  • ✅ Transparent background

box-shadow only needs one line of CSS, but we have to make sure that each shape has its own SVG as we can’t apply box-shadow directly to the shapes. Another thing to consider is that we have to apply the color of the background in the declaration.

  • 🙁 Solution only for one shape
  • ✅ Simple code
  • ✅ Borders are smooth
  • 🙁 No transparent background

SVG gradients

SVG radial gradients only work on circles ☺️. We can directly apply the gradient on the stroke, but it’s better to use variables as we have to declare the colors many times in the code.

  • 🙁 Solution only for one shape
  • ✅ Simple code
  • 🙁 Borders are smooth
  • 🙁 No transparent background

Solutions for all shapes

These will work with all shapes, but the code could become bloated or complex.

filter: drop-shadow()

Finally, one solution for all shapes! We must have each shape in its own <svg> since the filter won’t apply directly to the shapes. We are using one declaration in CSS and have flexible colors using variables. The downside? The borders don’t look very smooth.

  • ✅ One solution for all shapes
  • ✅ Simple code
  • 🙁 Borders look pixelated
  • 🙁 No transparent background

SVG filters

This is a very flexible solution. We can create a filter and add it to the shapes through SVG’s filter attribute. The complicated part here is the filter itself. We’ll need three paintings, one for the outside border, one for the background flood, and the last one to paint the shape on the front. The result looks better than using drop-shadow, but the borders are still pixelated.

  • ✅ One solution for all shapes
  • 🙁 Complex code
  • 🙁 Borders look pixelated
  • 🙁 No transparent background

Reusing shapes

There are a couple of possible options here.

Option 1: Transforms

This solution requires transforms. We place one figure over the other, where the main figure has a fill color and a stroke color, and the other figure has no fill, a red stroke, and is scaled and repositioned to the center. We defined our shapes on the <defs>. The trick is to translate half of the viewBox to the negative space so that, when we scale them, we can do it from the center of the figure.

  • ✅ One solution for all shapes
  • 🙁 Duplicated code
  • ✅ Borders are smooth
  • ✅ Transparent background
Option 2: <use>

I found a clever solution in the www-svg mailing list by Doug Schepers that uses SVG <use>. Again, it requires defining the shapes once and referring to them twice using <use>. This time the main shape has a bigger stroke. The second shape has half the stroke of the main shape, no fill, and a stroke matching the background color.

  • ✅ One solution for all shapes
  • 🙁 Duplicated code
  • ✅ Borders are smooth
  • 🙁 No transparent background

Here are the full results!

Just so you have them all in one place. Let me know it you can think of other possible solutions!

Solution All shapes Simple code Smooth borders Transparent background
outline 🙁
box-shadow 🙁 🙁
SVG gradients 🙁 🙁 🙁
filter: drop-shadow() 🙁 🙁
SVG filters 🙁 🙁 🙁
Reusing shapes:
Tranforms
🙁
Reusing shapes:
<use>
🙁 🙁

The post How to Add a Double Border to SVG Shapes appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

Creating CSS Shapes with Emoji

CSS Shapes is a standard that lets us create geometric shapes over floated elements that cause the inline contents — usually text — around those elements to wrap along the specified shapes.

Such a shaped flow of text looks good in editorial designs or designs that work with text-heavy contents to add some visual relief from the chunks of text.

Here’s an example of CSS Shape in use:

The shape-outside property specifies the shape of a float area using either one of the basic shape functions — circle(), ellipse(), polygon() or inset() — or an image, like this:

Inline content wraps along the right side of a left-floated element, and the left side of a right-floated element.

In this post, we’ll use the concept of CSS Shapes with emoji to create interesting text-wrapping effects. Images are rectangles. Many of the shapes we draw in CSS are also boxy or at least limited to standard shapes. Emoji, on the other hand, offers neat opportunities to break out of the box!

Here’s how we’ll do it: We’ll first create an image out of an emoji, and then float it and apply a CSS Shape to it.

I’ve already covered multiple ways to convert emojis to images in this post on creative background patterns. In that I said I wasn’t able to figure out how to use SVG <text> to do the conversion, but I’ve figured it out now and will show you how in this post.  You don’t need to have read that article for this one to make sense, but it’s there if you want to see it.

Let’s make an emoji image

The three steps we’re using to create an emoji image are:

  • Create an emoji-shaped cutout in SVG
  • Convert the SVG code to a DataURL by URL encoding and prefixing it with data:image/svg+xml
  • Use the DataURL as the url() value of an element’s background-image.

Here’s the SVG code that creates the emoji shaped cutout:

<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'>    <clipPath id='emojiClipPath'>      <text x='0' y='130px' font-size='130px'>🦕</text>    </clipPath>    <text x='0' y='130px' font-size='130px' clip-path='url(#emojiClipPath)'>🦕</text> </svg>

What’s happening here is we’re providing a <text> element with an emoji character for a <clipPath>. A clip path is an outline of a region to be kept visible when that clip path is applied to an element. In our code, that outline is the shape of the emoji character.

Then the emoji’s clip path is referenced by a <text> element carrying the same emoji character, using its clip-path property, creating a cutout in the shape of the emoji.

Now, we convert the SVG code to a DataURL. You can URL encode it by hand or use online tools (like this one!) that can do it for you.

Here’s the resulted DataURL, used as the url() value for the background image of an .emoji element in CSS:

.emoji {   background: url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='0' y='130px'  font-size='130px'>🦕</text> </clipPath> <text x='0' y='130px' font-size='130px' clip-path='url(%23emojiClipPath)'>🦕</text></svg>"); }

If we were to stop here and give the .emoji element dimensions, we’d see our character displayed as a background image:

Now let’s turn this into a CSS Shape

We can do this in two steps:

  • Float the element with the emoji background
  • Use the DataURL as the url() value for the element’s shape-outside property
.emoji {   --image-url: url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='0' y='130px'  font-size='130px'>🦕</text> </clipPath> <text x='0' y='130px'  font-size='130px' clip-path='url(#emojiClipPath)'>🦕</text></svg>");   background: var(--image-url);   float: left;   height: 150px;   shape-outside: var(--image-url);   width: 150px;   margin-left: -6px;  }

We placed the DataURL in a custom property, --image-url, so we can easily refer it in both the background and the shape-outside properties without repeating that big ol’ string of encoded SVG multiple times.

Now, any inline content near the floated .emoji element will flow in the shape of the emoji. We can adjust things even further with margin or shape-margin to add space around the shape.

If you want a color-blocked emoji shape, you can do that by applying the clip path to a <rect> element in the SVG:

<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'>      <clipPath id='emojiClipPath'>          <text x='0' y='130px' font-size='130px'>🦕</text>      </clipPath>      <rect x='0' y='0' fill='green' width='150px' height='150px' clip-path='url(#emojiClipPath)'/>  </svg>

The same technique will work with letters!

Just note that Firefox doesn’t always render the emoji shape. We can work around that by updating the SVG code.

<svg xmlns='http://www.w3.org/2000/svg' width='150px' height='150px'>   <foreignObject width='150px' height='150px'>     <div xmlns='http://www.w3.org/1999/xhtml' style='width:150px;height:150px;line-height:150px;text-align:center;color:transparent;text-shadow: 0 0 black;font-size:130px;'>🧗</div>   </foreignObject> </svg>

This creates a block-colored emoji shape by making the emoji transparent and giving it text-shadow with inline CSS. The <div> containing the emoji and inline CSS style is then inserted into a <foreignObject> element of SVG so the HTML <div> code can be used inside the SVG namespace. The rest of the code in this technique is same as the last one.

Now we need to center the shape

Since CSS Shapes can only be applied to floated elements, the text flows either to the right or left of the element depending on which side it’s floated. To center the element and the shape, we’ll do the following:

  • Split the emoji in half
  • Float the left-half of the emoji to the right, and the right-half to the left
  • Put both sides together!

One caveat to this strategy: if you’re using running sentences in the design, you’ll need to manually align the letters on both sides.

Here’s what we’re aiming to make:

First, we see the HTML for the left and right sides of the design. They are identical.

<div id="design">   <p id="leftSide">A C G T A <!-- more characters --> C G T A C G T A C G T <span class="emoji"></span>A C G <!-- more characters --> C G T </p>   <p id="rightSide">A C G T A <!-- more characters --> C G T A C G T A C G T <span class="emoji"></span>A C G <!-- more characters --> C G T </p> </div>

p#leftSide and p#rightSide inside #design are arranged side-by-side in a grid.

#design {   border-radius: 50%; /* A circle */   box-shadow: 6px 6px 20px silver;   display: grid;    grid: "1fr 1fr"; /* A grid with two columns */   overflow: hidden;   width: 400px; height: 400px; }

Here’s the CSS for the emoji:

span.emoji {   filter: drop-shadow(15px 15px 5px green);   shape-margin: 10px;   width: 75px;    height: 150px; }  /* Left half of the emoji */ p#leftSide>span.emoji {   --image-url:url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='0' y='130px'  font-size='130px'>🦎</text> </clipPath> <rect x='0' y='0' width='150px' height='150px' clip-path='url(%23emojiClipPath)'/></svg>");   background-image: var(--image-url);   float: right;   shape-outside: var(--image-url); }  /* Right half of the emoji */ p#rightSide>span.emoji {   --image-url:url("data:image/svg+xml,<svg width='150px' height='150px' xmlns='http://www.w3.org/2000/svg'> <clipPath id='emojiClipPath'> <text x='-75px' y='130px'  font-size='130px'>🦎</text> </clipPath> <rect x='0' y='0' width='150px' height='150px' clip-path='url(%23emojiClipPath)'/></svg>");   background-image: var(--image-url);   float: left;   shape-outside: var(--image-url); }

The width of the <span> elements that hold the emoji images (span.emoji) is 75px whereas the width of the SVG emoji images is 150px. This automatically crops the image in half when displayed inside the spans.

On the right side of the design, with the left-floated emoji (p#rightSide>span.emoji), we need to move the emoji halfway to the left to show the right-half, so the x value in the <text> in the DataURL is changed to 75px. That’s the only difference in the DataURLs from the left and right sides of the design.

Here’s that result once again:


That’s it! You can try the above method to center any CSS Shape as long as you can split the element up into two and put the halves back together with CSS.


The post Creating CSS Shapes with Emoji appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

How to Simplify SVG Code Using Basic Shapes

There are different ways to work with icons, but the best solution always includes SVG, whether it’s implemented inline or linked up as an image file. That’s because they’re “drawn” in code, making them flexible, adaptable, and scalable in any context.

But when working with SVG, there’s always the chance that they contain a lot of unnecessary code. In some cases, the code for an inline SVG can be long that it makes a document longer to scroll, uncomfortable to work with, and, yes, a little bit heavier than it needs to be.

We can work around this reusing chunks of code with the <use> element or apply native variables to manage our SVG styles from one place. Or, if we’re working in a server-side environment, we can always sprinkle in a little PHP (or the like) to extract the contents of the SVG file instead of dropping it straight in.

That’s all fine, but wouldn’t be great if we could solve this at the file level instead of resorting to code-based approaches? I want to focus on a different perspective: how to make the same figures with less code using basic shapes. This way, we get the benefits of smaller, controllable, and semantic icons in our projects without sacrificing quality or visual changes. I’ll go through different examples that explore the code of commonly used icons and how we can redraw them using some of the easiest SVG shapes we can make.

Here are the icons we’ll be working on:

Showing an close icon in the shape of an x, a clock with the hands pointing at 3 o-clock, and a closed envelope.

Let’s look at the basic shapes we can use to make these that keep the code small and simple.

Psssst! Here is a longer list of simple icons I created on holasvg.com! After this article, you’ll know how to modify them and make them your own.

Simplifying a close icon with the <line> element

This is the code for the “close” or “cross” icon that was downloaded from flaticon.com and built by pixel-perfect:

In this example, everything is happening inside the <path> with lots of commands and parameters in the data attribute (d). What this SVG is doing is tracing the shape from its borders.

A quick demonstration using mavo.io

If you are familiar with Illustrator, this is the equivalent of drawing two separate lines, converting them to shape, then combining both with the pathfinder to create one compound shape.

The <path> element allows us to draw complex shapes, but in this case, we can create the same figure with two lines, while keeping the same appearance:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50" height="50" overflow="visible" stroke="black" stroke-width="10" stroke-linecap="round">    <line x1="0" y1="0" x2="50" y2="50" />    <line x1="50" y1="0" x2="0" y2="50" /> </svg>

We started by defining a viewBox that goes from 0,0 to 50,50. You can choose whatever dimensions you prefer; the SVG will always scale nicely to any width and height you define. To make things easier, in this case, I also defined an inline width and height of 50 units, which avoids extra calculations in the drawing.

To use the <line> element, we declare the coordinates of the line’s first point and the coordinates of its last point. In this specific case, we started from x=0 y=0 and ended at x=50 y=50.

Grid of the coordinate system.

Here’s how that looks in code:

<line x1="0" y1="0" x2="50" y2="50" />

The second line will start from x=50 y=0 and end at x=0 y=50:

<line x1="50" y1="0" x2="0" y2="50" />

An SVG stroke doesn’t have a color by default — that’s why we added the black value on the stroke attribute. We also gave the stroke-width attribute a width of 10 units and the stroke-linecap a round value to replicate those rounded corners of the original design. These attributes were added directly to the <svg> tag so both lines will inherit them.

<svg ... stroke="black" stroke-width="10" stroke-linecap="round" ...>

Now that the stroke is 10 units bigger that its default size of 1 unit, the line might get cropped by the viewBox. We can either move the points 10 units inside the viewBox or add overflow=visible to the styles.

The values that are equal to 0 can be removed, as 0 is the default. That means the two lines end up with two very small lines of code:

<line x2="50" y2="50" /> <line x1="50" y2="50" />

Just by changing a <path> to a <line>, not only did we make a smaller SVG file, but a more semantic and controllable chunk of code that makes any future maintenance much easier. And the visual result is exactly the same as the original.

Same cross, different code.

Simplifying a clock icon with the <circle> and <path> elements

I took this example of a clock icon created by barracuda from The Noun Project:

This shape was also drawn with a <path>, but we also have a lot of namespaces and XML instructions related to the software used and the license of the file that we can delete without affecting the SVG. Can you tell what illustration editor was used to create the icon?

Let’s recreate this one from scratch using a circle and a path with simpler commands. Again, we need to start with a viewBox, this time from 0,0 to 100,100, and with a width and height matching those units.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100" fill="none" stroke="black" stroke-width="10" stroke-linecap="round" stroke-linejoin="round">   <circle cx="50" cy="50" r="40"/>   <path d="M50 25V50 H75" />  </svg>

We keep the same styles as the previous icon inside the <svg> tag. fill is black by default, so we need to explicitly give it a none value to remove it. Otherwise, the circle will have have a solid black fill, obscuring the other shapes.

To draw the <circle> we need to indicate a center point from where the radius will sit. We can achieve that with cx (center x) and cy (center y). Then r (radius) will declare how big our circle will be. In this example, the radius is slightly smaller than the viewBox, so it doesn’t get cropped when the stroke is 10 units wide.

What’s up with all those letters? Check out Chris Coyier’s illustrated guide for a primer on the SVG syntax.

We can use a <path> for the clock hands because it has some very useful and simple commands to draw. Inside the d (data) we must start with the M (move to) command followed by the coordinates from where we’ll start drawing which, in this example, is 50,25 (near the top-center of the circle). 

After the V (vertical) command, we only need one value as we can only move up or down with a negative or positive number. A positive number will go down. The same for H (horizontal) followed by a positive number, 75, that will draw toward the right. All commands are uppercase, so the numbers we choose will be points in the grid. If we decided to use lowercase (relative commands) the numbers will be the amount of units that we move in one direction and not an absolute point in the coordinate system.

Same clock, different code.

Simplifying an envelope icon with the <rect> and <polyline> elements

I drew the envelope icon in Illustrator without expanding the original shapes. Here’s the code that came from the export:

Illustrator offers some SVG options to export the graphic. I chose “Style Elements” in the “CSS Properties” dropdown so I can have a <style> tag that contains classes that I might want to move to a CSS file. But there are different ways to apply the styles in SVG, of course.

We already have basic shapes in this code! I unselected the “Shape to paths” option in Illustrator which helped a lot there. We can optimize this further with SVGOMG to remove the comments, XML instructions, and unnecessary data, like empty elements. From there, we can manually remove other extras, if we need to.

We already have something a little more concise:

<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 310 190" xml:space="preserve">   <style>.st0{fill:none;stroke:#000;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10}   </style><rect x="5" y="5" class="st0" width="300" height="180"/>   <polyline class="st0" points="5 5 155 110 305 5"/> </svg>

We can remove even more stuff without affecting the visual appearance of the envelope, including: 

  • version="1.1" (this has been deprecated since SVG 2)
  • id="Layer_1" (this has no meaning or use)
  • x="0" (this is a default value)
  • y="0" (this is a default value)
  • xml:space="preserve" (this has been deprecated since SVG 2)
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 310 190">   <style>.st0{fill:none;stroke:#000;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10}   </style>   <rect x="5" y="5" class="st0" width="300" height="180"/>   <polyline class="st0" points="5 5 155 110 305 5"/> </svg>

We can move the CSS styles to a separate stylesheet if we really want to get really aggressive.

<rect> needs a starting point from where we’ll extend a width and a height, so let’s use  x="5" and y="5" which is our top-left point. From there, we will create a rectangle that is 300 units wide with a height of 180 units. Just like the clock icon, we’ll use 5,5 as the starting point because we have a 10-unit stroke that will get cropped if the coordinates were located at 0,0.

<polyline> is similar to <line>, but with an infinite amount of points that we define, like pairs of coordinates, one after the other, inside the points attribute, where the first number in the pair will represent x and the second will be y. It’s easier to read the sequence with commas, but those can be replaced with whitespace without having an impact on the result.

Same envelope, different code.

Bonus shapes!

I didn’t include examples of icons that can be simplified with <polygon> and <ellipse> shapes, but here is a quick way to use them.

<polygon> is the same as <polyline>, only this element will always define a closed shape. Here’s an example that comes straight from MDN:

Remember the circle we drew earlier for the clock icon? Replace the r (radius) with rx and ry. Now you have two different values for radius. Here’s another example from MDN:

Wrapping up

We covered a lot here in a short amount of time! While we used examples to demonstrates the process of optimizing SVGs, here’s what I hope you walk away with from this post:

  • Remember that compression starts with how the SVG is drawn in illustration software.
  • Use available tools, like SVOMG, to compress SVG.
  • Remove unnecessary metadata by hand, if necessary.
  • Replace complex paths with basic shapes.
  • <use> is a great way to “inline” SVG as well as for establishing your own library of reusable icons.

How many icons can be created by combining these basic shapes? 

I’m working my list on holasvg.com/icons, I’ll be constantly uploading more icons and features here, and now you know how to easily modified them just by changing a few numbers. Go ahead and make them yours!


The post How to Simplify SVG Code Using Basic Shapes appeared first on CSS-Tricks.

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

CSS-Tricks

, , , ,
[Top]

Creative Background Patterns Using Gradients, CSS Shapes, and Even Emojis

You can create stripes in CSS. That’s all I thought about in terms of CSS background patterns for a long time. There’s nothing wrong with stripes; stripes are cool. They can be customized into wide and narrow bands, criss-crossed into a checked pattern, and played with in other ways using the idea of hard stops. But stripes can be boring, too. Too conventional, out of fashion, and sometimes even unpleasant.

Thankfully, we can conjure up far more background patterns than you can even imagine with CSS, with code that is similar in spirit to stripes.

Background patterns are images repeated across a background. They can be done by referencing an external image, like a PNG file, or can be drawn with CSS, which is traditionally done using CSS gradients. 

Linear gradients (and repeating linear gradients) for instance, are typically used for stripes. But there are other ways to create cool background patterns. Let’s see how we can use gradients in other ways and toss in other things, like CSS shapes and emoji, to spice things up.

Gradient patterns

There are three types of CSS gradients.

Linear (left), radial (center) and conic (right) gradients
  1. linear-gradient(): Colors flow from left-to-right, top-to-bottom, or at any angle you choose in a single direction.
  2. radial-gradient(): Colors start at a single point and emanate outward
  3. conic-gradient(): Similar in concept to radial gradients, but the color stops are placed around the circle rather than emanating from the center point.

I recommend checking out the syntax for all the gradients to thoroughly understand how to start and end a color in a gradient.

Radial gradient patterns

Let’s look at radial gradients first because they give us very useful things: circles and ellipses. Both can be used for patterns that are very interesting and might unlock some ideas for you!

background: radial-gradient(<gradient values>)

Here’s a pattern of repeating watermelons using this technique:

background:  	radial-gradient(circle at 25px 9px, black 2px, transparent 2px),  	radial-gradient(circle at 49px 28px, black 2px, transparent 2px),  	radial-gradient(circle at 38px 1px, black 2px, transparent 2px),  	radial-gradient(circle at 20px 4px, black 2px, transparent 2px),  	radial-gradient(circle at 80px 4px, black 2px, transparent 2px),  	radial-gradient(circle at 50px 10px, black 2px, transparent 2px),  	radial-gradient(circle at 60px 16px, black 2px, transparent 2px),  	radial-gradient(circle at 70px 16px, black 2px, transparent 2px),  	radial-gradient(ellipse at 50px 0, red 33px, lime 33px, lime 38px, transparent 38px)  	white; background-size: 100px 50px;

We start by providing a background size on the element then stack up the gradients inside it. An ellipse forms the green and red parts. Black circles are scattered across to represent the watermelon seeds. 

The first two parameters for a radial gradient function determine whether the gradient shape is a circle or an ellipse and the starting position of the gradient. That’s followed by the gradient color values along with the start and ending positions within the gradient.

Conic gradient patterns

Conic gradients create ray-like shapes. Like linear and radial gradients, conic gradients can be used to create geometric patterns.

background: conic-gradient(<gradient values>)
background:    conic-gradient(yellow 40deg, blue 40deg, blue 45deg, transparent 45deg),    conic-gradient(transparent 135deg, blue 135deg, blue 140deg, transparent 140deg) ; background-size: 60px 60px; background-color: white;

The rub with conic gradient is that it’s not supported in Firefox, at least at the time of writing. It’s always worth keeping an eye out for deeper support.

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
69 No No 79 12.1

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
81 No 81 12.2-12.4

Emoji icon patterns

This is where things begin to get interesting. Rather than just using geometric patterns (as in gradients), we now use the organic shapes of emojis to create background patterns. 🎉 

It starts with emoji icons. 

Solid-color emoji patterns

We can create emoji icons by giving emojis a transparent color and text shadow.

color: transparent; text-shadow: 0 0 black;

Those icons can then be turned into an image that can be used as a background, using SVG.

<svg>   <foreignObject>     <!-- The HTML code with emoji -->   </foreignObject> </svg>

The SVG can then be referred by the background property using data URL

background: url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><!-- SVG code --></svg>");

And, voilá! We get something like this:

background:      url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><foreignObject width=%22100px%22 height=%22100px%22><div xmlns=%22http://www.w3.org/1999/xhtml%22 style=%22color:transparent;text-shadow: 0 0 %23e42100, -2px 2px 0 black;font-size:70px%22>🏄‍♀️</div></foreignObject></svg>"),      white;  background-size: 60px 60px; 

Other than emojis, it’s also possible to draw CSS shapes and use them as patterns. Emojis are less work, though. Just saying. 

Gradient-colored emoji patterns

Instead of using plain emoji icons, we can use gradient emoji icons. To do that, skip the text shadow on the emojis. Add a gradient background behind them and use background-clip to trim the gradient background to the shape of the emojis. 

color: transparent; background: linear-gradient(45deg, blue 20%, fuchsia); background-clip: text; /* Safari requires -webkit prefix */

Then, just as before, use the combination of SVG and data URL to create the background pattern.

Translucent-colored emoji patterns

This is same as using block colored emoji icons. This time, however, we take away the opaqueness of the colors by using rgba() or hsla() values for the text shadow. 

color: transparent; text-shadow: 20px 10px rgba(0, 255, 0, .3),               0 0 red;

SVG-text emoji patterns

We’ve already looked at all the working methods I could think of to create background patterns, but I feel like I should also mention this other technique I tried, which is not as widely supported as I’d hoped.

 I tried placing the emoji in an SVG <text> element instead of the HTML added using <foreignObject>. But I wasn’t able to create a solid shadow behind it in all the browsers.

background:    url("data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%221em%22 font-size=%2270%22 fill=%22transparent%22 style=%22text-shadow: 0 0 %23e42100, -2px 2px 5px black, 0 0 6px white; ;%22>🏄‍♀️</text></svg>") 

Just in case, I tried using CSS and SVG filters for the shadow as well, thinking that might work. It didn’t. I also tried using the stroke attribute, to at least create an outline for the emoji, but that didn’t work, either. 

CSS element() patterns

I didn’t think of SVG when I first thought of converting emoji icons or CSS shapes into background images. I tried CSS element(). It’s a function that directly converts an HTML element into an image that can be referenced and used. I really like this approach, but browser support is a huge caveat, which is why I’m mentioning it here at the end.

Basically, we can drop an element in the HTML like this:

<div id=snake >🐍</div>

…then pass it into the element() function to use like an image on other elements, like this:

background:    -moz-element(#snake), /* Firefox only */   linear-gradient(45deg, transparent 20px, blue 20px, blue 30px, transparent 30px)    white; background-size: 60px 60px; background-color: white;

Now that snake emoji is technically an image that we get to include in the pattern.

Again, browser support is spotty, making this approach super experimental.

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
No 4* No No No

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
No 68* No No

In this method, the original emoji (or any CSS shape for that matter) used for the background pattern needs to render on screen for it to appear in the background pattern as well. To hide that original emoji, I used mix-blend-mode — it sort of masks out the original emoji in the HTML so it doesn’t show up on the page.


I hope you find the methods in this post useful in one way or another and learned something new in the process! Give them a try. Experiment with different emojis and CSS shapes because gradients, while cool and all, aren’t the only way to make patterns.. The background property takes multiple values, allowing us to think of creative ways to stack things.

The post Creative Background Patterns Using Gradients, CSS Shapes, and Even Emojis appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]