The Exciting World of Spec Development, Part 1

Last updated:

If you ever wondered why it takes so long to develop specs, this is why.

Consider the humble radial gradient. All in all, pretty easy to define. You give it a shape, set some colors, describe how to extrapolate from there, and you're done.

But you're not! Extrapolating from a shape is harder than it appears. Oh, it's easy enough when the shape is well-defined. If you've got an ellipse with non-zero axises, you're golden. But how do you scale an ellipse with zero width, or zero height, or both? These are valid, if degenerate, shapes, and they're possible to specify:

Case A
radial-gradient(center, 10px 10px, red 5px, blue 7px);

Case B
radial-gradient(center, 10px 0px, red 5px, blue 7px);

Case C
radial-gradient(center, 0px 10px, red 5px, blue 7px);

Case D
radial-gradient(center, 0px 0px, red 5px, blue 7px);

Mathematically, the degenerate shapes are nothing special; they're just degenerate ellipses. When you scale them you get various ellipses with one axis infinitely long - we usually call these "parallel lines". These have some implications:

  • If the gradient is zero-height (case B), the 100% point on the gradient-line is a reasonable distance away (whatever the width is), so percentage stops are still placed normally. However, when you try to paint the canvas outside of the ending-shape, the ellipses intersect the gradient-line at infinity, which means they're painted the color of the last color-stop.

    • For repeating gradients, the color at infinity is indeterminate. However, as you approach zero-height, the successive ellipses approach zero vertical separation between layers, which indicates that we should paint it the average color, same as any other repeating gradient that has color-stops too close together to render.
  • If the gradient is zero-width (case C), the 100% point on the gradient-line coincides with the 0% point, so percentage stops all resolve to 0px. Non-percentage stops are unaffected, as usual. When you paint the canvas outside of the ending-shape, you just get parallel lines advancing horizontally outward from the center, which intersects the gradient-line in the usual way. This looks like a pretty normal linear gradient that happens to be mirrored across the center.

    • For repeating gradients, nothing special happens. It looks like a mirrored repeating linear gradient.
  • If the gradient is double-zero (case D), the 100% point on the gradient-line coincides with the 0% point, so again percentage stops resolve to 0px. Painting outside the ending-shape presents some problems, though. The shape doesn't have an aspect-ratio, so the shape outside is undefined. We could take it to be circular, zero-width, zero-height, or anything in-between, and they all produce a different image. We just have to define it to be something.

I put together a little plaything to help make sure my intuitions on these were correct: If your browser doesn't implement <input type=range>, you can manually change the X and Y values - try anything between 0 and -3. Note that the double-zero case here looks more-or-less like what you'd get if you set the aspect ratio to be a circle. This is just an implementation artifact.

I think this ends up being similar to the question of what to define 0^0 as. There are multiple valid answers, but some are less consistent, so we can discard them. In this case, defining it as zero-height is less consistent, since the general zero-height case treats percentages differently.

Similarly, the fact that it's a degenerate case seems to make it slightly less consistent to treat it as a non-degenerate ratio.

So, this leaves us with treating it like zero-width. This means that there's a discontinuity in the image if you're approaching double-zero from any direction other than zero-width, but that's unavoidable.

This is what I spent the last hour on. (Well, that and writing an email which I then turned into this blog post.)

(a limited set of Markdown is supported)