Tag: Background

Animated Background Stripes That Transition on Hover

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.

CSS-Tricks

, , , ,

CSS Checkerboard Background… But With Rounded Corners and Hover Styles

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.

CSS-Tricks

, , , , ,
[Top]

Cool CSS Hover Effects That Use Background Clipping, Masks, and 3D

We’ve walked through a series of posts now about interesting approaches to CSS hover effects. We started with a bunch of examples that use CSS background properties, then moved on to the text-shadow property where we technically didn’t use any shadows. We also combined them with CSS variables and calc() to optimize the code and make it easy to manage.

In this article, we will build off those two articles to create even more complex CSS hover animations. We’re talking about background clipping, CSS masks, and even getting our feet wet with 3D perspectives. In other words, we are going to explore advanced techniques this time around and push the limits of what CSS can do with hover effects!

Cool Hover Effects series:

  1. Cool Hover Effects That Use Background Properties
  2. Cool Hover Effects That Use CSS Text Shadow
  3. Cool Hover Effects That Use Background Clipping, Masks, and 3D (you are here!)

Here’s just a taste of what we’re making:

Hover effects using background-clip

Let’s talk about background-clip. This CSS property accepts a text keyword value that allows us to apply gradients to the text of an element instead of the actual background.

So, for example, we can change the color of the text on hover as we would using the color property, but this way we animate the color change:

All I did was add background-clip: text to the element and transition the background-position. Doesn’t have to be more complicated than that!

But we can do better if we combine multiple gradients with different background clipping values.

In that example, I use two different gradients and two values with background-clip. The first background gradient is clipped to the text (thanks to the text value) to set the color on hover, while the second background gradient creates the bottom underline (thanks to the padding-box value). Everything else is straight up copied from the work we did in the first article of this series.

How about a hover effect where the bar slides from top to bottom in a way that looks like the text is scanned, then colored in:

This time I changed the size of the first gradient to create the line. Then I slide it with the other gradient that update the text color to create the illusion! You can visualize what’s happening in this pen:

We’ve only scratched the surface of what we can do with our background-clipping powers! However, this technique is likely something you’d want to avoid using in production, as Firefox is known to have a lot of reported bugs related to background-clip. Safari has support issues as well. That leaves only Chrome with solid support for this stuff, so maybe have it open as we continue.

Let’s move on to another hover effect using background-clip:

You’re probably thinking this one looks super easy compared to what we’ve just covered — and you are right, there’s nothing fancy here. All I am doing is sliding one gradient while increasing the size of another one.

But we’re here to look at advanced hover effects, right? Let’s change it up a bit so the animation is different when the mouse cursor leaves the element. Same hover effect, but a different ending to the animation:

Cool right? let’s dissect the code:

.hover {   --c: #1095c1; /* the color */    color: #0000;   background:      linear-gradient(90deg, #fff 50%, var(--c) 0) calc(100% - var(--_p, 0%)) / 200%,      linear-gradient(var(--c) 0 0) 0% 100% / var(--_p, 0%) no-repeat,     var(--_c, #0000);   -webkit-background-clip: text, padding-box, padding-box;           background-clip: text, padding-box, padding-box;   transition: 0s, color .5s, background-color .5s; } .hover:hover {   color: #fff;   --_c: var(--c);   --_p: 100%;   transition: 0.5s, color 0s .5s, background-color 0s .5s; }

We have three background layers — two gradients and the background-color defined using --_c variable which is initially set to transparent (#0000). On hover, we change the color to white and the --_c variable to the main color (--c).

Here’s what is happening on that transition: First, we apply a transition to everything but we delay the color and background-color by 0.5s to create the sliding effect. Right after that, we change the color and the background-color. You might notice no visual changes because the text is already white (thanks to the first gradient) and the background is already set to the main color (thanks to the second gradient).

Then, on mouse out, we apply an instant change to everything (notice the 0s delay), except for the color and background-color that have a transition. This means that we put all the gradients back to their initial states. Again, you will probably see no visual changes because the text color and background-color already changed on hover.

Lastly, we apply the fading to color and a background-color to create the mouse-out part of the animation. I know, it may be tricky to grasp but you can better visualize the trick by using different colors:

Hover the above a lot of times and you will see the properties that are animating on hover and the ones animating on mouse out. You can then understand how we reached two different animations for the same hover effect.

Let’s not forget the DRY switching technique we used in the previous articles of this series to help reduce the amount of code by using only one variable for the switch:

.hover {   --c: 16 149 193; /* the color using the RGB format */    color: rgb(255 255 255 / var(--_i, 0));   background:     /* Gradient #1 */     linear-gradient(90deg, #fff 50%, rgb(var(--c)) 0) calc(100% - var(--_i, 0) * 100%) / 200%,     /* Gradient #2 */     linear-gradient(rgb(var(--c)) 0 0) 0% 100% / calc(var(--_i, 0) * 100%) no-repeat,     /* Background Color */     rgb(var(--c)/ var(--_i, 0));   -webkit-background-clip: text, padding-box, padding-box;           background-clip: text, padding-box, padding-box;   --_t: calc(var(--_i,0)*.5s);   transition:      var(--_t),     color calc(.5s - var(--_t)) var(--_t),     background-color calc(.5s - var(--_t)) var(--_t); } .hover:hover {   --_i: 1; }

If you’re wondering why I reached for the RGB syntax for the main color, it’s because I needed to play with the alpha transparency. I am also using the variable --_t to reduce a redundant calculation used in the transition property.

Before we move to the next part here are more examples of hover effects I did a while ago that rely on background-clip. It would be too long to detail each one but with what we have learned so far you can easily understand the code. It can be a good inspiration to try some of them alone without looking at the code.

I know, I know. These are crazy and uncommon hover effects and I realize they are too much in most situations. But this is how to practice and learn CSS. Remember, we pushing the limits of CSS hover effects. The hover effect may be a novelty, but we’re learning new techniques along the way that can most certainly be used for other things.

Hover effects using CSS mask

Guess what? The CSS mask property uses gradients the same way the background property does, so you will see that what we’re making next is pretty straightforward.

Let’s start by building a fancy underline.

I’m using background to create a zig-zag bottom border in that demo. If I wanted to apply an animation to that underline, it would be tedious to do it using background properties alone.

Enter CSS mask.

The code may look strange but the logic is still the same as we did with all the previous background animations. The mask is composed of two gradients. The first gradient is defined with an opaque color that covers the content area (thanks to the content-box value). That first gradient makes the text visible and hides the bottom zig-zag border. content-box is the mask-clip value which behaves the same as background-clip

linear-gradient(#000 0 0) content-box

The second gradient will cover the whole area (thanks to padding-box). This one has a width that’s defined using the --_p variable, and it will be placed on the left side of the element.

linear-gradient(#000 0 0) 0 / var(--_p, 0%) padding-box

Now, all we have to do is to change the value of --_p on hover to create a sliding effect for the second gradient and reveal the underline.

.hover:hover {   --_p: 100%;   color: var(--c); }

The following demo uses with the mask layers as backgrounds to better see the trick taking place. Imagine that the green and red parts are the visible parts of the element while everything else is transparent. That’s what the mask will do if we use the same gradients with it.

With such a trick, we can easily create a lot of variation by simply using a different gradient configuration with the mask property:

Each example in that demo uses a slightly different gradient configuration for the mask. Notice, too, the separation in the code between the background configuration and the mask configuration. They can be managed and maintained independently.

Let’s change the background configuration by replacing the zig-zag underline with a wavy underline instead:

Another collection of hover effects! I kept all the mask configurations and changed the background to create a different shape. Now, you can understand how I was able to reach 400 hover effects without pseudo-elements — and we can still have more!

Like, why not something like this:

Here’s a challenge for you: The border in that last demo is a gradient using the mask property to reveal it. Can you figure out the logic behind the animation? It may look complex at first glance, but it’s super similar to the logic we’ve looked at for most of the other hover effects that rely on gradients. Post your explanation in the comments!

Hover effects in 3D

You may think it’s impossible to create a 3D effect with a single element (and without resorting to pseudo-elements!) but CSS has a way to make it happen.

What you’re seeing there isn’t a real 3D effect, but rather a perfect illusion of 3D in the 2D space that combines the CSS background, clip-path, and transform properties.

Breakdown of the CSS hover effect in four stages.
The trick may look like we’re interacting with a 3D element, but we’re merely using 2D tactics to draw a 3D box

The first thing we do is to define our variables:

--c: #1095c1; /* color */ --b: .1em; /* border length */ --d: 20px; /* cube depth */

Then we create a transparent border with widths that use the above variables:

--_s: calc(var(--d) + var(--b)); color: var(--c); border: solid #0000; /* fourth value sets the color's alpha */ border-width: var(--b) var(--b) var(--_s) var(--_s);

The top and right sides of the element both need to equal the --b value while the bottom and left sides need to equal to the sum of --b and --d (which is the --_s variable).

For the second part of the trick, we need to define one gradient that covers all the border areas we previously defined. A conic-gradient will work for that:

background: conic-gradient(   at left var(--_s) bottom var(--_s),   #0000 90deg,var(--c) 0  )   0 100% / calc(100% - var(--b)) calc(100% - var(--b)) border-box;
Diagram of the sizing used for the hover effect.

We add another gradient for the third part of the trick. This one will use two semi-transparent white color values that overlap the first previous gradient to create different shades of the main color, giving us the illusion of shading and depth.

conic-gradient(   at left var(--d) bottom var(--d),   #0000 90deg,   rgb(255 255 255 / 0.3) 0 225deg,   rgb(255 255 255 / 0.6) 0 ) border-box
Showing the angles used to create the hover effect.

The last step is to apply a CSS clip-path to cut the corners for that long shadow sorta feel:

clip-path: polygon(   0% var(--d),    var(--d) 0%,    100% 0%,    100% calc(100% - var(--d)),    calc(100% - var(--d)) 100%,    0% 100% )
Showing the coordinate points of the three-dimensional cube used in the CSS hover effect.

That’s all! We just made a 3D rectangle with nothing but two gradients and a clip-path that we can easily adjust using CSS variables. Now, all we have to do is to animate it!

Notice the coordinates from the previous figure (indicated in red). Let’s update those to create the animation:

clip-path: polygon(   0% var(--d), /* reverses var(--d) 0% */   var(--d) 0%,    100% 0%,    100% calc(100% - var(--d)),    calc(100% - var(--d)) 100%, /* reverses 100% calc(100% - var(--d)) */    0% 100% /* reverses var(--d) calc(100% - var(--d)) */ )

The trick is to hide the bottom and left parts of the element so all that’s left is a rectangular element with no depth whatsoever.

This pen isolates the clip-path portion of the animation to see what it’s doing:

The final touch is to move the element in the opposite direction using translate — and the illusion is perfect! Here’s the effect using different custom property values for varying depths:

The second hover effect follows the same structure. All I did is to update a few values to create a top left movement instead of a top right one.

Combining effects!

The awesome thing about everything we’ve covered is that they all complement each other. Here is an example where I am adding the text-shadow effect from the second article in the series to the background animation technique from the first article while using the 3D trick we just covered:

The actual code might be confusing at first, but go ahead and dissect it a little further — you’ll notice that it’s merely a combination of those three different effects, pretty much smushed together.

Let me finish this article with a last hover effect where I am combining background, clip-path, and a dash of perspective to simulate another 3D effect:

I applied the same effect to images and the result was quite good for simulating 3D with a single element:

Want a closer look at how that last demo works? I wrote something up on it.

Wrapping up

Oof, we are done! I know, it’s a lot of tricky CSS but (1) we’re on the right website for that kind of thing, and (2) the goal is to push our understanding of different CSS properties to new levels by allowing them to interact with one another.

You may be asking what the next step is from here now that we’re closing out this little series of advanced CSS hover effects. I’d say the next step is to take all that we learned and apply them to other elements, like buttons, menu items, links, etc. We kept things rather simple as far as limiting our tricks to a heading element for that exact reason; the actual element doesn’t matter. Take the concepts and run with them to create, experiment with, and learn new things!


Cool CSS Hover Effects That Use Background Clipping, Masks, and 3D originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

Cool Hover Effects That Use Background Properties

A while ago, Geoff wrote an article about a cool hover effect. The effect relies on a combination of CSS pseudo-elements, transforms, and transitions. A lot of comments have shown that the same effect can be done using background properties. Geoff mentioned that was his initial thought and that’s what I was thinking as well. I am not saying the pseudo-element he landed on is bad, but knowing different methods to achieve the same effect can only be a good thing.

In this post, we will re-work that hover effect, but also expand it into other types of hover effects that only use CSS background properties.

You can see the background properties at work in that demo, as well as how we can use custom properties and the calc() function to do even more. We are going to learn how to combine all of these so we are left with nicely optimized code!

Hover effect #1

Let’s start with the first effect which is the reproduction of the one detailed by Geoff in his article. The code used to achieve that effect is the following:

.hover-1 {   background: linear-gradient(#1095c1 0 0) var(--p, 0) / var(--p, 0) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%;   color: #fff; }

If we omit the color transition (which is optional), we only need three CSS declarations to achieve the effect. You are probably surprised how small the code is, but you will see how we got there.

First, let’s start with a simple background-size transition:

We are animating the size of a linear gradient from 0 100% to 100% 100%. That means the width is going from 0 to 100% while the background itself remains at full height. Nothing complex so far.

Let’s start our optimizations. We first transform our gradient to use the color only once:

background-image: linear-gradient(#1095c1 0 0);

The syntax might look a bit strange, but we are telling the browser that one color is applied to two color stops, and that’s enough to define a gradient in CSS. Both color stops are 0, so the browser automatically makes the last one 100% and fills our gradient with the same color. Shortcuts, FTW!

With background-size, we can omit the height because gradients are full height by default. We can do a transition from background-size: 0 to background-size: 100%.

.hover-1 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 0;   background-repeat: no-repeat;   transition: .4s; } .hover-1:hover {   background-size: 100%; }

Let’s introduce a custom property to avoid the repetition of background-size:

.hover-1 {   background-image: linear-gradient(#1095c1 0 0);   background-size: var(--p, 0%);   background-repeat: no-repeat;   transition: .4s; } .hover-1:hover {   --p: 100%; }

We are not defining --p initially, so the fallback value (0% in our case) will be used. On hover, we define a value that replaces the fallback one ( 100%).

Now, let’s combine all the background properties using the shorthand version to get:

.hover-1 {   background: linear-gradient(#1095c1 0 0) left / var(--p, 0%) no-repeat;   transition: .4s; } .hover-1:hover {   --p: 100%; }

We are getting closer! Note that I have introduced a left value (for the background-position) which is mandatory when defining the size in the background shorthand. Plus, we need it anyway to achieve our hover effect.

We need to also update the position on hover. We can do that in two steps:

  1. Increase the size from the right on mouse hover.
  2. Decrease the size from the left on mouse out.

To do this, we need to update the background-position on hover as well:

We added two things to our code:

  • A background-position value of right on hover
  • A transition-duration of 0s on the background-position

This means that, on hover, we instantly change the background-position from left (see, we needed that value!) to right so the background’s size will increase from the right side. Then, when the mouse cursor leaves the link, the transition plays in reverse, from right to left, making it appear that we are decreasing the background’s size from the left side. Our hover effect is done!

But you said we only needed three declarations and there are four.

That’s true, nice catch. The left and right values can be changed to 0 0 and 100% 0, respectively; and since our gradient is already full height by default, we can get by using 0 and 100%.

.hover-1 {   background: linear-gradient(#1095c1 0 0) 0 / var(--p, 0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%;   background-position: 100%; }

See how background-position and --p are using the same values? Now we can reduce the code down to three declarations:

.hover-1 {   background: linear-gradient(#1095c1 0 0) var(--p, 0%) / var(--p,0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%; }

The custom property --p is defining both the background position and size. On hover, It will update both of them as well. This is a perfect use case showing how custom properties can help us reduce redundant code and avoid writing properties more than once. We define our setting using custom properties and we only update the latter on hover.

But the effect Geoff described is doing the opposite, starting from left and ending at right. How do we do that when it seems we cannot rely on the same variable?

We can still use one variable and update our code slightly to achieve the opposite effect. What we want is to go from 100% to 0% instead of 0% to 100%. We have a difference of 100% that we can express using calc(), like this:

.hover-1 {   background: linear-gradient(#1095c1 0 0) calc(100% - var(--p,0%)) / var(--p,0%) no-repeat;   transition: .4s, background-position 0s; } .hover-1:hover {   --p: 100%; }

--p will change from 0% to 100%, but the background’s position will change from 100% to 0%, thanks to calc().

We still have three declarations and one custom property, but a different effect.

Before we move to the next hover effect, I want to highlight something important that you have probably noticed. When dealing with custom properties, I am using 0% (with a unit) instead of a unit-less 0. The unit-less zero may work when the custom property is alone, but will fail inside calc() where we need to explicitly define the unit. I may need another article to explain this quirk but always remember to add the unit when dealing with custom properties. I have two answers on StackOverflow (here and here) that go into more detail.

Hover effect #2

We need a more complex transition for this effect. Let’s take a look at a step-by-step illustration to understand what is happening.

Diagram showing the hover effect in three pieces.
Initially, a fixed-height, full-width gradient is outside of view. Then we move the gradient to the right to cover the bottom side. Finally, we increase the size of the gradient from the fixed height to 100% to cover the whole element.

We first have a background-position transition followed by a background-size one. Let’s translate this into code:

.hover-2 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 100% .08em; /* .08em is our fixed height; modify as needed. */   background-position: /* ??? */;   background-repeat: no-repeat;   transition: background-size .3s, background-position .3s .3s; } .hover-2:hover {   transition: background-size .3s .3s, background-position .3s;   background-size: 100% 100%;   background-position: /* ??? */; }

Note the use of two transition values. On hover, we need to first change the position and later the size, which is why we are adding a delay to the size. On mouse out, we do the opposite.

The question now is: what values do we use for background-position? We left those blank above. The background-size values are trivial, but the ones for background-position are not. And if we keep the actual configuration we’re unable to move our gradient.

Our gradient has a width equal to 100%, so we cannot use percentage values on background-position to move it.

Percentage values used with background-position are always a pain especially when you use them for the first time. Their behavior is non-intuitive but well defined and easy to understand if we get the logic behind it. I think it would take another article for a full explanation why it works this way, but here’s another “long” explanation I posted over at Stack Overflow. I recommend taking a few minutes to read that answer and you will thank me later!

The trick is to change the width to something different than 100%. Let’s use 200%. We’re not worried about the background exceeding the element because the overflow is hidden anyway.

.hover-2 {   background-image: linear-gradient(#1095c1 0 0);   background-size: 200% .08em;   background-position: 200% 100%;   background-repeat: no-repeat;   transition: background-size .3s, background-position .3s .3s; } .hover-2:hover {   transition: background-size .3s .3s, background-position .3s;   background-size: 200% 100%;   background-position: 100% 100%; }

And here’s what we get:

It’s time to optimize our code. If we take the ideas we learned from the first hover effect, we can use shorthand properties and write fewer declarations to make this work:

.hover-2 {   background:      linear-gradient(#1095c1 0 0) no-repeat     var(--p, 200%) 100% / 200% var(--p, .08em);   transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s)); } .hover-2:hover {   --p: 100%;   --t: .3s; }

We add all the background properties together using the shorthand version then we use --p to express our values. The sizes change from .08em to 100% and the position from 200% to 100%

I am also using another variable --t , to optimize the transition property. On mouse hover we have it set to a .3s value, which gives us this:

transition: .3s .3s, background-position .3s 0s;

On mouse out, --t is undefined, so the fallback value will be used:

transition: .3s 0s, background-position .3s .3s;

Shouldn’t we have background-size in the transition?

That is indeed another optimization we can make. If we don’t specify any property it means “all” the properties, so the transition is defined for “all” the properties (including background-size and background-position). Then it’s defined again for background-position which is similar to defining it for background-size, then background-position.

“Similar” is different than saying something is the “same.” You will see a difference if you change more properties on hover, so the last optimization might be unsuitable in some cases.

Can we still optimize the code and use only one custom property?

Yes, we can! Ana Tudor shared a great article explaining how to create DRY switching where one custom property can update multiple properties. I won’t go into the details here, but our code can be revised like this:

.hover-2 {   background:      linear-gradient(#1095c1 0 0) no-repeat     calc(200% - var(--i, 0) * 100%) 100% / 200% calc(100% * var(--i, 0) + .08em);   transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - calc(var(--i, 0) * .3s)); } .hover-2:hover {   --i: 1; }

The --i custom property is initially undefined, so the fallback value, 0, is used. On hover though, we replace 0 with 1. You can do the math for both cases and get the values for each one. You can see that variable as a “switch” that update all our values at once on hover.

Again, we’re back to only three declarations for a pretty cool hover effect!

Hover effect #3

We are going to use two gradients instead of one for this effect. We will see that combining multiple gradients is another way to create fancy hover effects.

Here’s a diagram of what we’re doing:

We initially have two gradients that overflow the element so that they are out of view. Each one has a fixed height and toes up half of the element’s width. Then we slide them into view to make them visible. The first gradient is placed at the bottom-left and the second one at the top-right. Finally, we increase the height to cover the whole element.

Here’s how that looks in CSS:

.hover-3 {   background-image:     linear-gradient(#1095c1 0 0),     linear-gradient(#1095c1 0 0);   background-repeat: no-repeat;   background-size: 50% .08em;   background-position:     -100% 100%,     200% 0;   transition: background-size .3s, background-position .3s .3s; } .hover-3:hover {   background-size: 50% 100%;   background-position:     0 100%,     100% 0;     transition: background-size .3s .3s, background-position .3s; }

The code is almost the same as the other hover effects we’ve covered. The only difference is that we have two gradients with two different positions. The position values may look strange but, again, that’s related to how percentages work with the background-position property in CSS, so I highly recommend reading my Stack Overflow answer if you want to get into the gritty details.

Now let’s optimize! You get the idea by now — we’re using shorthand properties, custom properties, and calc() to tidy things up.

.hover-3 {   --c: no-repeat linear-gradient(#1095c1 0 0);   background:      var(--c) calc(-100% + var(--p, 0%)) 100% / 50% var(--p, .08em),     var(--c) calc( 200% - var(--p, 0%)) 0    / 50% var(--p, .08em);   transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s)); } .hover-3:hover {   --p: 100%;   --t: 0.3s; }

I have added an extra custom property, --c, that defines the gradient since the same gradient is used in both places.

I am using 50.1% in that demo instead of 50% for the background size because it prevents a gap from showing between the gradients. I also added 1% to the positions for similar reasons.

Let’s do the second optimization by using the switch variable:

.hover-3 {   --c: no-repeat linear-gradient(#1095c1 0 0);   background:      var(--c) calc(-100% + var(--i, 0) * 100%) 100% / 50% calc(100% * var(--i, 0) + .08em),     var(--c) calc( 200% - var(--i, 0) * 100%) 0 / 50% calc(100% * var(--i, 0) + .08em);   transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - var(--i, 0) * .3s); } .hover-3:hover {   --i: 1; }

Are you started to see the patterns here? It’s not so much that the effects we’re making are difficult. It’s more the “final step” of code optimization. We start by writing verbose code with a lot of properties, then reduce it following simple rules (e.g. using shorthand, removing default values, avoiding redundant values, etc) to simplify things down as much as possible.

Hover effect #4

I will raise the difficulty level for this last effect, but you know enough from the other examples that I doubt you’ll have any issues with this one.

This hover effect relies on two conic gradients and more calculations.

Initially, we have both gradients with zero dimensions in Step 1. We increase the size of each one in Step 2. We keep increasing their widths until they fully cover the element, as shown in Step 3. After that, we slide them to the bottom to update their position. This is the “magic” part of the hover effect. Since both gradients will use the same coloration, changing their position in Step 4 will make no visual difference — but we will see a difference once we reduce the size on mouse out during Step 5.

If you compare Step 2 and Step 5, you can see that we have a different inclination. Let’s translate that into code:

.hover-4 {   background-image:     conic-gradient(/* ??? */),     conic-gradient(/* ??? */);   background-position:     0 0,     100% 0;   background-size: 0% 200%;   background-repeat: no-repeat;   transition: background-size .4s, background-position 0s; } .hover-4:hover {   background-size: /* ??? */ 200%;   background-position:     0 100%,     100% 100%; }

The positions are pretty clear. One gradient starts at top left (0 0) and ends at bottom left (0 100%) while the other starts at top right (100% 0) and ends at bottom right (100% 100%).

We’re using a transition on the background positions and sizes to reveal them. We only need a transition value for the background-size. And like before, background-position needs to change instantly, so we’re assigning a 0s value for the transition’s duration.

For the sizes, both gradient need to have 0 width and twice the element height (0% 200%). We will see later how their sizes change on hover. Let’s first define the gradient configuration.

The diagram below illustrates the configuration of each gradient:

Note that for the second gradient (indicated in green), we need to know the height to use it inside the conic-gradient we’re creating. For this reason, I am going to add a line-height that sets the element’s height and then try that same value for the conic gradient values we left out.

.hover-4 {   --c: #1095c1;   line-height: 1.2em;   background-image:     conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0),     conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0);   background-position:     0 0,     100% 0;   background-size: 0% 200%;   background-repeat: no-repeat;   transition: background-size .4s, background-position 0s; } .hover-4:hover {   background-size: /* ??? */ 200%;   background-position:     0 100%,     100% 100%; }

The last thing we have left is to figure out the background’s size. Intuitively, we may think that each gradient needs to take up half of the element’s width but that’s actually not enough.

We’re left with a large gap if we use 50% as the background-size value for both gradients.

We get a gap equal to the height, so we actually need to do is increase the size of each gradient by half the height on hover for them to cover the whole element.

.hover-4:hover {   background-size: calc(50% + .6em) 200%;   background-position:     0 100%,     100% 100%; }

Here’s what we get after optimizing them like the previous examples:

.hover-4 {   --c: #1095c1;   line-height: 1.2em;   background:     conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0)        0  var(--p, 0%) / var(--s, 0%) 200% no-repeat,     conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0)        100% var(--p, 0%) / var(--s, 0%) 200% no-repeat;   transition: .4s, background-position 0s; } .hover-4:hover {   --p: 100%;   --s: calc(50% + .6em); } 

What about the version with only one custom property?

I will leave that for you! After looking at four similar hover effects, you should be able to get the final optimization down to a single custom property. Share your work in the comment section! There’s no prize, but we may end up with different implementations and ideas that benefit everyone!

Before we end, let me share a version of that last hover effect that Ana Tudor cooked up. It’s an improvement! But note that it lacks Firefox supports due to a known bug. Still, it’s a great idea that shows how to combine gradients with blend modes to create even cooler hover effects.

Wrapping up

We made four super cool hover effects! And even though they are different effects, they all take the same approach of using CSS background properties, custom properties, and calc(). Different combinations allowed us to make different versions, all using the same techniques that leave us with clean, maintainable code.

If you want to get some ideas, I made a collection of 500 (yes, 500!) hover effects, 400 of which are done without pseudo-elements. The four we covered in this article are just the tip of the iceberg!


Cool Hover Effects That Use Background Properties originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

The Search For a Fixed Background Effect With Inline Images

I was working on a client project a few days ago and wanted to create a certain effect on an <img>. See, background images can do the effect I was looking for somewhat easily with background-attachment: fixed;. With that in place, a background image stays in place—even when the page scrolls. It isn’t used all that often, so the effect can look unusual and striking, especially when used sparingly.

Table of Contents

It took me some time to figure out how to achieve the same effect only with an inline image, rather than a CSS background image. This is a video of the effect in action:

The exact code for the above demo is available in this Git repo. Just note that it’s a Next.js project. We’ll get to a CodePen example with raw HTML in a bit.

Why use <img> instead of background-image?

The are a number of reasons I wanted this for my project:

  • It’s easier to lazy load (e.g. <img loading="lazy"… >.
  • It provides better SEO (not to mention accessibility), thanks to alt text.
  • It’s possible to use srcset/sizes to improve the loading performance.
  • It’s possible to use the <picture> tag to pick the best image size and format for the user’s browser.
  • It allows users to download save the image (without resorting to DevTools).

Overall, it’s better to use the image tag where you can, particularly if the image could be considered content and not decoration. So, I wound up landing on a technique that uses CSS clip-path. We’ll get to that in a moment, right after we first look at the background-image method for a nice side-by-side comparison of both approaches.

1. Using CSS background-image

This is the “original” way to pull off a fixed scrolling effect. Here’s the CSS:

.hero-section {   background-image: url("nice_bg_image.jpg");   background-repeat: no-repeat;   background-size: cover;   background-position: center;    background-attachment: fixed; }

But as we just saw, this approach isn’t ideal for some situations because it relies on the CSS background-image property to call and load the image. That means the image is technically not considered content—and thus unrecognized by screen readers. If we’re working with an image that is part of the content, then we really ought to make it accessible so it is consumed like content rather than decoration.

Otherwise, this technique works well, but only if the image spans the whole width of the viewport and/or is centered. If you have an image on the right or left side of the page like the example, you’ll run into a whole number of positioning issues because background-position is relative to the center of the viewport.

Fixing it requires a few media queries to make sure it is positioned properly on all devices.

2. Using the clip-path trick on an inline image

Someone on StackOverflow shared this clip-path trick and it gets the job done well. You also get to keep using the<img> tag, which, as we covered above, might be advantageous in some circumstances, especially where an image is part of the content rather than pure decoration.

Here’s the trick:

.image-container {   position: relative;   height: 200px;   clip-path: inset(0); }  .image {   object-fit: cover;   position: fixed;   left: 0;   top: 0;   width: 100%;   height: 100%; }

Check it out in action:

Now, before we rush out and plaster this snippet everywhere, it has its own set of downsides. For example, the code feels a bit lengthy to me for such a simple effect. But, even more important is the fact that working with clip-path comes with some implications as well. For one, I can’t just slap a border-radius: 10px; in there like I did in the earlier example to round the image’s corners. That won’t work—it requires making rounded corners from the clipping path itself.

Another example: I don’t know how to position the image within the clip-path. Again, this might be a matter of knowing clip-path really well and drawing it where you need to, or cropping the image itself ahead of time as needed.

Is there something better?

Personally, I gave up on using the fixed scrolling effect on inline images and am back to using a CSS background image—which I know is kind of limiting.

Have you ever tried pulling this off, particularly with an inline image, and managed it well? I’d love to hear!


The Search For a Fixed Background Effect With Inline Images originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

The Search For a Fixed Background Effect With Inline Images

I was working on a client project a few days ago and wanted to create a certain effect on an <img>. See, background images can do the effect I was looking for somewhat easily with background-attachment: fixed;. With that in place, a background image stays in place—even when the page scrolls. It isn’t used all that often, so the effect can look unusual and striking, especially when used sparingly.

It took me some time to figure out how to achieve the same effect only with an inline image, rather than a CSS background image. This is a video of the effect in action:

The exact code for the above demo is available in this Git repo. Just note that it’s a Next.js project. We’ll get to a CodePen example with raw HTML in a bit.

Why use <img> instead of background-image?

The are a number of reasons I wanted this for my project:

  • It’s easier to lazy load (e.g. <img loading="lazy"… >.
  • It provides better SEO (not to mention accessibility), thanks to alt text.
  • It’s possible to use srcset/sizes to improve the loading performance.
  • It’s possible to use the <picture> tag to pick the best image size and format for the user’s browser.
  • It allows users to download save the image (without resorting to DevTools).

Overall, it’s better to use the image tag where you can, particularly if the image could be considered content and not decoration. So, I wound up landing on a technique that uses CSS clip-path. We’ll get to that in a moment, right after we first look at the background-image method for a nice side-by-side comparison of both approaches.

1. Using CSS background-image

This is the “original” way to pull off a fixed scrolling effect. Here’s the CSS:

.hero-section {   background-image: url("nice_bg_image.jpg");   background-repeat: no-repeat;   background-size: cover;   background-position: center;    background-attachment: fixed; }

But as we just saw, this approach isn’t ideal for some situations because it relies on the CSS background-image property to call and load the image. That means the image is technically not considered content—and thus unrecognized by screen readers. If we’re working with an image that is part of the content, then we really ought to make it accessible so it is consumed like content rather than decoration.

Otherwise, this technique works well, but only if the image spans the whole width of the viewport and/or is centered. If you have an image on the right or left side of the page like the example, you’ll run into a whole number of positioning issues because background-position is relative to the center of the viewport.

Fixing it requires a few media queries to make sure it is positioned properly on all devices.

2. Using the clip-path trick on an inline image

Someone on StackOverflow shared this clip-path trick and it gets the job done well. You also get to keep using the<img> tag, which, as we covered above, might be advantageous in some circumstances, especially where an image is part of the content rather than pure decoration.

Here’s the trick:

.image-container {   position: relative;   height: 200px;   clip-path: inset(0); }  .image {   object-fit: cover;   position: fixed;   left: 0;   top: 0;   width: 100%;   height: 100%; }

Check it out in action:

Now, before we rush out and plaster this snippet everywhere, it has its own set of downsides. For example, the code feels a bit lengthy to me for such a simple effect. But, even more important is the fact that working with clip-path comes with some implications as well. For one, I can’t just slap a border-radius: 10px; in there like I did in the earlier example to round the image’s corners. That won’t work—it requires making rounded corners from the clipping path itself.

Another example: I don’t know how to position the image within the clip-path. Again, this might be a matter of knowing clip-path really well and drawing it where you need to, or cropping the image itself ahead of time as needed.

Is there something better?

Personally, I gave up on using the fixed scrolling effect on inline images and am back to using a CSS background image—which I know is kind of limiting.

Have you ever tried pulling this off, particularly with an inline image, and managed it well? I’d love to hear!


The Search For a Fixed Background Effect With Inline Images originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

The Fixed Background Attachment Hack

What options do you have if you want the body background in a fixed position where it stays put on scroll? background-attachment: fixed in CSS, at best, does not work well in mobile browsers, and at worst is not even supported by the most widely used mobile browsers. You can ditch this idea completely and let the background scroll on small screens using media queries.

Or get around it with a small fix. I suppose we could call it a “hack” since it’s a workaround in code that arguably we shouldn’t have to do at all.

The issue

Before I show you the fix, let’s examine the issue. We can see it by looking at two different approaches to CSS backgrounds:

  1. a background using a linear gradient
  2. a background using an image

Linear gradient

I want to keep the background gradient in a fixed position on scroll, so let’s apply basic CSS styling to the body that does exactly that:

body {   background: linear-gradient(335deg, rgba(255,140,107,1) 0%, rgba(255,228,168,1) 100%);   background-attachment: fixed;   background-position: center;   background-repeat: no-repeat;   height: 100vh; }

Here are the results in Chrome and Firefox, both on Android, respectively:

Chrome Android

Firefox Android

The gradient simply scrolls along with other content then jumps back. I don’t know exactly why that is — maybe when the URL tab goes up or disappears on scroll and the browser finds it difficult to re-render the gradient in real time? That’s my best guess since it only seems to happen in mobile browsers.

If you’re wondering about iOS Safari, I haven’t tested on iOS personally, but the issue is there too. Some have already reported the issue and it appears to behave similarly.

Background image

This issue with images is no different.

body {   background: url(../assets/test_pic.jpg);   background-repeat: no-repeat;   background-size: cover;   background-position: center;   background-attachment: fixed;   height: 100vh; }
The grey section at the top just indicates the presence of an actual URL bar in Chrome Android.

Another interesting thing to note is that when background-attachment: fixed is applied, the height is ignored even if we explicitly specify it. That’s because background-attachment calculates a fixed background position relative to the viewport.

Even if we say the body is 100vh, background-attachment: fixed is not exactly in accordance with it. Weird! Perhaps the reason is that background-attachment: fixed relies on the smallest possible viewport while elements rely on the largest possible viewport. David Bokan explains,

Lengths defined in viewport units (i.e. vh) will not resize in response to the URL bar being shown or hidden. Instead, vh units will be sized to the viewport height as if the URL bar is always hidden. That is, vh units will be sized to the “largest possible viewport”. This means 100vh will be larger than the visible height when the URL bar is shown.

The issues are nicely documented over at caniuse:

  • Firefox does not appear to support the local value when applied on a textarea element.
  • Chrome has an issue that occurs when using the will-change property on a selector which also has background-attachment: fixed defined. It causes the image to get cut off and gain whitespace around it.
  • iOS has an issue preventing background-attachment: fixed from being used with background-size: cover.

Let’s fix it

Call it a temporary hack, if you will. Some of you may have already tried it. Whatever the case, it fixes the linear gradient and background image issues we just saw.

So, as you know, we are getting in trouble with the background-attachment: fixed property and, as you might have guessed, we are removing it from our code. If it’s looking at the smallest possible viewport, then maybe we should be working with an element that looks for the largest possible viewport and position that instead.

So, we are creating two separate elements — one for the background-gradient and another for the rest of the content. We are replacing background-attachment: fixed with position: fixed.

<div class="bg"></div> <div class="content">   <!-- content --> </div>
.bg {   background: linear-gradient(335deg, rgba(255,140,107,1) 0%, rgba(255,228,168,1) 100%);   background-repeat: no-repeat;   background-position: center;   height: 100vh;   width: 100vw;   position: fixed;   /* z-index usage is up to you.. although there is no need of using it because the default stack context will work. */   z-index: -1; // this is optional }

Now, wrap up the rest of the content — except for the element containing the background image — inside a main container.

.content{   position: absolute;   margin-top: 5rem;   left: 50%;    transform: translateX(-50%);   width: 80%; }

Success!

Chrome Android

Firefox Android

We can use the same trick hack with background images and it works fine. However, you do get some sort of background scrolling when the URL bar hides itself, but the white patch is no longer there.

.img {       background: url('../assets/test_pic.jpg');   background-position: center;   background-repeat: no-repeat;   background-size: cover;   position: fixed;   height: 100vh;   width: 100vw; }  .content {   position: absolute;   left: 50%;    margin-top: 5rem;   transform: translateX(-50%);   width: 80%; }
Chrome Android

Firefox Android

Here are my takeaways

A fixed-position element with a height set to 100% behaves just like the element with background-attachment: fixed property, which is clearly evident in the example below! Just observe the right-most bar (purple color) in the video.

The website which is being tested is taken from this article.

Even though, David Bokan in his article states that:

That is, a position: fixed element whose containing block is the ICB will resize in response to the URL bar showing or hiding. For example, if its height is 100% it will always fill exactly the visible height, whether or not the URL bar is shown. Similarly for vh lengths, they will also resize to match the visible height taking the URL bar position into account.

If we take into account that last sentence, that doesn’t seem to be the case here. Elements that have fixed positioning and 100vh height don’t change their height whether the URL bar is shown or not. In fact, the height is according to the height of the “largest possible viewport”. This is evident in the example below. Just observe the light blue colored bar in the video.

The website which is being tested is taken from this article.

So, it appears that, when working with a container that is 100vh, background-attachment: fixed considers the smallest possible viewport height while elements in general consider the largest possible viewport height.

For example, background-attachment: fixed simply stops working when a repaint is needed, like when a mobile browser’s address bar goes away on scroll. The browser adjusts the background according to the largest possible viewport (which is now, in fact, the smallest possible viewport as URL bar is hidden) and the browser isn’t efficient enough to repaint on the fly, which results in a major lag.

Our hack addresses this by making the background an element instead of, well, an actual background. We give the element containing the content an absolute position to stack it on top of the element containing the image, then apply a fixed position on the latter. Hey, it works!

Note that the viewport height is calculated excluding the navigation bar at the bottom (if present). Here’s a comparison between the presence and absence of navigation bar at the bottom in Chrome Android.

Is there a downside? Perhaps! We’re using a general <div> instead of an actual <img> tag, so I wouldn’t say the markup is semantic. And that can lead to accessibility issues. If you’re working with an image that adds meaning or context to the content, then an <img> is the correct way to go, utilizing a proper alt description for screen readers.

But if we go the proper <img> route, then we’re right back where we started. Also, if you have a navigation bar at the bottom which too auto hides itself, then I can’t help it. If the hack won’t cut it, then perhaps JavaScript can come to the rescue.


The post The Fixed Background Attachment Hack appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Put a Background on Open Details Elements

One thing that can be just a smidge funky about the <details> element is that, when open, it’s not always 100% clear what is inside that element and what isn’t. I’m not saying that always matters or that it’s a particularly hard problem to solve, I’m just noting it as it came up recently for me.

Here’s a visual example:

A screenshot of some text with details elements present. One of them is open. It's not clear what text is within that details and what isn't.
What text here is inside a <details> and what isn’t?

The solution is… CSS. Style the <details> somewhat uniquely, and that problem goes away. Even if you want the typography to be the same, or you don’t want any exclusive styling until the <details> is opened, it’s still possible. Using an alpha-transparent fill, you can even make sure that deeper-nested <details> remain clear.

Here’s that CSS:

details[open] {   --bg: rgb(0 0 0 / 0.2);   background: var(--bg);   outline: 1rem solid var(--bg);   margin: 0 0 2rem 0; }

And the demo:


The post Put a Background on Open Details Elements appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values

In this week’s round-up, prefers-contrast lands in Safari, MathML gets some attention, :is() is actually quite forgiving, more ADA-related lawsuits, inconsistent initial values for CSS Backgrounds properties can lead to unwanted — but sorta neat — patterns.

The prefers-contrast: more media query is supported in Safari Preview

After prefers-reduced-motion in 2017, prefers-color-scheme in 2019, and forced-colors in 2020, a fourth user preference media feature is making its way to browsers. The CSS prefers-contrast: more media query is now supported in the preview version of Safari. This feature will allow websites to honor a user’s preference for increased contrast.

Screenshot of the iPhone 12 landing page on Apple's website. A big red arrow points out light grey text on the page.
Apple could use this new media query to increase the contrast of gray text on its website
.pricing-info {   color: #86868b; /* contrast ratio 3.5:1 */ }  @media (prefers-contrast: more) {   .pricing-info {     color: #535283; /* contrast ratio 7:1 */   } }

Making math a first-class citizen on the web

One of the earliest specifications developed by the W3C in the mid-to-late ’90s was a markup language for displaying mathematical notations on the web called MathML. This language is currently supported in Firefox and Safari. Chrome’s implementation was removed in 2013 because of “concerns involving security, performance, and low usage on the Internet.”

If you’re using Chrome or Edge, enable “Experimental Web Platform features” on the about:flags page to view the demo.

There is a renewed effort to properly integrate MathML into the web platform and bring it to all browsers in an interoperable way. Igalia has been developing a MathML implementation for Chromium since 2019. The new MathML Core Level 1 specification is a fundamental subset of MathML 3 (2014) that is “most suited for browser implementation.” If approved by the W3C, a new Math Working Group will work on improving the accessibility and searchability of MathML.

The mission of the Math Working Group is to promote the inclusion of mathematics on the Web so that it is a first-class citizen of the web that displays well, is accessible, and is searchable.

CSS :is() upgrades selector lists to become forgiving

The new CSS :is() and :where() pseudo-classes are now supported in Chrome, Safari, and Firefox. In addition to their standard use cases (reducing repetition and keeping specificity low), these pseudo-classes can also be used to make selector lists “forgiving.”

For legacy reasons, the general behavior of a selector list is that if any selector in the list fails to parse […] the entire selector list becomes invalid. This can make it hard to write CSS that uses new selectors and still works correctly in older user agents.

In other words, “if any part of a selector is invalid, it invalidates the whole selector.” However, wrapping the selector list in :is() makes it forgiving: Unsupported selectors are simply ignored, but the remaining selectors will still match.

Unfortunately, pseudo-elements do not work inside :is() (although that may change in the future), so it is currently not possible to turn two vendor-prefixed pseudo-elements into a forgiving selector list to avoid repeating styles.

/* One unsupported selector invalidates the entire list */ ::-webkit-slider-runnable-track, ::-moz-range-track {   background: red; }  /* Pseudo-elements do not work inside :is() */ :is(::-webkit-slider-runnable-track, ::-moz-range-track) {   background: red; }  /* Thus, the styles must unfortunately be repeated */ ::-webkit-slider-runnable-track {   background: red; } ::-moz-range-track {   background: red; }

Dell and Kraft Heinz sued over inaccessible websites

More and more American businesses are facing lawsuits over accessibility issues on their websites. Most recently, the tech corporation Dell was sued by a visually impaired person who was unable to navigate Dell’s website and online store using the JAWS and VoiceOver screen readers.

The Defendant fails to communicate information about its products and services effectively because screen reader auxiliary aids cannot access important content on the Digital Platform. […] The Digital Platform uses visual cues to convey content and other information. Unfortunately, screen readers cannot interpret these cues and communicate the information they represent to individuals with visual disabilities.

Earlier this year, Kraft Heinz Foods Company was sued for failing to comply with the Web Content Accessibility Guidelines on one of the company’s websites. The complaint alleges that the website did not declare a language (lang attribute) and provide accessible labels for its image links, among other things.

In the United States, the Americans with Disabilities Act (ADA) applies to websites, which means that people can sue retailers if their websites are not accessible. According to the CEO of Deque Systems (the makers of axe), the recent increasing trend of web-based ADA lawsuits can be attributed to a lack of a single overarching regulation that would provide specific compliance requirements.

background-clip and background-origin have different initial values

By default, a CSS background is painted within the element’s border box (background-clip: border-box) but positioned relative to the element’s padding box (background-origin: padding-box). This inconsistency can result in unexpected patterns if the element’s border is semi-transparent or dotted/dashed.

A pink and triple rectangle with rounded edges. The colors overlap in a pattern.
.box {   /* semi-transparent border */   border: 20px solid rgba(255, 255, 255, 0.25);    /* background gradient */   background: conic-gradient(     from 45deg at bottom left,     deeppink,     rebeccapurple   ); }

Because of the different initial values, the background gradient in the above image is repeated as a tiled image on all sides under the semi-transparent border. In this case, positioning the background relative to the border box (background-origin: border-box) makes more sense.


The post Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , , , ,
[Top]

CSS Background Patterns

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

Like this:

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

Direct Link to ArticlePermalink


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

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

CSS-Tricks

,
[Top]