gradients – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Thu, 08 Dec 2022 15:36:22 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 gradients – CSS-Tricks https://css-tricks.com 32 32 45537868 Animated Background Stripes That Transition on Hover https://css-tricks.com/animated-background-stripes-transition-hover/ https://css-tricks.com/animated-background-stripes-transition-hover/#comments Thu, 08 Dec 2022 15:36:21 +0000 https://css-tricks.com/?p=375697 How often to do you reach for the CSS background-size property? If you’re like me — and probably lots of other front-end folks — then it’s usually when you background-size: cover an image to fill the space of an entire …


Animated Background Stripes That Transition on Hover originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
How often to do you reach for the CSS background-size property? If you’re like me — and probably lots of other front-end folks — then it’s usually when you background-size: cover an image to fill the space of an entire element.

Well, I was presented with an interesting challenge that required more advanced background sizing: background stripes that transition on hover. Check this out and hover it with your cursor:

There’s a lot more going on there than the size of the background, but that was the trick I needed to get the stripes to transition. I thought I’d show you how I arrived there, not only because I think it’s a really nice visual effect, but because it required me to get creative with gradients and blend modes that I think you might enjoy.

Let’s start with a very basic setup to keep things simple. I’m talking about a single <div> in the HTML that’s styled as a green square:

<div></div>
div {
  width: 500px;
  height: 500px;
  background: palegreen;
}
Perfect square with a pale green background color.

Setting up the background stripes

If your mind went straight to a CSS linear gradient when you saw those stripes, then we’re already on the same page. We can’t exactly do a repeating gradient in this case since we want the stripes to occupy uneven amounts of space and transition them, but we can create five stripes by chaining five backgrounds on top of our existing background color and placing them to the top-right of the container:

div {
  width: 500px;
  height: 500px;
  background: 
    linear-gradient(black, black) top right,
    linear-gradient(black, black) top 100px right,
    linear-gradient(black, black) top 200px right,
    linear-gradient(black, black) top 300px right,
    linear-gradient(black, black) top 400px right, 
    palegreen;
}

I made horizontal stripes, but we could also go vertical with the approach we’re covering here. And we can simplify this quite a bit with custom properties:

div {
  --gt: linear-gradient(black, black);
  --n: 100px;

  width: 500px;
  height: 500px;
  background: 
    var(--gt) top right,
    var(--gt) top var(--n) right,
    var(--gt) top calc(var(--n) * 2) right,
    var(--gt) top calc(var(--n) * 3) right,
    var(--gt) top calc(var(--n) * 4) right, 
    palegreen;
}

So, the --gt value is the gradient and --n is a constant we’re using to nudge the stripes downward so they are offset vertically. And you may have noticed that I haven’t set a true gradient, but rather solid black stripes in the linear-gradient() function — that’s intentional and we’ll get to why I did that in a bit.

One more thing we ought to do before moving on is prevent our backgrounds from repeating; otherwise, they’ll tile and fill the entire space:

div {
  --gt: linear-gradient(black, black);
  --n: 100px;

  width: 500px;
  height: 500px;
  background: 
    var(--gt) top right,
    var(--gt) top var(--n) right,
    var(--gt) top calc(var(--n) * 2) right,
    var(--gt) top calc(var(--n) * 3) right,
    var(--gt) top calc(var(--n) * 4) right, 
    palegreen;
  background-repeat: no-repeat;
}

We could have set background-repeat in the background shorthand, but I decided to break it out here to keep things easy to read.

Offsetting the stripes

We technically have stripes, but it’s pretty tough to tell because there’s no spacing between them and they cover the entire container. It’s more like we have a solid black square.

This is where we get to use the background-size property. We want to set both the height and the width of the stripes and the property supports a two-value syntax that allows us to do exactly that. And, we can chain those sizes by comma separating them the same way we did on background.

Let’s start simple by setting the widths first. Using the single-value syntax for background-size sets the width and defaults the height to auto. I’m using totally arbitrary values here, so set the values to what works best for your design:

div {
  --gt: linear-gradient(black, black);
  --n: 100px;

  width: 500px;
  height: 500px;
  background: 
    var(--gt) top right,
    var(--gt) top var(--n) right,
    var(--gt) top calc(var(--n) * 2) right,
    var(--gt) top calc(var(--n) * 3) right,
    var(--gt) top calc(var(--n) * 4) right, 
    palegreen;
  background-repeat: no-repeat;
  background-size: 60%, 90%, 70%, 40%, 10%;
}

If you’re using the same values that I am, you’ll get this:

Doesn’t exactly look like we set the width for all the stripes, does it? That’s because of the auto height behavior of the single-value syntax. The second stripe is wider than the others below it, and it is covering them. We ought to set the heights so we can see our work. They should all be the same height and we can actually re-use our --n variable, again, to keep things simple:

div {
  --gt: linear-gradient(black, black);
  --n: 100px;

  width: 500px;
  height: 500px;
  background: 
    var(--gt) top right,
    var(--gt) top var(--n) right,
    var(--gt) top calc(var(--n) * 2) right,
    var(--gt) top calc(var(--n) * 3) right,
    var(--gt) top calc(var(--n) * 4) right, 
    palegreen;
    background-repeat: no-repeat;
    background-size: 60% var(--n), 90% var(--n), 70% var(--n), 40% var(--n), 10% var(--n); // HIGHLIGHT 15
}

Ah, much better!

Adding gaps between the stripes

This is a totally optional step if your design doesn’t require gaps between the stripes, but mine did and it’s not overly complicated. We change the height of each stripe’s background-size a smidge, decreasing the value so they fall short of filling the full vertical space.

We can continue to use our --n variable, but subtract a small amount, say 5px, using calc() to get what we want.

background-size: 60% calc(var(--n) - 5px), 90% calc(var(--n) - 5px), 70% calc(var(--n) - 5px), 40% calc(var(--n) - 5px), 10% calc(var(--n) - 5px);

That’s a lot of repetition we can eliminate with another variable:

div {
  --h: calc(var(--n) - 5px);
  /* etc. */
  background-size: 60% var(--h), 90% var(--h), 70% var(--h), 40% var(--h), 10% var(--h);
}

Masking and blending

Now let’s swap the palegreen background color we’ve been using for visual purposes up to this point for white.

div {
  /* etc. */
  background: 
    var(--gt) top right,
    var(--gt) top var(--n) right,
    var(--gt) top calc(var(--n) * 2) right,
    var(--gt) top calc(var(--n) * 3) right,
    var(--gt) top calc(var(--n) * 4) right, 
    #fff;
  /* etc. */
}

A black and white pattern like this is perfect for masking and blending. To do that, we’re first going to wrap our <div> in a new parent container and introduce a second <div> under it:

<section>
  <div></div>
  <div></div>
</section>

We’re going to do a little CSS re-factoring here. Now that we have a new parent container, we can pass the fixed width and height properties we were using on our <div> over there:

section {
  width: 500px;
  height: 500px;
} 

I’m also going to use CSS Grid to position the two <div> elements on top of one another. This is the same trick Temani Afif uses to create his super cool image galleries. The idea is that we place both divs over the full container using the grid-area property and align everything toward the center:

section {
  display: grid;
  align-items: center;
  justify-items: center;
  width: 500px;
  height: 500px;
} 

section > div {
  width: inherit;
  height: inherit;
  grid-area: 1 / 1;
}

Now, check this out. The reason I used a solid gradient that goes from black to black earlier is to set us up for masking and blending the two <div> layers. This isn’t true masking in the sense that we’re calling the mask property, but the contrast between the layers controls what colors are visible. The area covered by white will remain white, and the area covered by black leaks through. MDN’s documentation on blend modes has a nice explanation of how this works.

To get that working, I’ll apply the real gradient we want to see on the first <div> while applying the style rules from our initial <div> on the new one, using the :nth-child() pseudo-selector:

div:nth-child(1) { 
  background: linear-gradient(to right, red, orange); 
}

div:nth-child(2)  {
  --gt: linear-gradient(black, black);
  --n: 100px;
  --h: calc(var(--n) - 5px);
  background: 
    var(--gt) top right,
    var(--gt) top var(--n) right,
    var(--gt) top calc(var(--n) * 2) right,
    var(--gt) top calc(var(--n) * 3) right,
    var(--gt) top calc(var(--n) * 4) right, 
    white;
  background-repeat: no-repeat;
  background-size: 60% var(--h), 90% var(--h), 70% var(--h), 40% var(--h), 10% var(--h);
}

If we stop here, we actually won’t see any visual difference from what we had before. That’s because we haven’t done the actual blending yet. So, let’s do that now using the screen blend mode:

div:nth-child(2)  {
  /* etc. */
  mix-blend-mode: screen;
}

I used a beige background color in the demo I showed at the beginning of this article. That slightly darker sort of off-white coloring allows a little color to bleed through the rest of the background:

The hover effect

The last piece of this puzzle is the hover effect that widens the stripes to full width. First, let’s write out our selector for it. We want this to happen when the parent container (<section> in our case) is hovered. When it’s hovered, we’ll change the background size of the stripes contained in the second <div>:

/* When <section> is hovered, change the second div's styles */
section:hover > div:nth-child(2){
  /* styles go here */
}

We’ll want to change the background-size of the stripes to the full width of the container while maintaining the same height:

section:hover > div:nth-child(2){
  background-size: 100% var(--h);
}

That “snaps” the background to full-width. If we add a little transition to this, then we see the stripes expand on hover:

section:hover > div:nth-child(2){
  background-size: 100% var(--h);
  transition: background-size 1s;
}

Here’s that final demo once again:

I only added text in there to show what it might look like to use this in a different context. If you do the same, then it’s worth making sure there’s enough contrast between the text color and the colors used in the gradient to comply with WCAG guidelines. And while we’re touching briefly on accessibility, it’s worth considering user preferences for reduced motion when it comes to the hover effect.

That’s a wrap!

Pretty neat, right? I certainly think so. What I like about this, too, is that it’s pretty maintainable and customizable. For example, we can alter the height, colors, and direction of the stripes by changing a few values. You might even variablize a few more things in there — like the colors and widths — to make it even more configurable.

I’m really interested if you would have approached this a different way. If so, please share in the comments! It’d be neat to see how many variations we can collect.


Animated Background Stripes That Transition on Hover originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/animated-background-stripes-transition-hover/feed/ 2 375697
Making Static Noise From a Weird CSS Gradient Bug https://css-tricks.com/making-static-noise-from-a-weird-css-gradient-bug/ https://css-tricks.com/making-static-noise-from-a-weird-css-gradient-bug/#comments Fri, 18 Nov 2022 13:55:24 +0000 https://css-tricks.com/?p=374993 What I will be doing here is kind of an experiment to explore tricks that leverage a bug with the way CSS gradients handle sub-pixel rendering to create a static noise effect — like you might see on a TV with no signal.


Making Static Noise From a Weird CSS Gradient Bug originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
👋 The demos in this article experiment with a non-standard bug related to CSS gradients and sub-pixel rendering. Their behavior may change at any time in the future. They’re also heavy as heck. We’re serving them async where you click to load, but still want to give you a heads-up in case your laptop fan starts spinning.

Do you remember that static noise on old TVs with no signal? Or when the signal is bad and the picture is distorted? In case the concept of a TV signal predates you, here’s a GIF that shows exactly what I mean.

View image (contains auto-playing media)
Animated image showing static noise from a TV screen.

Yes, we are going to do something like this using only CSS. Here is what we’re making:

Before we start digging into the code, I want to say that there are better ways to create a static noise effect than the method I am going to show you. We can use SVG, <canvas>, the filter property, etc. In fact, Jimmy Chion wrote a good article showing how to do it with SVG.

What I will be doing here is kind of a CSS experiment to explore some tricks leveraging a bug with gradients. You can use it on your side projects for fun but using SVG is cleaner and more suitable for a real project. Plus, the effect behaves differently across browsers, so if you’re checking these out, it’s best to view them in Chrome, Edge, or Firefox.

Let’s make some noise!

To make this noise effect we are going to use… gradients! No, there is no secret ingredient or new property that makes it happen. We are going to use stuff that’s already in our CSS toolbox!

The “trick” relies on the fact that gradients are bad at anti-aliasing. You know those kind of jagged edges we get when using hard stop colors? Yes, I talk about them in most of my articles because they are a bit annoying and we always need to add or remove a few pixels to smooth things out:

As you can see, the second circle renders better than the first one because there is a tiny difference (0.5%) between the two colors in the gradient rather than using a straight-up hard color stop using whole number values like the first circle.

Here’s another look, this time using a conic-gradient where the result is more obvious:

An interesting idea struck me while I was making these demos. Instead of fixing the distortion all the time, why not trying to do the opposite? I had no idea what would happen but it was a fun surprise! I took the conic gradient values and started to decrease them to make the poor anti-aliasing results look even worse.

Do you see how bad the last one is? It’s a kind of scrambled in the middle and nothing is smooth. Let’s make it full-screen with smaller values:

I suppose you see where this is going. We get a strange distorted visual when we use very small decimal values for the hard colors stops in a gradient. Our noise is born!

We are still far from the grainy noise we want because we can still see the actual conic gradient. But we can decrease the values to very, very small ones — like 0.0001% — and suddenly there’s no more gradient but pure graininess:

Tada! We have a noise effect and all it takes is one CSS gradient. I bet if I was to show this to you before explaining it, you’d never realize you’re looking at a gradient. You have to look very carefully at center of the gradient to see it.

We can increase the randomness by making the size of the gradient very big while adjusting its position:

The gradient is applied to a fixed 3000px square and placed at the 60% 60% coordinates. We can hardly notice its center in this case. The same can be done with radial gradient as well:

And to make things even more random (and closer to a real noise effect) we can combine both gradients and use background-blend-mode to smooth things out:

Our noise effect is perfect! Even if we look closely at each example, there’s no trace of either gradient in there, but rather beautiful grainy static noise. We just turned that anti-aliasing bug into a slick feature!

Now that we have this, let’s see a few interesting examples where we might use it.

Animated no TV signal

Getting back to the demo we started with:

If you check the code, you will see that I am using a CSS animation on one of the gradients. It’s really as simple as that! All we’re doing is moving the conic gradient’s position at a lightning fast duration (.1s) and this is what we get!

I used this same technique on a one-div CSS art challenge:

Grainy image filter

Another idea is to apply the noise to an image to get an old-time-y look. Hover each image to see them without the noise.

I am using only one gradient on a pseudo-element and blending it with the image, thanks to mix-blend-mode: overlay.

We can get an even funnier effect if we use the CSS filter property

And if we add a mask to the mix, we can make even more effects!

Grainy text treatment

We can apply this same effect to text, too. Again, all we need is a couple of chained gradients on a background-image and then blend the backgrounds. The only difference is that we’re also reaching for background-clip so the effect is only applied to the bounds of each character.

Generative art

If you keep playing with the gradient values, you may get more surprising results than a simple noise effect. We can get some random shapes that look a lot like generative art!

Of course, we are far from real generative art, which requires a lot of work. But it’s still satisfying to see what can be achieved with something that is technically considered a bug!

Monster face

One last example I made for CodePen’s divtober 2022 collection:

Wrapping up

I hope you enjoyed this little CSS experiment. We didn’t exactly learn something “new” but we took a little quirk with gradients and turned it into something fun. I’ll say it again: this isn’t something I would consider using on a real project because who knows if or when anti-aliasing will be addressed at some point in time. Instead, this was a very random, and pleasant, surprise when I stumbled into it. It’s also not that easy to control and it behaves inconsistently across browsers.

This said, I am curious to see what you can do with it! You can play with the values, combine different layers, use a filter, or mix-blend-mode, or whatever, and you will for sure get something really cool. Share your creations in the comment section — there are no prizes but we can get a nice collection going!


Making Static Noise From a Weird CSS Gradient Bug originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/making-static-noise-from-a-weird-css-gradient-bug/feed/ 9 374993
Some Links About CSS Gradients https://css-tricks.com/some-links-about-css-gradients/ https://css-tricks.com/some-links-about-css-gradients/#comments Wed, 02 Nov 2022 12:59:02 +0000 https://css-tricks.com/?p=374697 Every once in a while, the blogging zeitgiest seems to coalesce around a certain topic and it’s like the saved articles in my bookmarks folder are having a conversation. The conversation sitting in there now is all about CSS Gradients …


Some Links About CSS Gradients originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Every once in a while, the blogging zeitgiest seems to coalesce around a certain topic and it’s like the saved articles in my bookmarks folder are having a conversation. The conversation sitting in there now is all about CSS Gradients and I thought I’d link some of the more interesting pieces.


Some Links About CSS Gradients originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/some-links-about-css-gradients/feed/ 4 374697
Fancy Image Decorations: Outlines and Complex Animations https://css-tricks.com/fancy-image-decorations-outlines-and-complex-animations/ https://css-tricks.com/fancy-image-decorations-outlines-and-complex-animations/#comments Fri, 28 Oct 2022 12:45:47 +0000 https://css-tricks.com/?p=374302 We’ve spent the last two articles in this three-part series playing with gradients to make really neat image decorations using nothing but the <img> element. In this third and final piece, we are going to explore more techniques using the …


Fancy Image Decorations: Outlines and Complex Animations originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We’ve spent the last two articles in this three-part series playing with gradients to make really neat image decorations using nothing but the <img> element. In this third and final piece, we are going to explore more techniques using the CSS outline property. That might sound odd because we generally use outline to draw a simple line around an element — sorta like border but it can only draw all four sides at once and is not part of the Box Model.

We can do more with it, though, and that’s what I want to experiment with in this article.

Fancy Image Decorations series

Let’s start with our first example — an overlay that disappears on hover with a cool animation:

We could accomplish this by adding an extra element over the image, but that’s what we’re challenging ourselves not to do in this series. Instead, we can reach for the CSS outline property and leverage that it can have a negative offset and is able to overlap its element.

img {
  --s: 250px; /* the size of the image */
  --b: 8px;   /* the border thickness*/
  --g: 14px;  /* the gap */
  --c: #4ECDC4;

  width: var(--s);
  aspect-ratio: 1;
  outline: calc(var(--s) / 2) solid #0009;
  outline-offset: calc(var(--s) / -2);
  cursor: pointer;
  transition: 0.3s;
}
img:hover {
  outline: var(--b) solid var(--c);
  outline-offset: var(--g);
}

The trick is to create an outline that’s as thick as half the image size, then offset it by half the image size with a negative value. Add in some semi-transparency with the color and we have our overlay!

Diagram showing the size of the outline sround the image and how it covers the image on hover.

The rest is what happens on :hover. We update the outline and the transition between both outlines creates the cool hover effect. The same technique can also be used to create a fading effect where we don’t move the outline but make it transparent.

Instead of using half the image size in this one, I am using a very big outline thickness value (100vmax) while applying a CSS mask. With this, there’s no longer a need to know the image size — it trick works at all sizes!

Diagram showing how adding a mask clips the extra outline around the image.

You may face issues using 100vmax as a big value in Safari. If it’s the case, consider the previous trick where you replace the 100vmax with half the image size.

We can take things even further! For example, instead of simply clipping the extra outline, we can create shapes and apply a fancy reveal animation.

Cool right? The outline is what creates the yellow overlay. The clip-path clips the extra outline to get the star shape. Then, on hover, we make the color transparent.

Oh, you want hearts instead? We can certainly do that!

Imagine all the possible combinations we can create. All we have to do is to draw a shape with a CSS mask and/or clip-path and combine it with the outline trick. One solution, infinite possibilities!

And, yes, we can definitely animate this as well. Let’s not forget that clip-path is animatable and mask relies on gradients — something we covered in super great detail in the first two articles of this series.

I know, the animation is a bit glitchy. This is more of a demo to illustrate the idea rather than the “final product” to be used in a production site. We’d wanna optimize things for a more natural transition.

Here is a demo that uses mask instead. It’s the one I teased you with at the end of the last article:

Did you know that the outline property was capable of so much awesomeness? Add it to your toolbox for fancy image decorations!

Combine all the things!

Now that we have learned many tricks using gradients, masks, clipping, and outline, it’s time for the grand finale. Let’s cap off this series by combine all that we have learned the past few weeks to showcase not only the techniques, but demonstrate just how flexible and modular these approaches are.

If you were seeing these demos for the first time, you might assume that there’s a bunch of extra divs wrappers and pseudo-elements being used to pull them off. But everything is happening directly on the <img> element. It’s the only selector we need to get these advanced shapes and effects!

Wrapping up

Well, geez, thanks for hanging out with me in this three-part series the past few weeks. We explored a slew of different techniques that turn simple images into something eye-catching and interactive. Will you use everything we covered? Certainly not! But my hope is that this has been a good exercise for you to dig into advanced uses of CSS features, like gradients, mask, clip-path, and outline.

And we did everything with just one <img> element! No extra div wrappers and pseudo-elements. Sure, it’s a constraint we put on ourselves, but it also pushed us to explore CSS and try to find innovative solutions to common use cases. So, before pumping extra markup into your HTML, think about whether CSS is already capable of handling the task.

Fancy Image Decorations series


Fancy Image Decorations: Outlines and Complex Animations originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/fancy-image-decorations-outlines-and-complex-animations/feed/ 3 374302
Fancy Image Decorations: Masks and Advanced Hover Effects https://css-tricks.com/fancy-image-decorations-masks-and-advanced-hover-effects/ https://css-tricks.com/fancy-image-decorations-masks-and-advanced-hover-effects/#comments Fri, 21 Oct 2022 12:46:27 +0000 https://css-tricks.com/?p=374194 Welcome to Part 2 of this three-part series! We are still decorating images without any extra elements and pseudo-elements. I hope you already took the time to digest Part 1 because we will continue working with a lot of gradients …


Fancy Image Decorations: Masks and Advanced Hover Effects originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Welcome to Part 2 of this three-part series! We are still decorating images without any extra elements and pseudo-elements. I hope you already took the time to digest Part 1 because we will continue working with a lot of gradients to create awesome visual effects. We are also going to introduce the CSS mask property for more complex decorations and hover effects.

Fancy Image Decorations series

Let’s turn to the first example we’re working on together…

The Postage Stamp

Believe or not, all it takes to make postage stamp CSS effect is two gradients and a filter:

img {
  --r: 10px; /* control the radius of the circles */
  padding: calc(2 * var(--r));
  filter: grayscale(.4);
  background: 
    radial-gradient(var(--r),#0000 98%,#fff) round
      calc(-1.5 * var(--r)) calc(-1.5 * var(--r)) / calc(3 * var(--r)) calc(3 * var(--r)),
    linear-gradient(#fff 0 0) no-repeat
      50% / calc(100% - 3 * var(--r)) calc(100% - 3 * var(--r));
}

As we saw in the previous article, the first step is to make space around the image with padding so we can draw a background gradient and see it there. Then we use a combination of radial-gradient() and linear-gradient() to cut those circles around the image.

Here is a step-by-step illustration that shows how the gradients are configured:

Note the use of the round value in the second step. It’s very important for the trick as it ensures the size of the gradient is adjusted to be perfectly aligned on all the sides, no matter what the image width or height is.

From the specification: The image is repeated as often as will fit within the background positioning area. If it doesn’t fit a whole number of times, it is rescaled so that it does.

The Rounded Frame

Let’s look at another image decoration that uses circles…

This example also uses a radial-gradient(), but this time I have created circles around the image instead of the cut-out effect. Notice that I am also using the round value again. The trickiest part here is the transparent gap between the frame and the image, which is where I reach for the CSS mask property:

img {
  --s: 20px; /* size of the frame */
  --g: 10px; /* the gap */
  --c: #FA6900; 

  padding: calc(var(--g) + var(--s));
  background: 
    radial-gradient(farthest-side, var(--c) 97%, #0000) 
      0 0 / calc(2 * var(--s)) calc(2 * var(--s)) round;
  mask:
    conic-gradient(from 90deg at calc(2 * var(--s)) calc(2 * var(--s)), #0000 25%, #000 0)
      calc(-1 * var(--s)) calc(-1 * var(--s)),
    linear-gradient(#000 0 0) content-box;
}

Masking allows us to show the area of the image — thanks to the linear-gradient() in there — as well as 20px around each side of it — thanks to the conic-gradient(). The 20px is nothing but the variable --s that defines the size of the frame. In other words, we need to hide the gap.

Here’s what I mean:

The linear gradient is the blue part of the background while the conic gradient is the red part of the background. That transparent part between both gradients is what we cut from our element to create the illusion of an inner transparent border.

The Inner Transparent Border

For this one, we are not going to create a frame but rather try something different. We are going to create a transparent inner border inside our image. Probably not that useful in a real-world scenario, but it’s good practice with CSS masks.

Similar to the previous example, we are going to rely on two gradients: a linear-gradient() for the inner part, and a conic-gradient() for the outer part. We’ll leave a space between them to create the transparent border effect.

img {
  --b: 5px;  /* the border thickness */
  --d: 20px; /* the distance from the edge */

  --_g: calc(100% - 2 * (var(--d) + var(--b)));
  mask:
    conic-gradient(from 90deg at var(--d) var(--d), #0000 25%, #000 0)
      0 0 / calc(100% - var(--d)) calc(100% - var(--d)),
    linear-gradient(#000 0 0) 50% / var(--_g) var(--_g) no-repeat;
}
Detailing the parts of the image that correspond to CSS variables.

You may have noticed that the conic gradient of this example has a different syntax from the previous example. Both are supposed to create the same shape, so why are they different? It’s because we can reach the same result using different syntaxes. This may look confusing at first, but it’s a good feature. You are not obliged to find the solution to achieve a particular shape. You only need to find one solution that works for you out of the many possibilities out there.

Here are four ways to create the outer square using gradients:

There are even more ways to pull this off, but you get the point.

There is no Best™ approach. Personally, I try to find the one with the smallest and most optimized code. For me, any solution that requires fewer gradients, fewer calculations, and fewer repeated values is the most suitable. Sometimes I choose a more verbose syntax because it gives me more flexibility to change variables and modify things. It comes with experience and practice. The more you play with gradients, the more you know what syntax to use and when.

Let’s get back to our inner transparent border and dig into the hover effect. In case you didn’t notice, there is a cool hover effect that moves that transparent border using a font-size trick. The idea is to define the --d variable with a value of 1em. This variables controls the distance of the border from the edge. We can transform like this:

--_d: calc(var(--d) + var(--s) * 1em)

…giving us the following updated CSS:

img {
  --b: 5px;  /* the border thickness */
  --d: 20px; /* the distance from the edge */
  --o: 15px; /* the offset on hover */
  --s: 1;    /* the direction of the hover effect (+1 or -1)*/

  --_d: calc(var(--d) + var(--s) * 1em);
  --_g: calc(100% - 2 * (var(--_d) + var(--b)));
  mask:
    conic-gradient(from 90deg at var(--_d) var(--_d), #0000 25%, #000 0)
     0 0 / calc(100% - var(--_d)) calc(100% - var(--_d)),
    linear-gradient(#000 0 0) 50% / var(--_g) var(--_g) no-repeat;
  font-size: 0;
  transition: .35s;
}
img:hover {
  font-size: var(--o);
}

The font-size is initially equal to 0 ,so 1em is also equal to 0 and --_d is be equal to --d. On hover, though, the font-size is equal to a value defined by an --o variable that sets the border’s offset. This, in turn, updates the --_d variable, moving the border by the offset. Then I add another variable, --s, to control the sign that decides whether the border moves to the inside or the outside.

The font-size trick is really useful if we want to animate properties that are otherwise unanimatable. Custom properties defined with @property can solve this but support for it is still lacking at the time I’m writing this.

The Frame Reveal

We made the following reveal animation in the first part of this series:

We can take the same idea, but instead of a border with a solid color we will use a gradient like this:

If you compare both codes you will notice the following changes:

  1. I used the same gradient configuration from the first example inside the mask property. I simply moved the gradients from the background property to the mask property.
  2. I added a repeating-linear-gradient() to create the gradient border.

That’s it! I re-used most of the same code we already saw — with super small tweaks — and got another cool image decoration with a hover effect.

/* Solid color border */

img {
  --c: #8A9B0F; /* the border color */
  --b: 10px;   /* the border thickness*/
  --g: 5px;  /* the gap on hover */

  padding: calc(var(--g) + var(--b));
  --_g: #0000 25%, var(--c) 0;
  background: 
    conic-gradient(from 180deg at top var(--b) right var(--b), var(--_g))
     var(--_i, 200%) 0 / 200% var(--_i, var(--b)) no-repeat,
    conic-gradient(at bottom var(--b) left  var(--b), var(--_g))
     0 var(--_i, 200%) / var(--_i, var(--b)) 200% no-repeat;
  transition: .3s, background-position .3s .3s;
  cursor: pointer;
}
img:hover {
  --_i: 100%;
  transition: .3s, background-size .3s .3s;
}
/* Gradient color border */

img {
  --b: 10px; /* the border thickness*/
  --g: 5px;  /* the gap on hover */
  background: repeating-linear-gradient(135deg, #F8CA00 0 10px, #E97F02 0 20px, #BD1550 0 30px);

  padding: calc(var(--g) + var(--b));
  --_g: #0000 25%, #000 0;
  mask: 
    conic-gradient(from 180deg at top var(--b) right var(--b), var(--_g))
     var(--_i, 200%) 0 / 200% var(--_i, var(--b)) no-repeat,
    conic-gradient(at bottom var(--b) left  var(--b), var(--_g))
     0 var(--_i, 200%) / var(--_i, var(--b)) 200% no-repeat,
    linear-gradient(#000 0 0) content-box;
  transition: .3s, mask-position .3s .3s;
  cursor: pointer;
}
img:hover {
  --_i: 100%;
  transition: .3s, mask-size .3s .3s;
}

Let’s try another frame animation. This one is a bit tricky as it has a three-step animation:

The first step of the animation is to make the bottom edge bigger. For this, we adjust the background-size of a linear-gradient():

You are probably wondering why I am also adding the top edge. We need it for the third step. I always try to optimize the code I write, so I am using one gradient to cover both the top and bottom sides, but the top one is hidden and revealed later with a mask.

For the second step, we add a second gradient to show the left and right edges. But this time, we do it using background-position:

We can stop here as we already have a nice effect with two gradients but we are here to push the limits so let’s add a touch of mask to achieve the third step.

The trick is to make the top edge hidden until we show the bottom and the sides and then we update the mask-size (or mask-position) to show the top part. As I said previously, we can find a lot of gradient configurations to achieve the same effect.

Here is an illustration of the gradients I will be using:

I am using two conic gradients having a width equal to 200%. Both gradients cover the area leaving only the top part uncovered (that part will be invisible later). On hover, I slide both gradients to cover that part.

Here is a better illustration of one of the gradients to give you a better idea of what’s happening:

Now we put this inside the mask property and we are done! Here is the full code:

img {
  --b: 6px;  /* the border thickness*/
  --g: 10px; /* the gap */
  --c: #0E8D94;

  padding: calc(var(--b) + var(--g));
  --_l: var(--c) var(--b), #0000 0 calc(100% - var(--b)), var(--c) 0;
  background:
    linear-gradient(var(--_l)) 50%/calc(100% - var(--_i,80%)) 100% no-repeat,
    linear-gradient(90deg, var(--_l)) 50% var(--_i,-100%)/100% 200% no-repeat;  
  mask:
    conic-gradient(at 50% var(--b),#0000 25%, #000 0) calc(50% + var(--_i, 50%)) / 200%,
    conic-gradient(at 50% var(--b),#000 75%, #0000 0) calc(50% - var(--_i, 50%)) / 200%;
  transition: 
    .3s calc(.6s - var(--_t,.6s)) mask-position, 
    .3s .3s background-position,
    .3s var(--_t,.6s) background-size,
    .4s transform;
  cursor: pointer;
}
img:hover {
  --_i: 0%;
  --_t: 0s;
  transform: scale(1.2);
}

I have also introduced some variables to optimize the code, but you should be used to this right now.

What about a four-step animation? Yes, it’s possible!

No explanation for this because it’s your homework! Take all that you have learned in this article to dissect the code and try to articulate what it’s doing. The logic is similar to all the previous examples. The key is to isolate each gradient to understand each step of the animation. I kept the code un-optimized to make things a little easier to read. I do have an optimized version if you are interested, but you can also try to optimize the code yourself and compare it with my version for additional practice.

Wrapping up

That’s it for Part 2 of this three-part series on creative image decorations using only the <img> element. We now have a good handle on how gradients and masks can be combined to create awesome visual effects, and even animations — without reaching for extra elements or pseudo-elements. Yes, a single <img> tag is enough!

We have one more article in this series to go. Until then, here is a bonus demo with a cool hover effect where I use mask to assemble a broken image.

Fancy Image Decorations series


Fancy Image Decorations: Masks and Advanced Hover Effects originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/fancy-image-decorations-masks-and-advanced-hover-effects/feed/ 1 374194
CSS Checkerboard Background… But With Rounded Corners and Hover Styles https://css-tricks.com/css-checkerboard-background-but-with-rounded-corners-and-hover-styles/ https://css-tricks.com/css-checkerboard-background-but-with-rounded-corners-and-hover-styles/#comments Tue, 20 Sep 2022 13:19:24 +0000 https://css-tricks.com/?p=373167 On one hand, creating simple checkered backgrounds with CSS is easy. On the other hand, though, unless we are one of the CSS-gradient-ninjas, we are kind of stuck with basic patterns.

At least that’s what I thought while staring at …


CSS Checkerboard Background… But With Rounded Corners and Hover Styles originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
On one hand, creating simple checkered backgrounds with CSS is easy. On the other hand, though, unless we are one of the CSS-gradient-ninjas, we are kind of stuck with basic patterns.

At least that’s what I thought while staring at the checkered background on my screen and trying to round those corners of the squares just a little…until I remembered my favorite bullet point glyph — — and figured that if only I could place it over every intersection in the pattern, I’ll surely get the design I want.

Turns out it’s possible! Here’s the proof.

Let’s start with the basic pattern:

<div></div>
div {
 background: 
  repeating-linear-gradient(
    to right, transparent, 
    transparent 50px, 
    white 50px, 
    white 55px
  ),
  repeating-linear-gradient(
    to bottom, transparent,  
    transparent 50px, 
    white 50px, 
    white 55px
  ),
  linear-gradient(45deg, pink, skyblue);
  /* more styles */
}

What that gives us is a repeating background of squares that go from pink to blue with 5px white gaps between them. Each square is fifty pixels wide and transparent. This is created using repeating-linear-gradient, which creates a linear gradient image where the gradient repeats throughout the containing area.

In other words, the first gradient in that sequence creates white horizontal stripes and the second gradient creates white vertical stripes. Layered together, they form the checkered pattern, and the third gradient fills in the rest of the space.

Now we add the star glyph I mentioned earlier, on top of the background pattern. We can do that by including it on the same background property as the gradients while using an encoded SVG for the shape:

div {
  background: 
    repeat left -17px top -22px/55px 55px
    url("data:image/svg+xml,
    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 35px 35px'>
      <foreignObject width='35px' height='35px'>
        <div xmlns='http://www.w3.org/1999/xhtml' style='color: white; font-size: 35px'>✦</div>
      </foreignObject>
    </svg>"
    ), 
    repeating-linear-gradient(
      to right, transparent,
      transparent 50px,
      white 50px,
      white 55px
    ),
    repeating-linear-gradient(
      to bottom, transparent,
      transparent 50px,
      white 50px,
      white 55px
    ),
    linear-gradient(45deg, pink, skyblue);
  /* more style */
}

Let’s break that down. The first keyword, repeat, denotes that this is a repeating background image. Followed by that is the position and size of each repeating unit, respectively (left -17px top -22px/55px 55px). This offset position is based on the glyph and pattern’s size. You’ll see below how the glyph size is given. The offset is added to re-position the repeating glyph exactly over each intersection in the checkered pattern.

The SVG has an HTML <div> carrying the glyph. Notice that I declared a font-size on it. That ultimately determines the border radius of the squares in the checkerboard pattern — the bigger the glyph, the more rounded the squares. The unrolled SVG from the data URL looks like this:

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 35px 35px'>
  <foreignObject width='35px' height='35px'>
    <div xmlns='http://www.w3.org/1999/xhtml' style='color:white;font-size:35px'>✦</div>
  </foreignObject>
</svg>

Now that a CSS pattern is established, let’s add a :hover effect where the glyph is removed and the white lines are made slightly translucent by using rgb() color values with alpha transparency.

div:hover {
  background:
    repeating-linear-gradient(
      to right, transparent,
      transparent 50px,
      rgb(255 255 255 / 0.5) 50px,
      rgb(255 255 255 / 0.5) 55px
    ),
    repeating-linear-gradient(
      to bottom, transparent,
      transparent 50px,
      rgb(255 255 255 / 0.5) 50px,
      rgb(255 255 255 / 0.5) 55px
    ),
  linear-gradient(45deg, pink, skyblue);
  box-shadow: 10px 10px 20px pink;
}

There we go! Now, not only do we have our rounded corners, but we also have more control control over the pattern for effects like this:

Again, this whole exercise was an attempt to get a grid of squares in a checkerboard pattern that supports rounded corners, a background gradient that serves as an overlay across the pattern, and interactive styles. I think this accomplishes the task quite well, but I’m also interested in how you might’ve approached it. Let me know in the comments!


CSS Checkerboard Background… But With Rounded Corners and Hover Styles originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-checkerboard-background-but-with-rounded-corners-and-hover-styles/feed/ 4 373167
Making a Real-Time Clock With a Conic Gradient Face https://css-tricks.com/making-a-real-time-clock-with-a-conic-gradient-face/ https://css-tricks.com/making-a-real-time-clock-with-a-conic-gradient-face/#comments Mon, 19 Sep 2022 12:58:09 +0000 https://css-tricks.com/?p=373184 Gradients have been a part of the CSS spectrum for quite some time now. We see a lot of radial and linear gradients in a lot of projects, but there is one type of gradient that seems to be a …


Making a Real-Time Clock With a Conic Gradient Face originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Gradients have been a part of the CSS spectrum for quite some time now. We see a lot of radial and linear gradients in a lot of projects, but there is one type of gradient that seems to be a bit lonely: the conic gradient. We’re going to make a watch face using this type of gradient.

Working with conic gradients

What we’re making consists of a gradient with color transitions rotated around a center point and can have multiple color values. For this clock to work, we will also be using the angle value of a conic gradient which defines the rotation or starting point. The angle is defined by using a from value.

background-image: conic-gradient(from 45deg, #6e7dab, #5762d5);

What is interesting about this, is that a starting angle can have a negative value in CSS, which will come in handy later.

A simple elegant example of a conical gradient:

Building our basic clock

Let’s start by adding some HTML for the clock and the hands:

Let’s create some default styling for our clock. For this to work properly, we will update CSS variables with JavaScript later on, so let’s scope these variables inside our .clock selector. For easy tweaking, let’s add the colors of the hands as well.

.clock {
  /* general clock vars */
  --hour-hand-color: #000;
  --hour-hand-degrees: 0deg;
  --minute-hand-color: #000;
  --minute-hand-degrees: 0deg;
  --second-hand-color: hotpink;
  --second-hand-degrees: 0deg;

  position: relative;
  min-width: 320px;
  width: 25vw;
  height: 25vw;
  min-height: 320px;
  border-radius: 50%;
  margin: 0 auto;
  border: 7px solid #000;
}

/* clock hands */
.hand {
  position: absolute;
  left: 50%;
  bottom: 50%;
  height: 45%;
  width: 4px;
  margin-left: -2px;
  background: var(--second-hand-color);
  border-radius: 6px;
  transform-origin: bottom center;
  transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1);
}
.second-hand {
  transform: rotate(var(--second-hand-degrees));
}
.hour-hand {
  height: 35%;
  border-radius: 40px;
  background-color: var(--hour-hand-color);
  transform: rotate(var(--hour-hand-degrees));
}
.minute-hand {
  height: 50%;
  background: var(--minute-hand-color);
  transform: rotate(var(--minute-hand-degrees));
}

This sets us up with the general styling we need for the clock. We’ve set transform-origin on the hands so that they properly rotate around the face of the clock. There are also a few custom properties in there to set angles on the hands that we’ll update with JavaScript to get the timing just right so that each hand maps to seconds, minutes, and hours accordingly.

Here’s what we have so far:

Alright, let’s move on to updating those custom properties!

Adding the JavaScript for our basic clock

First off, we’re going to target our clock and create a function:

const clock = document.getElementById("clock");
function setDate() {
  // Code to set the current time and hand angles.
}
setDate();

Inside of our function we’re going to fetch the current time using the Date() function to calculate the correct angle of the hands:

const now = new Date();
const secondsAngle = now.getSeconds() * 6; 
const minsAngle = now.getMinutes() * 6 + secondsAngle / 60;
const hourAngle = ((now.getHours() % 12) / 12) * 360 + minsAngle / 12;

Here is how this calculation works:

  • Seconds: We take 60 seconds and multiply it by 6, which happens to be 360, the perfect number of angles in a full circle.
  • Minutes: Same as seconds, but now we add the seconds angle and divide it by 60 to increase the angle just a little bit within the minute for a more accurate result.
  • Hours: First, we calculate the remainder of the hour and divide it by 12. Then we divide that remainder by 12 again to get a decimal value we can multiply by 360. For example, when we’re at the 23rd hour, 23 / 12 = remain 11. Divide this by 12 and we get 0.916 which then gets multiplied by 360 for a grand total of 330. Here, we will do the same thing we did with the minutes and add the minutes angle, divided by 12, for a more accurate result.

Now that we have our angles, the only thing left to do is to update the variables of our clock by adding the following at the end of our function:

clock.style.setProperty("--second-hand-degrees", secondsAngle + "deg");
clock.style.setProperty("--minute-hand-degrees", minsAngle + "deg");
clock.style.setProperty("--hour-hand-degrees", hourAngle + "deg");

Last, but not least, we will trigger the function with an interval of a second to get a working clock:

const clock = document.getElementById("clock");
function setDate() {
  // etc.
}
// Tick tick tick
setInterval(setDate, 1000);
setDate();

See the working demo of our basic clock:

Applying this to a conical gradient

OK, so the hands of our clock are working. What we really want is to map them to a conical gradient that updates as the time changes. You may have seen the same effect if you have an Apple Watch with the “Gradient” face active:

Black Apple Watch on a person's wrist showing a deep purple conic gradient face.
Credit: Macworld

To do this, let’s start by updating our .clock element with a conic gradient and two custom properties that control the starting and ending angles :

.clock {
  /* same as before */

  /* conic gradient vars */
  --start: 0deg;
  --end: 0deg;

  /* same as before */

  background: 
    conic-gradient(
      from var(--start),
      rgb(255 255 255) 2deg,
      rgb(0 0 0 / 0.5) var(--end),
      rgb(255 255 255) 2deg,
      rgb(0 0 0 / 0.7)
  );
}

You can play around with this a bit to style it just the way you like it. I added some extra colors in the gradient to my liking, but as long as you have a starting point and an ending point, you’re good to go.

Next up, we will update our setDate() function so that it updates the variables for our starting and ending points on the conic gradient. The starting point will be our seconds hand, which is easy to find because it will be the same as the angle of our minutes. To make this end at the hours hand, we should make our ending point the same as the hourAngle variable in the script, but subtract our starting point from it.

let startPosition = minsAngle;
let endPosition = hourAngle - minsAngle;

Now we can update our variables with JavaScript again:

clock.style.setProperty("--start", startPosition + "deg");
clock.style.setProperty("--end", endPosition + "deg");

It looks like we could be done at this point, but there is a catch! This calculation works fine as long as the minutes hand has a smaller angle than the hours hand. Our conic gradient will get messy the moment when the minutes hand has moved past it. To fix this, we will use a negative value as a starting point. Luckily, it’s easy to spot when this happens. Before updating our variables we’ll add the following:

if (minsAngle > hourAngle) {
  startPosition = minsAngle - 360;
  endPosition = hourAngle - startPosition;
}

By subtracting 360 from our minutes angle, we are able to set a negative value for our startposition variable. Because of this negative starting point, our end position should be updated by the hour angle, subtracted by the starting position.

There we go — now the hour and minute hands are set to gradient angles:

That’s it! But don’t let that stop you from taking this even further. Create your own styles and share them with me in the comments so I can check them out.. Here is a little inspiration to get you going:


Making a Real-Time Clock With a Conic Gradient Face originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/making-a-real-time-clock-with-a-conic-gradient-face/feed/ 8 373184
Single Element Loaders: Going 3D! https://css-tricks.com/single-element-loaders-going-3d/ https://css-tricks.com/single-element-loaders-going-3d/#comments Fri, 01 Jul 2022 13:24:09 +0000 https://css-tricks.com/?p=366544 For this fourth and final article of our little series on single-element loaders, we are going to explore 3D patterns. When creating a 3D element, it’s hard to imagine that just one HTML element is enough to simulate something like all six faces of a cube. But  maybe we can get away …


Single Element Loaders: Going 3D! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
For this fourth and final article of our little series on single-element loaders, we are going to explore 3D patterns. When creating a 3D element, it’s hard to imagine that just one HTML element is enough to simulate something like all six faces of a cube. But  maybe we can get away with something more cube-like instead by showing only the front three sides of the shape — it’s totally possible and that’s what we’re going to do together.

The split cube loader

Here is a 3D loader where a cube is split into two parts, but is only made with only a single element:

Each half of the cube is made using a pseudo-element:

Cool, right?! We can use a conic gradient with CSS clip-path on the element’s ::before and ::after pseudos to simulate the three visible faces of a 3D cube. Negative margin is what pulls the two pseudos together to overlap and simulate a full cube. The rest of our work is mostly animating those two halves to get neat-looking loaders!

Let’s check out a visual that explains the math behind the clip-path points used to create this cube-like element:

We have our variables and an equation, so let’s put those to work. First, we’ll establish our variables and set the sizing for the main .loader element:

.loader {
  --s: 150px; /* control the size */
  --_d: calc(0.353 * var(--s)); /* 0.353 = sin(45deg)/2 */

  width: calc(var(--s) + var(--_d)); 
  aspect-ratio: 1;
  display: flex;
}

Nothing too crazy so far. We have a 150px square that’s set up as a flexible container. Now we establish our pseudos:

.loader::before,
.loader::after {
  content: "";
  flex: 1;
}

Those are two halves in the .loader container. We need to paint them in, so that’s where our conic gradient kicks in:

.loader::before,
.loader::after {
  content: "";
  flex: 1;
  background:
    conic-gradient(from -90deg at calc(100% - var(--_d)) var(--_d),
    #fff 135deg, #666 0 270deg, #aaa 0);
}

The gradient is there, but it looks weird. We need to clip it to the element:

.loader::before,
.loader::after {
  content: "";
  flex: 1;
  background:
    conic-gradient(from -90deg at calc(100% - var(--_d)) var(--_d),
    #fff 135deg, #666 0 270deg, #aaa 0);
  clip-path:
    polygon(var(--_d) 0, 100% 0, 100% calc(100% - var(--_d)), calc(100% - var(--_d)) 100%, 0 100%, 0 var(--_d));
}

Let’s make sure the two halves overlap with a negative margin:

.loader::before {
  margin-right: calc(var(--_d) / -2);
}

.loader::after {
  margin-left: calc(var(--_d) / -2);
}

Now let’s make ‘em move!

.loader::before,
.loader::after {
  /* same as before */
  animation: load 1.5s infinite cubic-bezier(0, .5, .5, 1.8) alternate;
}

.loader::after {
  /* same as before */
  animation-delay: -.75s
}

@keyframes load{
  0%, 40%   { transform: translateY(calc(var(--s) / -4)) }
  60%, 100% { transform: translateY(calc(var(--s) / 4)) }
}

Here’s the final demo once again:

The progress cube loader

Let’s use the same technique to create a 3D progress loader. Yes, still only one element!

We’re not changing a thing as far as simulating the cube the same way we did before, other than changing the loader’s height and aspect ratio. The animation we’re making relies on a surprisingly easy technique where we update the width of the left side while the right side fills the remaining space, thanks to flex-grow: 1.

The first step is to add some transparency to the right side using opacity:

This simulates the effect that one side of the cube is filled in while the other is empty. Then we update the color of the left side. To do that, we either update the three colors inside the conic gradient or we do it by adding a background color with a background-blend-mode:

.loader::before {
  background-color: #CC333F; /* control the color here */
  background-blend-mode: multiply;
}

This trick only allows us to update the color only once. The right side of the loader blends in with the three shades of white from the conic gradient to create three new shades of our color, even though we’re only using one color value. Color trickery!

Let’s animate the width of the loader’s left side:

Oops, the animation is a bit strange at the beginning! Notice how it sort of starts outside of the cube? This is because we’re starting the animation at the 0% width. But due to the clip-path and negative margin we’re using, what we need to do instead is start from our --_d variable, which we used to define the clip-path points and the negative margin:

@keyframes load {
  0%,
  5% {width: var(--_d); }
  95%,
  100% {width: 100%; }
}

That’s a little better:

But we can make this animation even smoother. Did you notice we’re missing a little something? Let me show you a screenshot to compare what the final demo should look like with that last demo:

It’s the bottom face of the cube! Since the second element is transparent, we need to see the bottom face of that rectangle as you can see in the left example. It’s subtle, but should be there!

We can add a gradient to the main element and clip it like we did with the pseudos:

background: linear-gradient(#fff1 0 0) bottom / 100% var(--_d) no-repeat;

Here’s the full code once everything is pulled together:

.loader {
  --s: 100px; /* control the size */
  --_d: calc(0.353*var(--s)); /* 0.353 = sin(45deg) / 2 */

  height: var(--s); 
  aspect-ratio: 3;
  display: flex;
  background: linear-gradient(#fff1 0 0) bottom / 100% var(--_d) no-repeat;
  clip-path: polygon(var(--_d) 0, 100% 0, 100% calc(100% - var(--_d)), calc(100% - var(--_d)) 100%, 0 100%, 0 var(--_d));
}
.loader::before,
.loader::after {
  content: "";
  clip-path: inherit;
  background:
    conic-gradient(from -90deg at calc(100% - var(--_d)) var(--_d),
     #fff 135deg, #666 0 270deg, #aaa 0);
}
.loader::before {
  background-color: #CC333F; /* control the color here */
  background-blend-mode: multiply;
  margin-right: calc(var(--_d) / -2);
  animation: load 2.5s infinite linear;
}
.loader:after {
  flex: 1;
  margin-left: calc(var(--_d) / -2);
  opacity: 0.4;
}

@keyframes load {
  0%,
  5% { width: var(--_d); }
  95%,
  100% { width: 100%; }
}

That’s it! We just used a clever technique that uses pseudo-elements, conic gradients, clipping, background blending, and negative margins to get, not one, but two sweet-looking 3D loaders with nothing more than a single element in the markup.

More 3D

We can still go further and simulate an infinite number of 3D cubes using one element — yes, it’s possible! Here’s a grid of cubes:

This demo and the following demos are unsupported in Safari at the time of writing.

Crazy, right? Now we’re creating a repeated pattern of cubes made using a single element… and no pseudos either! I won’t go into fine detail about the math we are using (there are very specific numbers in there) but here is a figure to visualize how we got here:

We first use a conic-gradient to create the repeating cube pattern. The repetition of the pattern is controlled by three variables:

  • --size: True to its name, this controls the size of each cube.
  • --m: This represents the number of columns.
  • --n: This is the number of rows.
  • --gap: this the gap or distance between the cubes
.cube {
  --size: 40px; 
  --m: 4; 
  --n: 5;
  --gap :10px;

  aspect-ratio: var(--m) / var(--n);
  width: calc(var(--m) * (1.353 * var(--size) + var(--gap)));
  background:
    conic-gradient(from -90deg at var(--size) calc(0.353 * var(--size)),
      #249FAB 135deg, #81C5A3 0 270deg, #26609D 0) /* update the colors here */
    0 0 / calc(100% / var(--m)) calc(100% / var(--n));
}

Then we apply a mask layer using another pattern having the same size. This is the trickiest part of this idea. Using a combination of a linear-gradient and a conic-gradient we will cut a few parts of our element to keep only the cube shapes visible.

.cube {
  /* etc. */
  mask: 
    linear-gradient(to bottom right,
       #0000 calc(0.25 * var(--size)),
       #000 0 calc(100% - calc(0.25 * var(--size)) - 1.414 * var(--gap)),
       #0000 0),
    conic-gradient(from -90deg at right var(--gap) bottom var(--gap), #000 90deg, #0000 0);  
  mask-size: calc(100% / var(--m)) calc(100% / var(--n));
  mask-composite: intersect;
}

The code may look a bit complex but thanks to CSS variables all we need to do is to update a few values to control our matrix of cubes. Need a 10⨉10 grid? Update the --m and --n variables to 10. Need a wider gap between cubes? Update the --gap value. The color values are only used once, so update those for a new color palette!

Now that we have another 3D technique, let’s use it to build variations of the loader by playing around with different animations. For example, how about a repeating pattern of cubes sliding infinitely from left to right?

This loader defines four cubes in a single row. That means our --n value is 4 and --m is equal to 1 . In other words, we no longer need these!

Instead, we can work with the --size and --gap variables in a grid container:

.loader {
  --size: 70px;
  --gap: 15px;  

  width: calc(3 * (1.353 * var(--size) + var(--gap)));
  display: grid;
  aspect-ratio: 3;
}

This is our container. We have four cubes, but only want to show three in the container at a time so that we always have one sliding in as one is sliding out. That’s why we are factoring the width by 3 and have the aspect ratio set to 3 as well.

Let’s make sure that our cube pattern is set up for the width of four cubes. We’re going to do this on the container’s ::before pseudo-element:

.loader::before { 
  content: "";
  width: calc(4 * 100% / 3);
  /*
     Code to create four cubes
  */
}

Now that we have four cubes in a three-cube container, we can justify the cube pattern to the end of the grid container to overflow it, showing the last three cubes:

.loader {
  /* same as before */
  justify-content: end;
}

Here’s what we have so far, with a red outline to show the bounds of the grid container:

Now all we have to do is to move the pseudo-element to the right by adding our animation:

@keyframes load {
  to { transform: translate(calc(100% / 4)); }
}

Did you get the trick of the animation? Let’s finish this off by hiding the overflowing cube pattern and by adding a touch of masking to create that fading effect that the start and the end:

.loader {
  --size: 70px;
  --gap: 15px;  
  
  width: calc(3*(1.353*var(--s) + var(--g)));
  display: grid;
  justify-items: end;
  aspect-ratio: 3;
  overflow: hidden;
  mask: linear-gradient(90deg, #0000, #000 30px calc(100% - 30px), #0000);
}

We can make this a lot more flexible by introducing a variable, --n, to set how many cubes are displayed in the container at once. And since the total number of cubes in the pattern should be one more than --n, we can express that as calc(var(--n) + 1).

Here’s the full thing:

OK, one more 3D loader that’s similar but has the cubes changing color in succession instead of sliding:

We’re going to rely on an animated background with background-blend-mode for this one:

.loader {
  /* ... */
  background:
    linear-gradient(#ff1818 0 0) 0% / calc(100% / 3) 100% no-repeat,
    /* ... */;
  background-blend-mode: multiply;
  /* ... */
  animation: load steps(3) 1.5s infinite;
}
@keyframes load {
  to { background-position: 150%; }
}

I’ve removed the superfluous code used to create the same layout as the last example, but with three cubes instead of four. What I am adding here is a gradient defined with a specific color that blends with the conic gradient, just as we did earlier for the progress bar 3D loader.

From there, it’s animating the background gradient’s background-position as a three-step animation to make the cubes blink colors one at a time.

If you are not familiar with the values I am using for background-position and the background syntax, I highly recommend one of my previous articles and one of my Stack Overflow answers. You will find a very detailed explanation there.

Can we update the number of cubes to make it variables?

Yes, I do have a solution for that, but I’d like you to take a crack at it rather than embedding it here. Take what we have learned from the previous example and try to do the same with this one — then share your work in the comments!

Variations galore!

Like the other three articles in this series, I’d like to leave you with some inspiration to go forth and create your own loaders. Here is a collection that includes the 3D loaders we made together, plus a few others to get your imagination going:

That’s a wrap

I sure do hope you enjoyed spending time making single element loaders with me these past few weeks. It’s crazy that we started with seemingly simple spinner and then gradually added new pieces to work ourselves all the way up to 3D techniques that still only use a single element in the markup. This is exactly what CSS looks like when we harness its powers: scalable, flexible, and reusable.

Thanks again for reading this little series! I’ll sign off by reminding you that I have a collection of more than 500 loaders if you’re looking for more ideas and inspiration.


Single Element Loaders: Going 3D! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/single-element-loaders-going-3d/feed/ 2 366544
Single Element Loaders: The Bars https://css-tricks.com/single-element-loaders-the-bars/ https://css-tricks.com/single-element-loaders-the-bars/#comments Fri, 24 Jun 2022 20:00:29 +0000 https://css-tricks.com/?p=366526 We’ve looked at spinners. We’ve looked at dots. Now we’re going to tackle another common pattern for loaders: bars. And we’re going to do the same thing in this third article of the series as we have the others …


Single Element Loaders: The Bars originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We’ve looked at spinners. We’ve looked at dots. Now we’re going to tackle another common pattern for loaders: bars. And we’re going to do the same thing in this third article of the series as we have the others by making it with only one element and with flexible CSS that makes it easy to create variations.

Let’s start with not one, not two, but 20 examples of bar loaders.

What?! Are you going to detail each one of them? That’s too much for an article!

It might seem like that at first glance! But all of them rely on the same code structure and we only update a few values to create variations. That’s all the power of CSS. We don’t learn how to create one loader, but we learn different techniques that allow us to create as much loader as we want using merely the same code structure.

Let’s make some bars!

We start by defining the dimensions for them using width (or height) with aspect-ratio to maintain proportion:

.bars {
  width: 45px;
  aspect-ratio: 1;
}

We sort of “fake” three bars with a linear gradient on the background — very similar to how we created dot loaders in Part 2 of this series.

.bars {
  width: 45px;
  aspect-ratio: 1;
  --c: no-repeat linear-gradient(#000 0 0); /* we define the color here */
  background: 
    var(--c) 0%   50%,
    var(--c) 50%  50%,
    var(--c) 100% 50%;
  background-size: 20% 100%; /* 20% * (3 bars + 2 spaces) = 100% */
}

The above code will give us the following result:

Like the other articles in this series, we are going to deal with a lot of background trickery. So, if you ever feel like we’re jumping around too fast or feel you need a little more detail, please do check those out. You can also read my Stack Overflow answer where I give a detailed explanation on how all this works.

Animating the bars

We either animate the element’s size or position to create the bar loader. Let’s animate the size by defining the following animation keyframes:

@keyframes load {
  0%   { background-size: 20% 100%, 20% 100%, 20% 100%; }  /* 1 */
  33%  { background-size: 20% 10% , 20% 100%, 20% 100%; }  /* 2 */
  50%  { background-size: 20% 100%, 20% 10% , 20% 100%; }  /* 3 */
  66%  { background-size: 20% 100%, 20% 100%, 20% 10%;  }  /* 4 */
  100% { background-size: 20% 100%, 20% 100%, 20% 100%; }  /* 5 */
}

See what’s happening there? Between 0% and 100%, the animation changes the background-size of the element’s background gradient. Each keyframe sets three background sizes (one for each gradient).

And here’s what we get:

Can you start to imagine all the possible variations we can get by playing with different animation configurations for the sizes or the positions?

Let’s fix the size to 20% 50% and update the positions this time:

.loader {
  width: 45px;
  aspect-ratio: .75;
  --c: no-repeat linear-gradient(#000 0 0);
  background: 
    var(--c),
    var(--c),
    var(--c);
  background-size: 20% 50%;
  animation: load 1s infinite linear;
}
@keyframes load {
  0%   { background-position: 0% 100%, 50% 100%, 100% 100%; } /* 1 */
  20%  { background-position: 0% 50% , 50% 100%, 100% 100%; } /* 2 */
  40%  { background-position: 0% 0%  , 50% 50% , 100% 100%; } /* 3 */
  60%  { background-position: 0% 100%, 50% 0%  , 100% 50%;  } /* 4 */
  80%  { background-position: 0% 100%, 50% 100%, 100% 0%;   } /* 5 */ 
  100% { background-position: 0% 100%, 50% 100%, 100% 100%; } /* 6 */
}

…which gets us another loader!

You’ve probably got the trick by now. All you need is to define a timeline that you translate into a keyframe. By animating the size, the position — or both! — there’s an infinite number of loader possibilities at our fingertips.

And once we get comfortable with such a technique we can go further and use a more complex gradient to create even more loaders.

Expect for the last two examples in that demo, all of the bar loaders use the same underlying markup and styles and different combinations of animations. Open the code and try to visualize each frame independently; you’ll see how relatively trivial it is to make dozens — if not hundreds — of variations.

Getting fancy

Did you remember the mask trick we did with the dot loaders in the second article of this series? We can do the same here!

If we apply all the above logic inside the mask property we can use any background configuration to add a fancy coloration to our loaders.

Let’s take one demo and update it:

All I did is updating all the background-* with mask-* and I added a gradient coloration. As simple as that and yet we get another cool loader.

So there is no difference between the dots and the bars?

No difference! I wrote two different articles to cover as many examples as possible but in both, I am relying on the same techniques:

  1. Gradients to create the shapes (dots or bars or maybe something else)
  2. Animating background-size and/or background-position to create the loader animation
  3. Adding mask to add a touch of colors

Rounding the bars

Let’s try something different this time where we can round the edges of our bars.

Using one element and its ::before and ::after pseudos, we define three identical bars:

.loader {
  --s: 100px; /* control the size */

  display: grid;
  place-items: center;
  place-content: center;
  margin: 0 calc(var(--s) / 2); /* 50px */
}
.loader::before,
.loader::after {
  content: "";
  grid-area: 1/1;
}
.loader,
.loader::before,
.loader::after {
  height: var(--s);
  width: calc(var(--s) / 5); /* 20px */
  border-radius: var(--s);
  transform: translate(calc(var(--_i, 0) * 200%));
}
.loader::before { --_i: -1; }
.loader::after { --_i:  1; }

That gives us three bars, this time without relying on a linear gradient:

Now the trick is to fill in those bars with a lovely gradient. To simulate a continuous gradient, we need to play with background properties. In the above figure, the green area defines the area covered by the loader. That area should be the size of the gradient and, if we do the math, it’s equal to multiplying both sides labeled S in the diagram, or background-size: var(--s) var(--s).

Since our elements are individually placed, we need to update the position of the gradient inside each one to make sure all of them overlap. This way, we’re simulating one continuous gradient even though it’s really three of them.

For the main element (placed at the center), the background needs to be at the center. We use the following:

.loader {
  /* etc. */
  background: linear-gradient() 50% / var(--s) var(--s);
}

For the pseudo-element on the left, we need the background on the left

.loader::before {
  /* etc. */
  background: linear-gradient() 0% / var(--s) var(--s);
}

And for the pseudo on the right, the background needs to be positioned to the right:

.loader::after {
  background: linear-gradient() 100% / var(--s) var(--s);
}

Using the same CSS variable, --_i, that we used for the translate, we can write the code like this:

.loader {
  --s: 100px; /* control the size */
  --c: linear-gradient(/* etc. */); /* control the coloration */

  display: grid;
  place-items: center;
  place-content: center;
}
.loader::before,
.loader::after{
  content: "";
  grid-area: 1/1;
}
.loader,
.loader::before,
.loader::after{
  height: var(--s);
  width: calc(var(--s) / 5);
  border-radius: var(--s);
  background: var(--c) calc(50% + var(--_i, 0) * 50%) / var(--s) var(--s);
  transform: translate(calc(var(--_i, 0) * 200%));
}
.loader::before { --_i: -1; }
.loader::after  { --_i:  1; }

Now, all we have to do is to animate the height and add some delays! Here are three examples where all that’s different are the colors and sizes:

Wrapping up

I hope so far you are feeling super encouraged by all the powers you have to make complex-looking loading animations. All we need is one element, either gradients or pseudos to draw the bars, then some keyframes to move things around. That’s the entire recipe for getting an endless number of possibilities, so go out and starting cooking up some neat stuff!

Until the next article, I will leave you with a funny collection of loaders where I am combining the dots and the bars!


Single Element Loaders: The Bars originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/single-element-loaders-the-bars/feed/ 8 366526
Single Element Loaders: The Dots https://css-tricks.com/single-element-loaders-the-dots/ https://css-tricks.com/single-element-loaders-the-dots/#comments Fri, 17 Jun 2022 14:47:55 +0000 https://css-tricks.com/?p=366342 We’re looking at loaders in this series. More than that, we’re breaking down some common loader patterns and how to re-create them with nothing more than a single div. So far, we’ve picked apart the classic spinning loader. Now, …


Single Element Loaders: The Dots originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We’re looking at loaders in this series. More than that, we’re breaking down some common loader patterns and how to re-create them with nothing more than a single div. So far, we’ve picked apart the classic spinning loader. Now, let’s look at another one you’re likely well aware of: the dots.

Dot loaders are all over the place. They’re neat because they usually consist of three dots that sort of look like a text ellipsis (…) that dances around.

Our goal here is to make this same thing out of a single div element. In other words, there is no one div per dot or individual animations for each dot.

That example of a loader up above is made with a single div element, a few CSS declarations, and no pseudo-elements. I am combining two techniques using CSS background and mask. And when we’re done, we’ll see how animating a background gradient helps create the illusion of each dot changing colors as they move up and down in succession.

The background animation

Let’s start with the background animation:

.loader {
  width: 180px; /* this controls the size */
  aspect-ratio: 8/5; /* maintain the scale */
  background: 
    conic-gradient(red   50%, blue   0) no-repeat, /* top colors */
    conic-gradient(green 50%, purple 0) no-repeat; /* bottom colors */
  background-size: 200% 50%; 
  animation: back 4s infinite linear; /* applies the animation */
}

/* define the animation */
@keyframes back {
  0%,                       /* X   Y , X     Y */
  100% { background-position: 0%   0%, 0%   100%; }
  25%  { background-position: 100% 0%, 0%   100%; }
  50%  { background-position: 100% 0%, 100% 100%; }
  75%  { background-position: 0%   0%, 100% 100%; }
}

I hope this looks pretty straightforward. What we’ve got is a 180px-wide .loader element that shows two conic gradients sporting hard color stops between two colors each — the first gradient is red and blue along the top half of the .loader, and the second gradient is green and purple along the bottom half.

The way the loader’s background is sized (200% wide), we only see one of those colors in each half at a time. Then we have this little animation that pushes the position of those background gradients left, right, and back again forever and ever.

When dealing with background properties — especially background-position — I always refer to my Stack Overflow answer where I am giving a detailed explanation on how all this works. If you are uncomfortable with CSS background trickery, I highly recommend reading that answer to help with what comes next.

In the animation, notice that the first layer is Y=0% (placed at the top) while X is changes from 0% to 100%. For the second layer, we have the same for X but Y=100% (placed at the bottom).

Why using a conic-gradient() instead of linear-gradient()?

Good question! Intuitively, we should use a linear gradient to create a two-color gradients like this:

linear-gradient(90deg, red 50%, blue 0)

But we can also reach for the same using a conic-gradient() — and with less of code. We reduce the code and also learn a new trick in the process!

Sliding the colors left and right is a nice way to make it look like we’re changing colors, but it might be better if we instantly change colors instead — that way, there’s no chance of a loader dot flashing two colors at the same time. To do this, let’s change the animation‘s timing function from linear to steps(1)

The loader dots

If you followed along with the first article in this series, I bet you know what comes next: CSS masks! What makes masks so great is that they let us sort of “cut out” parts of a background in the shape of another element. So, in this case, we want to make a few dots, show the background gradients through the dots, and cut out any parts of the background that are not part of a dot.

We are going to use radial-gradient() for this:

.loader {
  width: 180px;
  aspect-ratio: 8/5;
  mask:
    radial-gradient(#000 68%, #0000 71%) no-repeat,
    radial-gradient(#000 68%, #0000 71%) no-repeat,
    radial-gradient(#000 68%, #0000 71%) no-repeat;
  mask-size: 25% 40%; /* the size of our dots */
}

There’s some duplicated code in there, so let’s make a CSS variable to slim things down:

.loader {
  width: 180px;
  aspect-ratio: 8/5;
  --_g: radial-gradient(#000 68%, #0000 71%) no-repeat;
  mask: var(--_g),var(--_g),var(--_g);
  mask-size: 25% 40%;
}

Cool cool. But now we need a new animation that helps move the dots up and down between the animated gradients.

.loader {
  /* same as before */
  animation: load 2s infinite;
}

@keyframes load {      /* X  Y,     X   Y,    X   Y */
  0%     { mask-position: 0% 0%  , 50% 0%  , 100% 0%; } /* all of them at the top */
  16.67% { mask-position: 0% 100%, 50% 0%  , 100% 0%; }
  33.33% { mask-position: 0% 100%, 50% 100%, 100% 0%; }
  50%    { mask-position: 0% 100%, 50% 100%, 100% 100%; } /* all of them at the bottom */
  66.67% { mask-position: 0% 0%  , 50% 100%, 100% 100%; }
  83.33% { mask-position: 0% 0%  , 50% 0%  , 100% 100%; }
  100%   { mask-position: 0% 0%  , 50% 0%  , 100% 0%; } /* all of them at the top */
}

Yes, that’s a total of three radial gradients in there, all with the same configuration and the same size — the animation will update the position of each one. Note that the X coordinate of each dot is fixed. The mask-position is defined such that the first dot is at the left (0%), the second one at the center (50%), and the third one at the right (100%). We only update the Y coordinate from 0% to 100% to make the dots dance.

Dot loader dots with labels showing their changing positions.

Here’s what we get:

Now, combine this with our gradient animation and magic starts to happen:

Dot loader variations

The CSS variable we made in the last example makes it all that much easier to swap in new colors and create more variations of the same loader. For example, different colors and sizes:

What about another movement for our dots?

Here, all I did was update the animation to consider different positions, and we get another loader with the same code structure!

The animation technique I used for the mask layers can also be used with background layers to create a lot of different loaders with a single color. I wrote a detailed article about this. You will see that from the same code structure we can create different variations by simply changing a few values. I am sharing a few examples at the end of the article.

Why not a loader with one dot?

This one should be fairly easy to grok as I am using the same technique but with a more simple logic:

Here is another example of loader where I am also animating radial-gradient combined with CSS filters and mix-blend-mode to create a blobby effect:

If you check the code, you will see that all I am really doing there is animating the background-position, exactly like we did with the previous loader, but adding a dash of background-size to make it look like the blob gets bigger as it absorbs dots.

If you want to understand the magic behind that blob effect, you can refer to these interactive slides (Chrome only) by Ana Tudor because she covers the topic so well!

Here is another dot loader idea, this time using a different technique:

This one is only 10 CSS declarations and a keyframe. The main element and its two pseudo-elements have the same background configuration with one radial gradient. Each one creates one dot, for a total of three. The animation moves the gradient from top to bottom by using different delays for each dot..

Oh, and take note how this demo uses CSS Grid. This allows us to leverage the grid’s default stretch alignment so that both pseudo-elements cover the whole area of their parent. No need for sizing! Push the around a little with translate() and we’re all set.

More examples!

Just to drive the point home, I want to leave you with a bunch of additional examples that are really variations of what we’ve looked at. As you view the demos, you’ll see that the approaches we’ve covered here are super flexible and open up tons of design possibilities.

Next up…

OK, so we covered dot loaders in this article and spinners in the last one. In the next article of this four-part series, we’ll turn our attention to another common type of loader: the bars. We’ll take a lot of what we learned so far and see how we can extend them to create yet another single element loader with as little code and as much flexibility as possible.


Single Element Loaders: The Dots originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/single-element-loaders-the-dots/feed/ 5 366342
Single Element Loaders: The Spinner https://css-tricks.com/single-element-loaders-the-spinner/ https://css-tricks.com/single-element-loaders-the-spinner/#comments Fri, 10 Jun 2022 14:26:06 +0000 https://css-tricks.com/?p=366266 Making CSS-only loaders is one of my favorite tasks. It’s always satisfying to look at those infinite animations. And, of course, there are lots of techniques and approaches to make them — no need to look further than CodePen to …


Single Element Loaders: The Spinner originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Making CSS-only loaders is one of my favorite tasks. It’s always satisfying to look at those infinite animations. And, of course, there are lots of techniques and approaches to make them — no need to look further than CodePen to see just how many. In this article, though, we will see how to make a single element loader writing as little code as possible.

I have made a collection of more than 500 single div loaders and in this four-part series, I am going to share the tricks I used to create many of them. We will cover a huge number of examples, showing how small adjustments can lead to fun variations, and how little code we need to write to make it all happen!

Single-Element Loaders series:

  1. Single Element Loaders: The Spinner — you are here
  2. Single Element Loaders: The Dots
  3. Single Element Loaders: The Bars
  4. Single Element Loaders: Going 3D

For this first article, we are going to create a one of the more common loader patterns: spinning bars:

Here’s the approach

A trivial implementation for this loader is to create one element for each bar wrapped inside a parent element (for nine total elements), then play with opacity and transform to get the spinning effect.

My implementation, though, requires only one element:

<div class="loader"></div>

…and 10 CSS declarations:

.loader {
  width: 150px; /* control the size */
  aspect-ratio: 1;
  display: grid;
  mask: conic-gradient(from 22deg, #0003, #000);
  animation: load 1s steps(8) infinite;
}
.loader,
.loader:before {
  --_g: linear-gradient(#17177c 0 0) 50%; /* update the color here */
  background: 
    var(--_g)/34% 8%  space no-repeat,
    var(--_g)/8%  34% no-repeat space;
}
.loader:before {
  content: "";
  transform: rotate(45deg);
}
@keyframes load {
  to { transform: rotate(1turn); }
}

Let’s break that down

At first glance, the code may look strange but you will see that it’s more simple than what you might think. The first step is to define the dimension of the element. In our case, it’s a 150px square. We can put aspect-ratio to use so the element stays square no matter what.

.loader {
  width: 150px; /* control the size */
  aspect-ratio: 1; /* make height equal to width */
}

When building CSS loaders, I always try to have one value for controlling the overall size. In this case, it’s the width and all the calculations we cover will refer to that value. This allows me to change a single value to control the loader. It’s always important to be able to easily adjust the size of our loaders without the need to adjust a lot of additional values.

Next, we will use gradients to create the bars. This is the trickiest part! Let’s use one gradient to create two bars like the below:

background: linear-gradient(#17177c 0 0) 50%/34% 8% space no-repeat;
Showing a space between two gradient lines for a single element loader.

Our gradient is defined with one color and two color stops. The result is a solid color with no fading or transitions. The size is equal to 34% wide and 8% tall. It’s also placed in the center (50%). The trick is the use of the keyword value space — this duplicates the gradient, giving us two total bars.

From the specification:

The image is repeated as often as will fit within the background positioning area without being clipped and then the images are spaced out to fill the area. The first and last images touch the edges of the area.

I am using a width equal to 34% which means we cannot have more than two bars (3*34% is greater than 100%) but with two bars we will have empty spaces (100% - 2 * 34% = 32%). That space is placed in the center between the two bars. In other words, we use a width for the gradient that is between 33% and 50% to make sure we have at least two bars with a little bit of space between them. The value space is what correctly places them for us.

We do the same and make a second similar gradient to get two more bars at the top and bottom, which give us a background property value of:

background: 
 linear-gradient(#17177c 0 0) 50%/34% 8%  space no-repeat,
 linear-gradient(#17177c 0 0) 50%/8%  34% no-repeat space;

We can optimize that using a CSS variable to avoid repetition:

--_g: linear-gradient(#17177c 0 0) 50%; /* update the color here */
background: 
 var(--_g)/34% 8%  space no-repeat,
 var(--_g)/8%  34% no-repeat space;

So, now we have four bars and, thanks to CSS variables, we can write the color value once which makes it easy to update later (like we did with the size of the loader).

To create the remaining bars, let’s tap into the .loader element and its ::before pseudo-element to get four more bars for a grand total of eight in all.

.loader {
  width: 150px; /* control the size */
  aspect-ratio: 1;
  display: grid;
}
.loader,
.loader::before {
  --_g: linear-gradient(#17177c 0 0) 50%; /* update the color here */
  background: 
    var(--_g)/34% 8%  space no-repeat,
    var(--_g)/8%  34% no-repeat space;
}
.loader::before {
  content: "";
  transform: rotate(45deg);
}

Note the use of display: grid. This allows us to rely on the grid’s default stretch alignment to make the pseudo-element cover the whole area of its parent; thus there’s no need to specify a dimension on it — another trick that reduces the code and avoid us to deal with a lot of values!

Now let’s rotate the pseudo-element by 45deg to position the remaining bars. Hover the following demo to see the trick:

Setting opacity

What we’re trying to do is create the impression that there is one bar that leaves a trail of fading bars behind it as it travels a circular path. What we need now is to play with the transparency of our bars to make that trail, which we are going to do with CSS mask combined with a conic-gradient as follows:

mask: conic-gradient(from 22deg,#0003,#000);

To better see the trick, let’s apply this to a full-colored box:

The transparency of the red color is gradually increasing clockwise. We apply this to our loader and we have the bars with different opacity:

Radial gradient plus, spinner bars equals spinner bars with gradients.

In reality, each bar appears to fade because it’s masked by a gradient and falls between two semi-transparent colors. It’s hardly noticeable when this runs, so it’s sort of like being able to say that all the bars have the same color with a different level of opacity.

The rotation

Let’s apply a rotation animation to get our loader. Note, that we need a stepped animation and not a continuous one that’s why I am using steps(8). 8 is nothing but the number of the bars, so that value can be changed depending on how many bars are in use.

.loader {
  animation: load 3s steps(8) infinite;
}

/* Same as before: */
@keyframes load {
  to { transform: rotate(1turn) }
}

That’s it! We have our loader with only one element and a few lines of CSS. We can easily control its size and color by adjusting one value.

Since we only used the ::before pseudo-element, we can add four more bars by using ::after to end with 12 bars in total and almost the same code:

We update the rotation of our pseudo-elements to consider 30deg and 60deg instead of 45deg while using an twelve-step animation, rather than eight. I also decreased the height to 5% instead of 8% to make the bars a little thinner.

Notice, too, that we have grid-area: 1/1 on the pseudo-elements. This allows us to place them in the same area as one another, stacked on top of each other.

Guess what? We can reach for the same loader using another implementation:

Can you figure out the logic behind the code? Here is a hint: the opacity is no longer handled with a CSS mask but inside the gradient and is also using the opacity property.

Why not dots instead?

We can totally do that:

If you check the code, you will see that we’re now working with a radial gradient instead of a linear one. Otherwise, the concept is exactly the same where the mask creates the impression of opacity, but we made the shapes as circles instead of lines.

Below is a figure to illustrate the new gradient configuration:

Showing placement of dots in the single-element loader.

If you’re using Safari, note that the demo may be buggy. That’s because Safari currently lacks support for the at syntax in radial gradients. But we can reconfigure the gradient a bit to overcome that:

.loader,
.loader:before,
.loader:after {
  background:
    radial-gradient(
      circle closest-side,
      currentColor 90%,
      #0000 98%
    ) 
    50% -150%/20% 80% repeat-y,
    radial-gradient(
      circle closest-side,
      currentColor 90%,
      #0000 98%
    ) 
    -150% 50%/80% 20% repeat-x;
}

More loader examples

Here is another idea for a spinner loader similar to the previous one.

For this one, I am only relying on background and mask to create the shape (no pseudo-elements needed). I am also defining the configuration with CSS variables to be able to create a lot of variations from the same code — another example of just the powers of CSS variables. I wrote another article about this technique if you want to more details.

Note that some browsers still rely on a -webkit- prefix for mask-composite with its own set of values, and will not display the spinner in the demo. Here is a way to do it without mast-composite for more browser support.

I have another one for you:

For this one, I am using a background-color to control the color, and use mask and mask-composite to create the final shape:

Different steps for applying a master to a element in the shape of a circle.

Before we end, here are some more spinning loaders I made a while back. I am relying on different techniques but still using gradients, masks, pseudo-element, etc. It could be a good exercise to figure out the logic of each one and learn new tricks at the same time. This said, if you have any question about them, the comment section is down below.

Wrapping up

See, there’s so much we can do in CSS with nothing but a single div, a couple of gradients, pseudo-elements, variables. It seems like we created a whole bunch of different spinning loaders, but they’re all basically the same thing with slight modifications.

This is only the the beginning. In this series, we will be looking at more ideas and advanced concepts for creating CSS loaders.

Single-Element Loaders series:

  1. Single Element Loaders: The Spinner — you are here
  2. Single Element Loaders: The Dots
  3. Single Element Loaders: The Bars
  4. Single Element Loaders: Going 3D

Single Element Loaders: The Spinner originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/single-element-loaders-the-spinner/feed/ 4 366266
Add a CSS Lens Flare to Photos for a Bright Touch https://css-tricks.com/add-a-css-lens-flare-to-photos-for-a-bright-touch/ https://css-tricks.com/add-a-css-lens-flare-to-photos-for-a-bright-touch/#comments Tue, 12 Apr 2022 17:10:13 +0000 https://css-tricks.com/?p=364385 I’m a big fan of movies by J.J. Abrams. I enjoy their tight plots, quippy dialog, and of course: anamorphic lens flares. Filmmakers like Abrams use lens flare to add a dash of ‘homemade’ realism to their movies, …


Add a CSS Lens Flare to Photos for a Bright Touch originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’m a big fan of movies by J.J. Abrams. I enjoy their tight plots, quippy dialog, and of course: anamorphic lens flares. Filmmakers like Abrams use lens flare to add a dash of ‘homemade’ realism to their movies, a technique we can easily recreate in tools like Photoshop, then add to our sites as raster images.

But what if we wanted to apply the same lens flare look without the use of photo editing tools? We can create a CSS lens flare to add a cinematic touch to our gallery images, background photos, or user profiles.

There are different types of flares in photography. The one we’re working with is known as artifacts, as they leave behind little blotches of light that take the shape of a camera’s aperture where the light enters and reflects off the surface of the lens.

A diagram showing how light enters a camera lens at various angles to create a flare.
Source: Wikipedia

Here’s a good example of the sort of lens flare we’re going to make, pulled straight from a J.J. Abrams movie still, naturally:

An example of the CSS lens flare we are making, showing a flare to the right of an actor in a still from the 2009 Star Trek movie
Star Trek (2009)

There are a few parts to the lens flare above. Let’s list them out so we know what we’re aiming for:

  1. The center light source appears as a glowing ball of light.
  2. There are some horizontal elliptical light streaks — rays of light that are distorted and blurred, resulting in elongated ellipses.
  3. Random rays of light shoot off from the center light source at various angles.

We start with the HTML elements below that map to our flare components. There is a central light source and two off-diagonal circular flares, three horizontal lens flares, and three conical ray-like flares.

<div class="lens-center"></div>
    <div class="circle-1"></div>
    <div class="circle-2"></div>
    <div class="left-flare horizontal-flare"></div>
    <div class="right-flare horizontal-flare"></div>
    <div class="full-flare horizontal-flare"></div>
    <div class="conic-1"></div>
    <div class="conic-2"></div>
    <div class="conic-3"></div>
</div>

Lastly, in order for our lens flare to be believably superimposed on an image, its center light source has to be adjustable. This way, we can place it over a believable existing light source on a picture and not overlap with any faces.

The background and light source of a CSS lens flare

Let’s start with a black background and central light source for our CSS lens flare. Most gradients on the web are linear gradients with solid-color transitions, but we can apply alpha channels to them which is actually a nice way to produce a glowing effect. A circular-shaped radial gradient with multiple layers of semi-transparent colors gives us a good camera center effect.

background: radial-gradient(
  closest-side circle at center,
  hsl(4 5% 100% / 100%) 0%,
  hsl(4 5% 100% / 100%) 15%,
  hsl(4 10% 70% / 70%) 30%,
  hsl(4 0% 50% / 30%) 55%,
  hsl(4 0% 10% / 5%) 75%,
  transparent 99
);
filter: blur(4px);

Curious about that HSL syntax? It’s new and appears to be the future direction of defining alpha transparency in all CSS color functions.

Notice we’re using a CSS blur filter in there to make the gradients look a bit closer to layers of diffused light.

Now that we know how to add circular flares, we will also add a larger, diffused flare behind the light source, as well as three additional flares at a 45deg angle from the center, to give the effect a more realistic look.

Setting up horizontal light streaks

Let’s start with horizontal flares. There are a few options we can take, a very elongated ellipse gradient would be the simplest approach. However, I’ve noticed that horizontal lens flares are usually less symmetrical than the ones in my reference photos, so I wanted to make mine a little less symmetrical as well.

Luckily, radial gradients have an optional location argument in CSS. We can create two slightly differently-sized left and right portions of the same horizontal flare, and with slightly different colors. We can also add an opacity filter to make the area where the horizontal flares join the center to make the flare less jarring.

background: radial-gradient(
  closest-side circle at center,
  transparent 50%,
  hsl(4 10% 70% / 40%) 90%,
  transparent 100%
);
filter: blur(5px);

While we are at it, let’s also add a single full elongated elliptical bottom flare three-quarters of the way down the viewport for another touch of “realism.”

Creating the diffused rays of light

With both the radial and horizontal flares in place, all we have left are the angled rays of light shooting off from the light source. We could add additional elliptical radial gradients then skew and translate the container to get a close approximation. But we also have a CSS gradient that’s already made for the job, the conic gradient. Below is an example that gives us a 7deg conic gradient at a 5deg offset from its container’s bottom-right corner.

background: conic-gradient(
  from 5deg at 0% 100%,
  transparent 0deg,
  hsl(4 10% 70% / 30%) 7deg,
  transparent 15deg
);
transform-origin: bottom left;
transform: rotate(-45deg);

We’ll add a few conic gradients centered at our flare center, with various gradient angles of semi-transparent colors. Because conic gradients can show the corner of its container div, we will rotationally transform them using our light source as its origin, resulting in an offset diffused ray filter effect.

Using CSS custom properties for a more flexible lens flare

So far, we’ve created a responsive, but statically-positioned, lens flare effect at a fixed location. It would be difficult to adjust the center of the lens flare without also breaking the horizontal and conic flares around it.

To make the CSS lens flare both adjustable and less brittle, we’ll expose the light source flare’s position, size, and hue via a set of custom properties.

:root {
  --hue: 4;
  --lens-center-size: 40%;
  --lens-spread-size: 80%;
  --lens-left: 55%;
  --lens-top: 15%;
}

While we are at it, we are also going to adjust the flare hue and the size of the horizontal flare height. For horizontal flare width, we use CSS variable overloading to make them adjustable on their own; otherwise, we fall back to the light source flare center or the image center.

.left-flare {
  width: var(--left-flare-width, var(--lens-left, 50%));
}

This is what the completed CSS lens flare effect looks like with a photo background and the lens flare moved up so the light source location looks believable. Go ahead, add your own photo to see how it works in different contexts!

Other CSS and non-CSS lens flare examples

This is just one way to create a CSS lens flare, of course. I like this approach because it’s flexible in terms of the color, size, and positioning of the flare and its parts. That makes it more of a reusable component that can be used in many contexts.

Here’s one by Keith Grant that uses a linear gradient as well as CSS custom properties. Then it sprinkles some JavaScript in there to randomize the HSLA values.

Nicholas Guest has another CSS lens flare that applies a box shadow on the ::before and ::after pseudo-elements of a .flare element to get the effect, plus a smidge of jQuery that makes the flare follow the mouse on hover.

This one is made with Canvas and is neat in how the light source follows the mouse on hover while showing how the lens flare artifacts change position as the light source position changes.

The same sort of idea here:

And a fun one that uses GSAP, Canvas, and a library called JS.LensFlare:

How would you approach a CSS lens flare effect? Share in the comments!


Add a CSS Lens Flare to Photos for a Bright Touch originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/add-a-css-lens-flare-to-photos-for-a-bright-touch/feed/ 1 364385
Tricks to Cut Corners Using CSS Mask and Clip-Path Properties https://css-tricks.com/cut-corners-using-css-mask-and-clip-path-properties/ https://css-tricks.com/cut-corners-using-css-mask-and-clip-path-properties/#comments Wed, 30 Mar 2022 16:35:08 +0000 https://css-tricks.com/?p=364279 We recently covered creating fancy borders with CSS mask properties, and now we are going to cut the corners with CSS mask and clip-path! A lot of techniques exist to cut different shapes from the corners of any element. …


Tricks to Cut Corners Using CSS Mask and Clip-Path Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We recently covered creating fancy borders with CSS mask properties, and now we are going to cut the corners with CSS mask and clip-path! A lot of techniques exist to cut different shapes from the corners of any element. In this article, we will consider modern techniques to create unique corner shapes while trying to work from reusable code that allows us to produce different results by adjusting variables.

Check this online tool to get an idea of what we are building. It’s a CSS generator where you select the shape, the corners, and the size then you get the code in no time!

We mainly have two types of cuts: a circular one and an angled one. For each, we can get the full shape or the border-only shape, not to mention that we can select the corners we want to cut. A lot of combinations!

Like in the previous article, we will make lots of use of the CSS mask property. So, if you are not familiar with it, I recommend reading the quick primer I wrote before continuing.

Circular cut-out

For a circular or rounded cut, we will use radial-gradient(). To cut four corners, the logical solution is to create four gradients, one for each corner:

Each gradient is taking a quarter of the element’s dimensions. The syntax of the gradient is self-explanatory:

radial-gradient(circle 30px at top left, #0000 98%, red) top left;

Translated, this renders a circle at the top-left corner with a 30px radius. The main color is transparent (#0000) and the remaining is red. The whole gradient is also placed so that it starts at the element’s top-left corner. Same logic for the three other gradients. The keyword circle can be omitted since we explicitly specified one value for the radius.

Like I did in the previous article, I will be using slightly bigger or smaller values this time around in order to avoid bad visual result. Here, I am using 98% instead of 100% to avoid jagged edges and 51% instead of 50% to create an overlap between gradients and avoid white spaces. This logic will follow throughout this article. In fact, you will find that adding or removing 1% or 1deg typically results in a nice visual.

We apply this to the CSS mask property and we are done!

We can actually optimize that code a little:

--g: #0000 98%,#000;
--r: 30px;
mask:
  radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0,
  radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0,
  radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%,
  radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%;
mask-size: 51% 51%;
mask-repeat: no-repeat;

This way, we use custom properties for the redundant values and, as a personal preference, I am using numeric values for the positions instead of keywords.

In the generator, I will use the following syntax:

--g: #0000 98%,#000;
--r: 30px;
mask:
  radial-gradient(var(--r) at 0    0   ,var(--g)) 0    0   /51% 51% no-repeat,
  radial-gradient(var(--r) at 100% 0   ,var(--g)) 100% 0   /51% 51% no-repeat,
  radial-gradient(var(--r) at 0    100%,var(--g)) 0    100%/51% 51% no-repeat,
  radial-gradient(var(--r) at 100% 100%,var(--g)) 100% 100%/51% 51% no-repeat;

The shorthand syntax is easier to generate plus the whole value can be used as one custom property.

Can we use fewer gradients if we want?

Sure! One gradient can do the job. Hover the below to see the trick:

Here, we define one radial-gradient() with no size (by default it is 100% height and 100% width). This gives us a hole in the center. We translate/move the gradient by half the width and height of the image to move the hole to one corner. Since, by default, the CSS mask repeats, we get the same on each corner. We have four cut corners with only one gradient!

The only drawback of this method is that we need to know the width and height of the element in advance.

Can’t we use -50% instead of half the width and height?

Unfortunately, we’re unable to do that here because percentages doesn’t behave the same as pixel values when used with the CSS mask-position property. They’re tricky.

I have a detailed Stack Overflow answer that explains the difference. It deals with background-position but the same logic applies to the CSS mask-position property.

However, we can use some tricks to make it work with percentage values and without the need to know the width or the height. When a gradient (or a background layer) has a width and height equal to the element, we cannot move it using percentage values. So we need to change its size!

I will define a size equal to 99.5% 99.5%. I am reducing 0.5% from the width and the height to have a value different from 100% and at the same time keep the same visual result since we won’t notice a big difference between 100% and 99.5%. Now that our gradient has a size different from 100% we can move it using percentage values.

I will not detail all the math, but to move it by half the width and the height we need to use this equation:

100% * (50/(100 - 99.5)) = 100% * 100 = 10000%

It’s a strange value but it does the job:

mask: radial-gradient(30px,#0000 98%,#000) 10000% 10000%/99.5% 99.5%

As you can see, the trick works just fine. Whatever the size of the element is, we can cut four corners using only one gradient. However, this method has a small drawback when the width or the height of the element is a decimal value. Here is an example with an image having a width equal to 150.5px:

The use of 99.5% combined with 150.5px will create rounding issues that will break the calculation, resulting in the mask being misaligned. So, use this method with caution.

Guess what? There is a solution with one gradient and no rounding issue. Using the following code:

mask: radial-gradient(30px at 30px 30px,#0000 98%,#000) -30px -30px

The trick is to create a hole placed at the top left corner and by moving it with a negative offset we cover the four corners. Hover the below to see the trick.

This method is perfect as it uses one gradient and has no rounding issue but it has one drawback. The value of the radius is used 5 times. Not a big deal as we can use a custom property for it:

--r: 30px;
mask: radial-gradient(var(--r) at var(--r) var(--r),#0000 98%,#000) calc(-1*var(--r)) calc(-1*var(--r))

Let’s quickly recap the three methods we just covered:

  • The first method uses four gradients and has no drawbacks as far as usage. Sure, it’s verbose but it works with any kind of element and size.
  • The second method uses one gradient, but it can break in some particular cases. It’s suitable with fixed-size elements. It’s ok to use, but maybe less frequently.
  • The third method uses one gradient and has no rounding issue. It’s the perfect method among all of them but it requires using the radius many time within the gradient value.

The generator only supports the first and third methods.

Now that we saw the case with all the corners, let’s disable some of them. Using the first method, any corner we want to keep uncut we simply remove its gradient and adjust the size of what remains.

To disable the top-right corner:

  • We remove the top-right gradient (the blue one).
  • We have an empty corner, so we increase the size of the red gradient (or the purple one) to cover that leftover space.

Done!

You probably see just how many possibilities and combinations we can do here. If we want to cut N corners (where N ranges from 1 to 4), we use N gradients. All we need is to correctly set the size of each one to leave no space.

What about the other methods where there’s only one gradient? We will need another gradient! Those two methods use only one radial-gradient() to cut the corners, so we will rely on another gradient to “hide” the cut. We can use a conic-gradient() with four sections for this task:

conic-gradient(red 25%, blue 0 50%, green 0 75%, purple 0)

We add it on the top of the radial gradient to get the following:

The conic-gradient() covers the radial-gradient() and no corner is cut. Let’s change one color in the conic-gradient() to transparent. The one at the top-right, for example:

Did you see that? We revealed one corner of the radial-gradient() and we end with one cut corner!

Now let’s do the same thing, but for the bottom-left corner.

I think you probably get the trick by now. By changing the colors of the conic-gradient() from opaque to transparent, we reveal the corners we want to cut and gain all kinds of possible combinations.

Now you are probably wondering which method you have to use and when. As I said, I don’t recommend the second method a lot due to the rounding issue but you have to use the two others based on the number of cut-outs.

To cut four corners, the first method requires four gradients while the third one requires only one gradient so we use the latter. To cut one corner, the first method requires one gradient while the third one requires two gradients so you use the first one. To cut two corners, both use two gradients, and to cut three corners one method will use three gradients and the other one only two.

By picking the adequate method for each case, we don’t need more than two gradients in total. I have detailed all the methods but in the end, you should pick the optimized code for each case.

Circular border-only cut-out

Let’s make the border-only version of the previous shape. In other words, we achieve the same shape but knock out the fill so all we’re left with is a border of the shape.

This is a bit tricky because we have different cases with different code. Fair warning, I will be using a lot of gradients here while finding opportunities to trim the number of them.

It should be noted that we will consider a pseudo-element in this case. Showing only the border means we need to hide the inner “fill” of the shape. Applying this to the main element will also hide the content — that’s why this is a nice use case for a pseudo-element.

One cut corner

This one needs one radial gradient and two conic gradients:

The first example illustrates the radial gradient (in red) and both conic gradients (in blue and green). In the second example, we apply all of them inside the CSS mask property to create the border-only shape with one cut corner.

Here’s a diagram of the game plan.

As the diagram shows, the radial-gradient() creates the quarter of a circle and each conic-gradient() creates two perpendicular segments to cover two sides. It should be noted that overlapping gradients is not an issue since we are not going to change the CSS mask-composite property value.

Using the same code an adjusting a few variables, we can get the shape for the other corners.

Two cut corners

For the two-corner configuration we have two situations taking place.

In the first situation, there are two opposite corners where we need two radial gradients and two conic gradients.

The configuration is almost the same as cutting only one corner: we add an extra gradient and update a few variables.

In the second situation, there are two adjacent corners and, in this case, we need one radial gradient, one conic gradient, and one linear gradient.

“Wait!” you might exclaim. “How come the conic gradient covers three sides?” If you check the code, notice the repeat-y. In all of the examples, we always used no-repeat, but for this we can repeat one of them to cover more sides and reduce the number of gradients we use.

Here is an example with only the conic-gradient() to understand the repetition. The trick is to have a height equal to 100% minus the border size so that the gradient fills that space when repeating, which covers the third side in the process.

You are probably wondering how one radial gradient is cutting two corners. To do this, we create half a circle that we place at the top left corner. Then by using a negative offset we cut two adjacent corners. Hover the below to understand the trick.

Three cut corners

For this configuration, we need two radial gradients, one conic gradient, and two linear gradients.

Four corners cut

It takes one radial gradient and two linear gradients to cut all four corners.

I can hear you screaming, “How the heck am I supposed to memorize all these cases?!” You don’t need to memorize anything since you can easily generate the code for each case using the online generator. All you need is to understand the overall trick rather than each individual case. That’s why I’ve only gone into fine detail on the first configurations — the rest are merely iterations that tweak the initial foundation of the trick.

Notice there’s a general pattern we’ve been following throughout the examples:

  1. We add a radial-gradient() on the corners we want to cut.
  2. We fill the sides using either a conic-gradient() or a linear-gradient() to create the final shape.

It should be noted that we can find different ways to create the same shape. What I am showing in this post are the methods I found to be best after trying lots of other ideas. You may have a different approach you consider to be better! If so, definitely share it in the comments!

Angled cut-out

Let’s tackle another type of cut shape: the angled cut.

We have two parameters: the size and angle of the cut. To get the shape, we need a conic-gradient() for each corner. This configuration is very similar to the example that kicked off this article.

Here is an illustration of one corner to understand the trick:

The difference between each corner is an extra offset of 90deg in from and the at position. The full code is like below:

--size: 30px;
--angle: 130deg;

--g: #0000 var(--angle), #000 0;
mask:
  conic-gradient(from calc(var(--angle)/-2 -  45deg) 
    at top    var(--size) left  var(--size),var(--g)) top left,
  conic-gradient(from calc(var(--angle)/-2 + 45deg) 
    at top    var(--size) right var(--size),var(--g)) top right,
  conic-gradient(from calc(var(--angle)/-2 - 135deg) 
    at bottom var(--size) left  var(--size),var(--g)) bottom left,
  conic-gradient(from calc(var(--angle)/-2 + 135deg) 
    at bottom var(--size) right var(--size),var(--g)) bottom right;
mask-size: 51% 51%;
mask-repeat: no-repeat;

If we want to disable one corner, we remove the conic-gradient() for that corner and update the size of another one to fill the remaining space exactly like we did with the circular cut. Here’s how that looks for one corner:

We can do the exact same thing for all the other corners to get the same effect.

In addition to CSS mask, we can also use the CSS clip-path property to cut the corners. Each corner can be defined with three points.

Zooming in on a corner of the shape showing the three points that form the angled cut.
The shape consists of two points at each end of the cut, and one between them to form the angle.

The other corners will have the same value with an offset of 100%. This gives us the final code with a total of 12 points — three per corner.

/* I will define T = [1-tan((angle-90)/2)]*size */
clip-path: polygon(
  /* Top-left corner */
  0 T, size size,0 T, /* OR 0 0 */
  /* Top-right corner */
  calc(100% - T) 0,calc(100% - size) size,100% T, /* OR  100% 0 */
  /* Bottom-right corner*/
  100% calc(100% - T),calc(100% - size) calc(100% - size), calc(100% - T) 100%, /* OR 100% 100% */
  /* Bottom-left corner */ 
  T 100%, size calc(100% - size),0 calc(100% - T) /* OR 0 100% */
)

Notice the OR comments in that code. It defines the code we have to consider if we want to disable a particular corner. To cut a corner, we use three points. To uncut a corner, we use one point — which is nothing but the coordinate of that corner.

The 90deg special case

When the angle is equal to 90deg, we can optimize the code of the gradient version and rely on fewer gradients. To cut four corners we can use only one gradient:

--size: 30px;
mask: 
  conic-gradient(at var(--size) var(--size),#000 75%,#0000 0) 
  0 0/calc(100% - var(--size)) calc(100% - var(--size))

This doesn’t remind you of something? It’s exactly similar to the circular cut-out! For the 90deg we have two gradient methods, the first one we detailed previously where each corner is cut with one gradient, and this last method where we cut all the corners using one gradient. I think you know the rest of the story: to uncut some corners we combine the last method with a conic gradient

Two methods with gradients, one with clip-path, we have to add a conic-gradient?! I am lost …

As I said, no need to remember all the methods and tricks. The generator will do the job of generating the code for you. I simply try to make this article as detailed as possible to cover all the possible cases.

Border-only angled cut

Oof, we have reached the last and trickiest shape at last! This one can be achieved with either gradients or clip-path, but let’s go with the clip-path approach.

Things would get complex and verbose if we go with the gradient approach. Here’s a demo that illustrates that point:

There are nine gradients total, and I am still not done with the calculation. As you can tell, the thickness of the border is incorrect, plus the final result is unsatisfying due to the nature of gradients and their anti-aliasing issues. This approach might be a good exercise to push the limit of gradients, but I don’t recommend it in a production environment.

So, back to the clip-path method. We will still wind up with verbose code, but less of a big deal since the generator can do the job for us with a cleaner end result.

Here is an overview of the path. I am adding a small gap to better see the different points but we should have an overlap of points instead.

We have 13 outer points (the ones in black) and 13 inner points (the ones in blue).

The way we calculate the outer points is the same as how we did it for the regular angled cut. For the inner points, however, we need more math. Don’t worry, I’ll spare you some “boring” geometry explanation for this one. I know most of you don’t want it, but in case you need to dig into this, you can check the JavaScript file of the generator to find the code and the math I am using to generate the shape.

The 180deg special case

There’s a special case for the angle cut I want to call out. It’s where we use an angle equal to 180deg. Here’s what that produces:

We have a straight line on the corner so we can optimize the clip-path code. For the full shape, we can use eight points (two points per corner) instead of 12. And for the border-only version, we can use 18 points (nine inner points and outer points) instead of 26. In other words, we can remove the middle point.

The border-only shape can also be made using gradients. But rather than using nine gradients like we did before, we can get away with only four linear gradients and a clean result.

Conclusion

We just combined CSS masks with gradients to create some fancy shapes without resorting to hacks and a lot of code! We also experienced just how much it takes to strike the right balance of code to get the right results. We even learned a few tricks along the way, like changing values by one or even half a unit. CSS is super powerful!

But, as we discussed, the online generator I made is a great place to get the code you need rather than writing it out by hand. I mean, I went through all the work of figuring out how all of this works and I would likely still need to reference this very article to remember how it’s all put together. If you can memorize all of this, kudos! But it’s nice to have a generator to fall back on.


Tricks to Cut Corners Using CSS Mask and Clip-Path Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/cut-corners-using-css-mask-and-clip-path-properties/feed/ 2 364279
Using Different Color Spaces for Non-Boring Gradients https://css-tricks.com/color-spaces-for-more-interesting-css-gradients/ https://css-tricks.com/color-spaces-for-more-interesting-css-gradients/#respond Mon, 07 Feb 2022 21:46:18 +0000 https://css-tricks.com/?p=363102 A little gradient generator tool from Tom Quinonero. You’d think fading one color to another would be an obvious, simple, solved problem — it’s actually anything but!

Tom’s generator does two things that help make a gradient better:

  1. You


Using Different Color Spaces for Non-Boring Gradients originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
A little gradient generator tool from Tom Quinonero. You’d think fading one color to another would be an obvious, simple, solved problem — it’s actually anything but!

Tom’s generator does two things that help make a gradient better:

  1. You can pick an “interpolation space.” Gradients that use the sRGB color space (pretty much all the color stuff we have in CSS today) have a bad habit of going through a gray dead zone, and if you interpolate the gradient in another color space, it can turn out nicer (and yet convert it back to RGB to use today).
  2. Easing the colors, though the use of multiple color-stops, which can result in a less abrupt and more pleasing look.
Showing a color wheel with a line indicating the two colors in a gradient that goes from yellow to light blue. The resulting gradient is at top showing some gray tones as a result of the color space.
See the gray in the middle there?

Different gradient apps with different color spaces

Josh has another similar app, as does Erik Kennedy. So stinkin’ interesting how different gradients are in different color spaces. Think of the color spaces as a physical map where individual colors are points on the map. Gradients are dumb. They just walk straight from one point on the map to the next. The colors underneath their feet as they walk make a massive difference in how the gradient turns out.

To Shared LinkPermalink on CSS-Tricks


Using Different Color Spaces for Non-Boring Gradients originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/color-spaces-for-more-interesting-css-gradients/feed/ 0 363102
Fancy CSS Borders Using Masks https://css-tricks.com/css-borders-using-masks/ https://css-tricks.com/css-borders-using-masks/#comments Wed, 26 Jan 2022 14:26:49 +0000 https://css-tricks.com/?p=362018 Have you ever tried to make CSS borders in a repeating zig-zag pattern? Like where a colored section of a website ends and another differently colored section begins — not with a straight line, but angled zig zags, rounded humps, …


Fancy CSS Borders Using Masks originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Have you ever tried to make CSS borders in a repeating zig-zag pattern? Like where a colored section of a website ends and another differently colored section begins — not with a straight line, but angled zig zags, rounded humps, or waves. There are a number of ways you could do this sort of CSS border, dating all the way back to using a background-image. But we can get more modern and programmatic with it. In this article, we’ll look at some modern CSS mask techniques to achieve the look.

Before we dig into the technical parts, though, let’s take a look at what we are building. I have made a CSS border generator where you can easily generate any kind of border within a few seconds and get the CSS code.

Did you see that? With the CSS mask property and a few CSS gradients, we get a responsive and cool-looking border — all with CSS by itself. Not only this, but such effect can be applied to any element where we can have any kind of coloration (e.g. image, gradient, etc). We get all this without extra elements, pseudo elements, or magic numbers coming from nowhere!

Oh great! All I have to do is to copy/paste code and it’s done!

True, but it’s good to understand the logic to be able to manually adjust the code if you need to.

Masking things

Since all our effects rely on the CSS mask property, let’s take a quick refresh on how it works. Straight from the spec:

The effect of applying a mask to a graphical object is as if the graphical object will be painted onto the background through a mask, thus completely or partially masking out parts of the graphical object.

If we check the formal syntax of the mask property we can see it accepts an <image> as a value, meaning either a URL of an image or a color gradient. Gradients are what we’ll be using here. Let’s start with basic examples:

In the first example of this demo, a gradient is used to make it appear as though the image is fading away. The second example, meanwhile, also uses a gradient, but rather than a soft transition between colors, a hard color stop is used to hide (or mask) half of the image. That second example illustrates the technique we will be using to create our fancy borders.

Oh, and the CSS mask property can take multiple gradients as long as they are comma-separated. That means we have even more control to mask additional parts of the image.

That example showing multiple masking gradients may look a bit tricky at first glance, but what’s happening is the same as applying the multiple gradients on the background property. But instead of using a color that blends in with the page background, we use a “transparent” black value (#0000) for the hidden parts of the image and full black (#000) for the visible parts.

That’s it! Now we can tackle our fancy borders.

Zig-Zag CSS borders

As we saw in the video at the start of this article, the generator can apply borders on one side, two sides, or all sides. Let’s start with the bottom side using a step-by-step illustration:

  1. We start by adding a conic-gradient() with a specific dimension placed in the middle.
  2. Next, we repeat that gradient (by removing no-repeat ) and we already see the zig-zag shape!
  3. Gradients are known to have anti-aliasing issues creating jagged edges (especially on Chrome). To avoid this, we add a slight transition between the colors, changing blue 90deg, green 0 to green, blue 1deg 89deg, green 90deg.
  4. Last, we use everything inside the mask property!

We can extract two variables from those steps to define our shape: size (40px) and angle (90deg). Here’s how we can express that using placeholders for those variables. I will be using JavaScript to replace those variables with their final values.

mask:
  conic-gradient(
    from {-angle/2} at bottom,
    #0000, #000 1deg {angle - 1} ,#0000 {angle}
  ) 50%/{2*size*tan(angle/2)} 100%;

We can use CSS custom properties for the size and the angle, but trigonometric functions are unsupported features at this moment. In the future, we’ll be able to do something like this:

--size: 40px;
--angle: 90deg;
mask:
  conic-gradient(
    from calc(var(--angle)/-2) at bottom,
    #0000, #000 1deg calc(var(--angle) - 1deg), #0000 var(--angle)
  ) 50%/calc(2*var(--size)*tan(var(--angle)/2)) 100%;

Similar to the bottom border, the top one will have almost the same code with a few adjustments:

mask:
  conic-gradient(
    from {180deg - angle/2} at top,
    #0000, #000 1deg {angle - 1}, #0000 {angle}
  ) 50%/{2*size*tan(angle/2)} 100%;

We changed bottom with top, then updated the rotation of the gradient to 180deg - angle/2 instead of -angle/2. As simple as that!

That’s the pattern we can use for the rest of the sides, like the left:

mask:
  conic-gradient(
    from {90deg - angle/2} at left,
    #0000, #000 1deg {angle - 1}, #0000 {angle}
  ) 50%/100% {2*size*tan(angle/2)};

…and the right:

mask:
  conic-gradient(
    from {-90deg - angle/2} at right,
    #0000, #000 1deg {angle - 1}, #0000 {angle}
  ) 50%/100% {2*size*tan(angle/2)};

Let’s make the borders for when they’re applied to two sides at once. We can actually reuse the same code. To get both the top and bottom borders, we simply combine the code of both the top and bottom border.

mask:
  conic-gradient(
    from {-angle/2} at bottom,
    #0000, #000 1deg {angle - 1}, #0000 {angle}
  ) bottom/{2*size*tan(angle/2)} 51% repeat-x;
  conic-gradient(
    from {180deg - angle/2} at top, 
    #0000, #000 1deg {angle - 1}, #0000 {angle}
  ) top   /{2*size*tan(angle/2)} 51% repeat-x;

The same goes when applying borders to the left and right sides together:

mask:
  conic-gradient(
    from {90deg - angle/2} at left,
    #0000, #000 1deg {angle - 1}, #0000 {angle}
  ) left /51% {2*size*tan(angle/2)} repeat-y,
  conic-gradient(
    from {-90deg - angle/2} at right,
    #0000, #000 1deg {angle - 1}, #0000 {angle}
  ) right/51% {2*size*tan(angle/2)} repeat-y;

So, if we want to apply borders to all of the sides at once, we add all the gradients together, right?

Exactly! We have four conic gradients (one on each side) and one linear-gradient() in the middle. We set a fixed angle equal to 90deg because it the only one that results in nicer corners without weird overlapping. Note that I’m also using space instead of repeat-x or repeat-y to avoid bad result on corners like this:

Resizing a container with four sides configuration

Scooped CSS borders

Now let’s tackle scooped borders!

Oh no! another long explanation with a lot of calculation?!

Not at all! There is nothing to explain here. We take everything from the zig-zag example and update the conic-gradient() with a radial-gradient(). It’s even easier because we don’t have any angles to deal with — only the size variable.

Here is an illustration for one side to see how little we need to do to switch from the zig-zag border to the scooped border:

Again, all I did there was replace the conic-gradient() with this (using placeholders for size):

mask: radial-gradient({size} at bottom,#0000 98%,#000) 50% / {1.85*size} 100%;

What is the logic behind the magic numbers 1.85 and 98%?

Logically, we should use 100% instead of 98% to have a circle that touches the edges of the background area; but again, it’s the anti-aliasing issue and those jagged edges. We use a slightly smaller value to prevent weird overlapping.

The 1.85 value is more of a personal preference than anything. I initially used 2 which is the logical value to get a perfect circle, but the result doesn’t look quite as nice, so the smaller value creates a more seamless overlap between the circles.

Here’s the difference:

Now we need to replicate this for the rest of the sides, just as we did with the zig-zag CSS border, and guess what? We also need one gradient even for the case where we want to have the top/bottom or left/right version.

The only case where we need more than one gradient is the all sides configuration. Similar to the zig-zag border, We have to use four radial gradients and one linear gradient, but I had to introduce the CSS clip-path property to correct an overlapping issue at the corners. You can see the difference between with and without clip-path in the following demo:

It’s an eight-point path to cut the corners:

clip-path: polygon(
   {2*size} 0,calc(100% - {2*size}) 0,
   100% {2*size},100% calc(100% - {2*size}),
   calc(100% - {2*size}) 100%,{2*size} 100%,
   0 calc(100% - {2*size}),0 {2*size}
);

Scalloped CSS borders

For this border, we always need two gradients whatever the sides configuration. We use a radial gradient to create a repeated pattern of circles and a linear gradient to cover them and show only the sides we want.

Here is a demo to illustrate some of the cases:

Note how I’m using the round and space values. That’s to make sure we don’t cut off any of the circles and also avoid relying on more gradients. And, again, that 1.85 value is a personal preference value.

Wavy CSS borders

The wavy border is a kind of combination of the two previous borders. Here is an illustration to understand how we build the bottom wave.

We repeat that shape at the bottom and we are done.

mask: 
  radial-gradient({size} at 75% 100%,#0000 98%,#000) 50% calc(100% - {size})/{4*size} 100% repeat-x,
  radial-gradient({size} at 25% 50% ,#000 99%,#0000 101%) bottom/{4*size} {2*size} repeat-x;

We do the same process for the other sides as we did with the zig-zag and rounded CSS borders. All we need is to update a few variables to have a different wave for each side.

Showing part of the CSS for each side. You can find the full code over at the generator.

What about applying a wavy CSS border on all four sides? Will we have 8 gradients in total??”

Nope, and that’s because there is no demo where a wavy border is applied to all four sides. I was unable to find a combination of gradients that gives a good result on the corners. Maybe someone reading this knows a good approach? 😉

What we have done here is a simple case of a wavy border. I have another article where I go into fine detail about creating complex wavy shapes.

That’s borderline great stuff!

So, you know the ins and outs of my cool little online CSS border generator! Sure, you can use the code it spits out and do just fine — but now you have the secret sauce recipe that makes it work.

Specifically, we saw how gradients can be used to mask portions of an element. Then we went to work on multiple gradients to make certain shapes from those gradient CSS masks. And the result is a pattern that can be used along the edges of an element, creating the appearance of fancy borders that you might otherwise result to background-image for. Only this way, all it takes is swapping out some values to change the appearance rather than replace an entire raster image file or something.


Fancy CSS Borders Using Masks originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-borders-using-masks/feed/ 1 362018