It all started with this question Keith Clark recently asked on twitter.
CSS repeating-linear-gradients, do we need these? Can’t the same thing be achieved with a linear-gradient and background-size?
That’s a good question.
Their cousin, repeating radial gradients, definitely come in handy. They have saved me from having to write tens of stops inside a regular radial-gradient when I wanted to reproduce the look of a vinyl record with CSS.
But repeating-linear-gradient
?
Until this year, I was also convinced they’re useless. But in early February, while working on a canvas demo, I fell into the trap of styling the control panel for that demo and ended up reproducing over 100 slider designs I found online, each with the (self-imposed) restriction that I should use just one range input per slider.
Warning: while I did learn a lot creating them, I never went back to apply what I learned along the way to the sliders I had already coded, so, with the exception of the last few, I wouldn’t really recommend them as a good resource. Also: if I never have to style another range input again, it will be too soon.
Some of these styled range inputs had striped fills or rulers, so they required using gradients, particularly repeating linear gradients, quite a bit. I used repeating linear gradients precisely because it turned out normal linear gradients were not enough. Let’s see why!
We start by recreating the following pattern:
It has black and blue stripes, blue stripes being twice as wide as the black ones. If the black stripes have a width of .25em
, the code to get this effect with repeating-linear-gradient
is:
background: repeating-linear-gradient(135deg,
#000, #000 .25em /* black stripe */,
#0092b7 0, #0092b7 .75em /* blue stripe */
);
The gradient starts from the top left corner at an angle of 135°
. If you need a refresher on how linear gradients and their angles work, check out this explanation (ignore the part about the old syntax, we have now left that behind). There’s a black stripe from 0
to .25em
along the gradient line, then a blue stripe from .25em
to .75em
(.75em - .25em = .5em = the blue stripe width = 2*.25em = twice the black stripe width
). We can see it in action in this Pen:
See the Pen simple repeating-linear-gradient
pattern by Ana Tudor (@thebabydino) on CodePen.
Now let’s try to achieve the same with plain linear-gradient
and background-size
. We’ll make the background a small square with a diagonal adding up to the width of a black stripe (.25em
) and the width of a blue stripe (.5em
). The diagonal of a square is the square edge times √2
and, if we know the diagonal is .25em + .5em = .75em
, then the edge of the background-size
square is going to be .75em/sqrt(2)
. This brings us to the following code:
background: linear-gradient(135deg, #000 .25em, #0092b7 0);
background-size: .75em/sqrt(2) .75em/sqrt(2);
Hmm, this doesn’t look like what we wanted to achieve…
See the Pen simple linear-gradient
pattern – step 1 by Ana Tudor (@thebabydino) on CodePen.
What if we put the .25em
wide black stripe in the middle? Let’s try that!
background: linear-gradient(135deg,
#0092b7 calc(50% - .125em) /* blue corner */,
#000 0, #000 calc(50% + .125em) /* black stripe */,
#0092b7 0 /* blue corner */
);
background-size: .75em/sqrt(2) .75em/sqrt(2);
It’s a little closer now, but it still doesn’t look good.
See the Pen simple linear-gradient
pattern – step 2 by Ana Tudor (@thebabydino) on CodePen.
If we zoom in, it becomes more obvious what the problem is and what we need to do about it. We have to fill out those corners with black.
This means adding a small black stripe at the beginning of the gradient line and one at the end. Each of these will be half the width of the initial black stripe in the middle (so half of .25em
, which is .125em
).
background: linear-gradient(135deg,
#000 .125em /* black corner */,
#0092b7 0, #0092b7 calc(50% - .125em) /* blue stripe */,
#000 0, #000 calc(50% + .125em) /* black stripe */,
#0092b7 0, #0092b7 calc(100% - .125em) /* blue stripe */,
#000 0 /* black corner */
);
background-size: .75em/sqrt(2) .75em/sqrt(2);
It looks… slightly better?
See the Pen simple linear-gradient
pattern – step 3 by Ana Tudor (@thebabydino) on CodePen.
It’s still not what we want because now the blue stripes are too narrow. This is because the diagonal computations don’t work out right anymore. Going from the top left corner to the bottom right corner, we now have half a black stripe (.125em
), a blue stripe (.5em
), a black stripe (.25em
), another blue stripe (.5em
) and another half a black stripe (.125em
). This all adds up to a diagonal of 1.5em
, which means we need to change the background-size
to 1.5em/sqrt(2) 1.5em/sqrt(2)
:
See the Pen simple linear-gradient
pattern – step 4 by Ana Tudor (@thebabydino) on CodePen.
Perfect! It’s visually the same result as in the repeating-linear-gradient
version. But in this case, it’s too much work, too many computations, too much code, too much risk for rounding errors (the demo above will break if we set the font-size
to some weird value like 1.734em
which doesn’t compute to an integer pixel value).
And in case you’re not yet convinced that repeating-linear-gradient
is the better solution in this case, let’s imagine the gradient angle isn’t 135°
, but something different… let’s say 120°
.
Using repeating-linear-gradient
, the code is almost the same as before, we only need to replace 135deg
with 120deg
.
See the Pen simple repeating-linear-gradient
pattern v2 by Ana Tudor (@thebabydino) on CodePen.
All seems fine in Firefox and Edge/IE, but the transition from blue to black doesn’t look good in Chrome. We can fix that by not making the transition sharp, leaving a 1px
distance in between.
background: repeating-linear-gradient(120deg,
#0092b7 0,
#000 1px /* transition from previous blue stripe */, #000 .25em,
#0092b7 calc(.25em + 1px) /* from black to blue */, #0092b7 .75em
);
We use this Pen to test that this fixes the Chrome issue:
See the Pen simple repeating-linear-gradient
pattern v2 (Chrome fix) by Ana Tudor (@thebabydino) on CodePen.
With plain old linear-gradient
, we also need to change the angle from 135deg
to 120deg
. But it’s not enough this time.
See the Pen simple linear-gradient
pattern v2 – step 1 by Ana Tudor (@thebabydino) on CodePen.
We also need to change the dimensions of the background-size
, as this is not a square anymore. The x
dimension will be 1.5em*abs(cos(120deg))
, while the y
dimension will be 1.5em*abs(sin(120deg))
.
See the Pen simple linear-gradient
pattern v2 – step 2 by Ana Tudor (@thebabydino) on CodePen.
Again, the edges don’t look good in Chrome, so we need to use the 1px
spacing trick.
See the Pen simple linear-gradient
pattern v2 (Chrome fix) by Ana Tudor (@thebabydino) on CodePen.
The linear-gradient
alternative isn’t pretty, but it exists.
Is there something repeating-linear-gradient
can do that plain linear-gradient
and the proper background-size
can’t do at all?
Well… there is!
If you’ve paid attention to the slider examples shown at the beginning, the striped gradient reproduced above is used to fill the slider track before the thumb (on the left of the thumb).
IE and Firefox have dedicated pseudo-elements for this (-ms-fill-lower
and -moz-progress
respectively) and all we need to do is add the background created earlier on these pseudos.
But for WebKit browsers, the only way to get that striped fill is to add another background on top of the regular one used for the slider track and then update how much of the track it covers as the slider thumb gets adjusted. Now this creates a big problem with the plain linear-gradient
. We cannot control how much of the track it covers via background-size
because we are already using background-size
to create the striped look. In addition to that, gradients either repeat indefinitely or they don’t repeat at all.
It would probably be cool if we could specify number values for background-repeat
in the same way we can for animation-iteration-count
. Something like background-repeat: .5 1.5
would make half of the background to be displayed horizontally and have one tile and a half displayed vertically. But we cannot do this. Maybe we have some other way of making a linear-gradient
repeat just within certain limits?
Well, the only way we could create something that gets close to what we want with plain old linear-gradient
and background-size
is to stack up identical gradients and position them one after the other while not allowing them to repeat in at least one direction.
For example, if we wanted them to only cover a part of the element horizontally, we’d have to set background-repeat: repeat-y
. The first gradient background would start horizontally at at 0*$size-x
, the second at 1*$size-x
and so on… where $size-x
is the x
component of the background-size
. The following image illustrates how this would work:
But this only works for changing how much of the track the striped part covers in increments of $size-x
, it’s useless if we want finer control. Plus, it’s really ugly to stack up maybe 20 identical gradients.
background-image:
linear-gradient(135deg,
#000 0.125em,
#0092b7 0, #0092b7 calc(50% - .125em),
#000 0, #000 calc(50% + .125em),
#0092b7 0, #0092b7 calc(100% - .125em), #000 0
),
linear-gradient(135deg,
#000 0.125em,
#0092b7 0, #0092b7 calc(50% - .125em),
#000 0, #000 calc(50% + .125em),
#0092b7 0, #0092b7 calc(100% - .125em), #000 0
) /* repeat as many times as needed */;
;
background-position:
0*1.5em/sqrt(2) 0, 1*1.5em/sqrt(2) /* repeat for 2, 3, 4 and so on... */;
background-repeat: repeat-y;
Ugh. Repeating the exact same gradient 20 times is awful. We could make the Sass better, but the generated CSS will still look ugly:
$grad: linear-gradient(135deg,
#000 0.125em,
#0092b7 0, #0092b7 calc(50% - .125em),
#000 0, #000 calc(50% + .125em),
#0092b7 0, #0092b7 calc(100% - .125em), #000 0
);
background-image: $grad, $grad /* repeat as many times as needed */;
background-position:
0*1.5em/sqrt(2) 0, 1*1.5em/sqrt(2) /* repeat for 2, 3, 4 and so on... */;
background-repeat: repeat-y;
The following Pen shows this method in action:
See the Pen constrain a linear-gradient
within certain limits by Ana Tudor (@thebabydino) on CodePen.
In short, my advice is: don’t do this! The code is horrible and breakable, we could easily end up with gaps between the gradients for certain background sizes or on zoom.
We have a much better method available. We can limit how much of the element a repeating-linear-gradient
covers (no matter what the dimensions of the repeating part are) by using background-size
and setting background-repeat
to no-repeat
:
See the Pen constrain a repeating-linear-gradient
within certain limits by Ana Tudor (@thebabydino) on CodePen.
This is how I was able to control how much of the slider track was filled with the striped gradient every time the slider thumb value got updated – by changing the x
component of the background-size
.
The very same idea helps limit how much the rulers would extend in situations when we don’t want them to extend across the whole length of the slider. Let’s say we also want them to be horizontally in the middle, which makes the x
component of background-position
be 50%
. This is how a very simple ruler would get created:
width: 19em;
background:
repeating-linear-gradient(90deg,
#c8c8c8, #c8c8c8 0.125em /* lines */,
transparent 0.125em, transparent 1.25em /* space between */
) 50% no-repeat;
background-size: 12.625em /* = 10*1.25em + .125em */ .5em;
And we can see the result in this pen:
See the Pen simple ruler not covering the entire element by Ana Tudor (@thebabydino) on CodePen.
In order to have both major and minor lines, we need to add a second repeating gradient, one that repeats at twice the interval for the first one and is twice as tall:
background:
repeating-linear-gradient(90deg, /* major */
#c8c8c8, #c8c8c8 .125em /* lines */,
transparent , transparent 2.5em /* space between */
) 50% no-repeat,
repeating-linear-gradient(90deg, /* minor */
#c8c8c8, #c8c8c8 .125em /* lines */,
transparent , transparent 1.25em /* space between */
) 50% no-repeat;
background-size: 12.625em 1em, 12.625em .5em;
The result can be seen in this Pen:
See the Pen ruler not covering the entire element #2 by Ana Tudor (@thebabydino) on CodePen.
We cold also tweak the y
component of the background-position
so that they align to top or to bottom. For example, we could do something like this:
background-position: right 50% bottom 2.25em;
This is exactly the kind of ruler that was used in one of the examples at the beginning.
See the Pen ruler not covering the entire element #3 by Ana Tudor (@thebabydino) on CodePen.
The slider thumb grips in the following example were created in exactly the same manner.
There are two identical repeating linear gradients, both limited horizontally to twice the size of the repeating part plus the width of the non-transparent part. Horizontally, the first one is positioned at 25%
and the second one at 75%
.
background:
repeating-linear-gradient(90deg,
#6b4c1e, #6b4c1e 1px,
#e1ba75 0, #e1ba75 2px,
transparent 0, transparent 4px
) 25% 50% /* left */,
repeating-linear-gradient(90deg,
#6b4c1e, #6b4c1e 1px,
#e1ba75 0, #e1ba75 2px,
transparent 0, transparent 4px
) 75% 50% /* right */
orange;
background-repeat: no-repeat;
background-size: 10px 10px;
But this is a very WET style of writing code. We could make it more maintainable using Sass:
$grip: repeating-linear-gradient(90deg,
#6b4c1e, #6b4c1e 1px,
#e1ba75 0, #e1ba75 2px,
transparent 0, transparent 4px); /* we use this twice */
background:
$grip 25% 50% /* left */,
$grip 75% 50% /* right */
orange;
background-repeat: no-repeat;
background-size: 10px 10px;
This gives us the following result:
See the Pen thumb grips by Ana Tudor (@thebabydino) on CodePen.
Repeating gradients used to be a real headache and we can still run into issues with thin gradient lines, especially when they cover larger areas. They won’t always have the same width in WebKit browsers and Firefox on OS X isn’t very precise when rendering them (though Firefox on Windows rarely ever fails here).
But things have improved a lot and I believe they’ll continue to improve. Even as I was creating those sliders at the beginning of this year, I could see pretty big differences between what were then the stable (41) and the canary (43) versions of Chrome.
Repeating gradients are a lot safer to use now and I think they deserve a bit more love because they can make our lives much easier and that makes them pretty cool in my eyes.
I have also used it only once – to give a Harry Potter his Gryffindor tie :) so there are at least two cases for usage :D
See the Pen Harry Potter by Richard Vyslouzil (@remeritus) on CodePen.
Is there any fix for the thin diagonal lines issues on FF for OS and Safari? I’m running into these very issues on a project and I really don’t want to solve it by using images (if I can help it).
Blurring the lines with the
1px
difference trick fixed the problem for me in Chrome/ Opera on Windows (live test), but I personally haven’t tested on OS X – update: it appears like it doesn’t fix the issue for Firefox on OS X and Safari.If this method doesn’t work, SVG would be a lightweight and maintainable solution.
And of course, keeping on pestering implementors for the future. Things can and they do change for the better.
@AnaTudor Where do we pester them at? Can you share a URL? I would like to pester them about adding rounded outlines!
Excellent write-up, Ana – thanks!