My mind goes immediately to repeating-linear-gradient
and hard-stop gradients when thinking of creating stripes in CSS. You make one stripe by using the same color between two color stops, and another stripe (or more) but using a different color between two colors stops (sharing the one in the middle).
So like:
background: repeating-linear-gradient(
45deg,
black,
black 10px,
#444 10px,
#444 11px
);
That will make angled dark gray stripes 10px apart on black.
But this is how it renders on my screen:
Can you see that rendering jankiness where one or two of the stripes seems lighter and thinner than the others? I have no idea why. I assume it’s something to do with sub-pixel rendering or the like. This is not hard to replicate. It’s not just these two colors or this particular angle is just about any stripes created at all with repeating-linear-gradient
. It stops being so noticeable with thicker stripes though (say, 5px
and thicker).
I made a handful of examples. This one with tighter stripes going the other way is especially prevelant:
I needed to do this the other day, found the jankiness, and remembered this little note in our stripes article. It amounts to: don’t use repeating-linear-gradient
. Just use linear-gradient
, set a background-size
and let it repeat. Indeed, that seems to do the trick. The trouble with this is… how big do you make the background-size
? If the stripes are vertical or horizontal, it’s fairly easy to smudge something. But if the stripes are at an angle… calculating the perfect width×height is tricky. I’d guess it’s related to the Pythagorean theorem, but I’m out of my depth there.
So, what do you do?
Use this nice little generator tool thing:
It does whatever fancy math necessary to get it right. You can see the unminified JavaScript here. Search for / GET BACKGROUND SIZE /
to see all the math going on. Whatever it’s doing there, the stripes come out perfectly.
Kind of a shame repeating-linear-gradient
doesn’t have better visual output as that’s so much easier to reason about, but hey, you gotta do what you gotta do.
The main reason for the jank in your CSS stripes is varying cumulative subpixel error because you probably forgot all the trigonometry you learned at school.
You’re defining things at an angle of 45°, which means that for every 1px you go, you’ve only gone (√2÷2)px in the horizontal and vertical axes. Combined with subpixel rendering, this means that the fraction of a pixel that you’re rendering the line offset by is changing with each new line—e.g. the first may be at about 0.707px, then the next at 0.414px, then 0.121px, then 0.828px, then 0.536px, &c. (These are taken from the fractional part of each √2÷2×i for sequential natural numbers i.)
You can fix this class of error by ensuring that things hit pixel boundaries in both the x and y axes. At 45°, this means you should multiply your desired horizontal and vertical period by √2, so that a 10px period will become 14.142143562373059504px on that 45° axis. (Decide for yourself the precision to go to; I suggest going no less than single-precision, meaning about eight significant places.)
(Now in practice things get a bit weirder than this and the error often doesn’t look as random as you might expect from my explanation, because the browser is actually only doing fairly limited subpixel rendering with a form of supersampling, rather than what I might call infinite-precision subpixel rendering.)
Now even after you’ve fixed this up, repeating gradients are still a bit hit-and-miss, because browsers still do some slightly weird things with them, though over time they’ve been trending sanewards. Repeating carefully-sized linear gradients is definitely more robust for now, and often easier to think about—especially if you want angles other than 45°, then you need some more involved trigonometry to match it all to a pixel grid. (Yes, school-level trigonometry has solid practical applications in design when rendering to a raster device.)
One final note: the reason the error is most visible in the second example is actually because the “line” part is a gradient, because the third stop is at 5px when it should be at 4px. This tends to amplify the subpixel error visibility.
That’s the best article comment I’ve seen in a very long time.
@chris the comment blowed my mind more than the article itself!!! But I need to read it 100 times more….
If anybody is using Styled Components, I made a utility function from the source code posted above to generate stripes. Feel free to use it below.
Usage:
Function: