Re: [css3-images] radial-gradient issue raised by Brad

On Aug 12, 2011, at 4:28 PM, Brian Manthos wrote:

> http://dev.w3.org/csswg/css3-images/#radial-gradients
> # Brad suggests that we could drop the position/sizing arguments
> # and just use background-position and background-size. This
> # would force all non-background uses of radial gradient to be
> # centered and box-filling. Is this acceptable or not?
> 
> The current grammar is:
> 
> <radial-gradient> = radial-gradient(
> 	[<'background-position'>,]? 
> 	[[
> 		[<shape> || <size>]
> 		|
> 		[<length> | <percentage>]{2}
> 	],]? 
> 	<color-stop>[, <color-stop>]+
> )
> <shape> = circle | ellipse
> <size> = closest-side | closest-corner | farthest-side | farthest-corner | contain | cover
> 
> 
> For a moment, let's ignore the first parameter and the stops.  Thus we have the following combinations:
> 1	circle closest-side = circle contain
> 2	circle closest-corner
> 3	circle farthest-side
> 4	circle farthest-corner = circle cover
> 5	ellipse closest-side = ellipse contain
> 6	ellipse closest-corner
> 7	ellipse farthest-side
> 8	ellipse farthest-corner = ellipse cover
> 9	<length> <length>
> 10	<length> <percentage>
> 11	<percentage> <length>
> 12	<percentage> <percentage>
> 
> As I understand it, the proposed grammar is:
> 
> <radial-gradient> = radial-gradient(
> 	[<bg-position>,]? 
> 	[<bg-size>,]?
> 	<color-stop>[, <color-stop>]+
> )
> <bg-size> = [ <length> | <percentage> | auto ]{1,2} | cover | contain
> 
> Again, ignoring the first parameter and the stops, the following combinations are available:
> i	<length>
> ii	<length> <length>
> iii	<length> <percentage>
> iv	<length> auto
> v	<percentage>
> vi	<percentage> <length>
> vii	<percentage> <percentage>
> viii	<percentage> auto
> ix	auto
> x	auto <length>
> xi	auto <percentage>
> xii	auto auto
> xiii	cover
> xiv	contain
> 
> My initial thoughts of new syntax vs. old:
> A. [+1] Old has 16, new has 14. 
> B. [+2] Syntaxes 'i' and 'v' are added functionality, and potentially convenient.
> C. [-6] I'm unclear on the meaning or value of having the auto parameter (iv, viii, ix, x, xi, xii).
> D. [-1] No ability to distinguish ellipse vs. circle in cover (xiii vs 4, 8).
> E. [-1] No ability to distinguish ellipse vs. circle in contain (xiv vs 1, 5).
> F. [-2] Lost functionality for closest-corner (2, 6).
> G. [-2] Lost functionality for farthest-side (3, 7).
> 
> Tally that up: -9.
> 
> I strongly prefer the current syntax to the proposal.

That's not exactly my proposal. In fact, I would cut deeper, with the following:

radial-gradient(
	[circle,]? <color-stop>[, <color-stop>]+
)

The default shape would be a dimensionless ellipse, with 'contain' coverage (closest-side). Adding 'circle,' to the beginning would make the image have a fixed  1:1 aspect ratio (thus making it more useful for positioning in a background, e.g. by having a perfect circle that is based on percentage of background positioning area width). I am suggesting we use this simpler version for CSS3, and wait until CSS4 gradients to see if any more is really called for (I don't think it is, but that can be determined when CSS3 gradients are in more widespread use).

Let me explain why, and please read completely before commenting, in order to get a feel for my view as a whole (the whole is more than the sum of the pieces):

Brian, your tallying of the bewildering number of combinations of pieces of syntax is a big part of what I don't like about the current version of radial-gradient. It is extremely complex, almost comically so IMO. 

If all we cared about was long feature lists and having as much power as possible, then I'd agree with you. But CSS should be something simple to read, understand, learn, and write by ordinary human authors. Many of these humans frequently author code by hand, but would never consider doing the same with PostScript code or even SVG. CSS should favor syntax simplicity over raw power, while SVG is more suited for drawing anything without necessarily being optimized for human editing.The easy readability of the syntax was what allowed me to ramp up my own expertise in it, and was even part of the CSSWG mission statement around the time that I started getting involved with this list/group.  If the simple version covers 95% of the needs, then I say stop there and don't make it any more complex. 

_Linear_ gradients are ending up very well in that regard. They are very easy to learn and understand. There is a list of color/distance combinations, and there is an indication of angle and direction in which to array the stops. Once he knows the basics, any author can look at another author's linear-gradent work and will be able to easily understand what is going on. Even if he has never seen a CSS linear gradient before, he can figure it out quickly by looking at a couple examples of the code and seeing the results.

I cannot say the same of radial gradients. Also, they suffer from some of the same problems linear-gradient had in the early days: multiple ways of creating the same gradients, and a layering/multiplying of different coordinate spaces that make it hard to intuitively know where a percentage-based color stop will end up. 

KEYWORD CONFUSION:
Plus, there are keyword that mean the same thing (‘contain’ = ‘closest-side’,  ‘cover’ = ‘farthest-corner’). An author who didn't read the spec is forced to try to figure out what the difference is, then realize there is none. Other keywords are there just for completeness: 'farthest-side', 'closest-corner', and again the author has to figure out what it means. As it turns out, unless there is a <bg-position> offset within the image tile, 'ellipse farthest-corner' is the same as 'ellipse closest-corner', 'ellipse farthest-side' is the same as 'ellipse closest-side', and 'circle closest-corner' is the same as 'circle farthest-corner'. But the <bg-position> is generally not useful for many design needs except as a way to center the gradient on corners and/or transition/animate to and from corners. 

MULTIPLE LENGTHS, PERCENTAGES, & POSITIONS LEAD TO LESS CLARITY:
Yet transitioning and animating gradients is being saved for CSS4, and centering radial gradients on a corner (or elsewhere) is more obviously done by using the already-familiar background-position (and background-size, if desired), without having to figure out a separate way of doing it inside the gradient tile. By offsetting the center within the image, the gradient path length changes, sometimes enormously, making it harder to for a CSS author to intuitively resolve percentages in another author's color stops. They can end up being a percentage of a percentage of some dimension of the background-size [uh, always horizontal dimension, because the gradient line is always center to right?], and it takes a pretty thorough understanding to figure out what that is.

Then you have something that can look very much like a <bg-position>  but which is actually used to explicitly size the radial shape. So, for instance, you can have 'radial-gradient(10% 30%, 50% 70%, white 20%, black 70%)' , where the second pair of numbers is the height and width. So you have:
• Two pairs of similar looking lengths meaning very different things,
• The spec implies that one pair of lengths/percentages can be used without the other, when actually you need both if you want to use the second pair unambiguously,
• It also makes it even less obvious how long the percentages in the color stops are going to end up being. In this example black is, I think, at 70% of 90% (100-10) of 50% of the image width (which in backgrounds is determined by the horizontal part of 'background-size'). 
• And of course, when you include an explicit size you have to remember to get the order right (h-offset v-offset, h-size v-size).

And this extra power for CSS radial gradients is for options that would rarely, if ever, be needed in real word situations. When it is needed, SVG would usually be a better choice.

THE EXTRA SYNTAX IS MOSTLY REDUNDANT:
Consider this estimated breakdown that of expected gradient use by authors that I included in a different thread:

• probably 95% of gradient needs will be for horizontal or vertical gradients, 
• maybe another 3% for angled (including corner-to-corner), 
• and most of another 2% for radial gradients. 

*** Of that 2%, I would guess something like 99.99% would be used as background images, where there is already very familiar and easy to remember syntax for offsetting and/or sizing the image. ***

Background properties will be easier to use, as it does not involve learning something new, looking things up in a spec to see what a pair of lengths or percentages represent (or the different thing that another pair of lengths or percentages represent), and then doing multiple equations to get a sense of the lengths of your color stops. Further limits on the extent of the gradient can be achieved by simply using the length portion of the last color stop, and that color stop can be easily animated/transitioned (as can background-size and background-position) without any special extra syntax or additional speccing. 

I really don't think that the muddying of the syntax is worth it, to gain a lot of power for the limited purposes of creating, say, list style images with non-centered radial gradients, or for allowing needlessly complex radial gradients in some purely theoretical future image-using property. And we can get to REC sooner if we don't need to have tests for all the different variations that a more complex syntax allows.

CONTAIN OR COVER CAN BE REDUCED TO ONE MODEL INSTEAD OF TWO (OR 6):
With regard to the 'cover' and 'contain' components, consider that the following two rules produce the same results:

	radial-gradient(contain, white, black 142%)
	radial-gradient(cover, white, black)

As do these two:

	radial-gradient(cover, white, black 70.64%)
	radial-gradient(contain, white, black)

For instance, take a look at this, in a UA that supports the current WD:

	http://www.bradclicks.com/cssplay/radial-equivelance.html

CONTAIN/COVER: WE CAB JUST PICK ONE FOR HOW RADIAL GRADIENTS SHOULD WORK:
It is sometimes useful to be able to draw a circle/ellipse that extends exactly to the tile edges, but going to the corners does not have to be super precise when designing a gradient (go past the corners by a pixel or two and no one will notice, whether the gradient is a soft blend or a hard edge). That is why I would have the 'contain' behavior be the default, and if you want it to go to the corners you just use 142% for the last color stop. From there, background-size and background-position can make the tile any dimension or offset that's needed. I know this is counter to the expectations of those here who have gotten used to making the inside of the gradient a world unto itself, but it is much simpler syntax, with less redundancy and confusion this way. 

Consider this, in the current syntax:

	background: radial-gradient(50% top, 700px 500px, white, black); 

It is not demonstrably better than this (if 'contain' is the norm):

	background: radial-gradient(white, black) top left / 700px 500px black;

AVOID REDUNDANCY, LEARN LESS, USE BACKGROUND PROPERTIES:

The WD syntax also allows this, for more complex needs:

	background: radial-gradient(50% top, 300px 200px, white, black);

This can be written with simpler gradient syntax and existing background syntax like this:

	background: radial-gradient(contain, white, black) 50% -200px / 600px 400px no-repeat black;

Or if all radial gradients were 'contain', like this:

	background: radial-gradient(white, black) 50% -200px / 600px 400px no-repeat black;

This is slightly longer, but more familiar and very clear (using background-size in the shorthand will become more familiar once UAs start supporting it more). To even read and understand the WD syntax when seeing it written, an author has to learn the differences of the similar-looking syntax:

	• radial-gradient uses a comma instead of a slash to disambiguate between position and size,
	• the size values are for a quarter of the image size, not the whole image size,
	• the explicit size doesn't give the image implicit dimensions,
	• the positioning lengths and keywords apply to the center of the gradient, instead of to the top left 
	  (and percentage offsets are even more different), 
	• the positioning values move the whole gradient and then clip it to the image dimensions. 

This last point is not clear even in the spec, by the way, where it seems like it might only mean to offset the center of the gradient (that is, the starting point of the gradient line), without also offsetting the end points. The sentence about the color-stop positions being measured from the center of the gradient to the right strengthens this misunderstanding by implying that they could be different if measured from the center to the left.

A lot of what I've written above about how radial gradients work was learned by following this mailing list, and then by trying to understand the examples in Lea Verue's CSS3 Patterns Gallery [1], and then going back to the spec multiple times over the course of weeks to clarify things I still didn't fully understand. I think few authors would show the same perseverance to try to understand. We generally learn by looking at examples, and the current WD of radial-gradients hinders that sort of learning.

LOOKING AT EXTREME EXAMPLES TO UNDERSTAND WHERE THE COMPLEX SYNTAX COULD HELP:
In trying to get a handle on how and what advanced authors would do with so much expressive power in radial gradients, I looked to Lea Verue's CSS3 Patterns Gallery, as it contains probably the most advanced use of CSS3 gradients anyone is likely to encounter. Most of these are pushing the gradient syntax to the extreme, so much so that they can no longer be thought of as gradients at all in any normal sense of the word. 

Of these, there were 12 that used radial-gradient. I found that every single one of them called out explicit 'background-size' values, and all but one called out 'background-position' values too. 5 of the 12 used color stops only. The "cross" pattern used color stops and the 'circle' keyword, but creates an identical pattern without the keyword (color stops only).

The remaining radial gradients all use keywords. Two of these, "Brady Bunch" and "Shippo" used only the 'closest-side' keyword (equivalent to 'contain'), but could have just used different percentages or fixed lengths  without that (background-size was set anyway). In fact, "Brady Bunch" used color stops that ranged from 0% to 140%, and so shows that it is not unreasonable for authors to use 'contain' behavior on everything and to just use a color stop of about 142% when they want 'cover' behavior, as that is essentially what the author of "Brady Bunch" (Estelle Weyl) did anyway. 

In fact then, all but 3 of the radial patterns examples in the Gallery can be drawn with the simpler syntax I proposed, by combining with the existing background properties that their authors are using anyway, and/or by setting different percentages in the color stops that they were setting anyway. These other 3 are: Waves, Seigaiha, and Ying Yang. In each of these 3 examples, Lea is using <bg-position> offsets and the keyword 'circle' in order to draw hard-edged circles that are clipped by the edge of the image itself. They are not gradients in any normal sense; they are just using a side effect of a complex syntax in order to draw curvilinear shapes with multiple layers of clipped circles. This could be done more appropriately (and probably more simply) with SVG, which was designed to do exactly that sort of thing. Let's not burden the radial gradient syntax with the extra cruft just to awkwardly simulate that capability.

HEARTS:
I haven't mentioned the "Hearts" pattern from CSS3 Patterns Gallery yet. In this pattern, the heart shapes are drawn via 5 intersecting circles of the same size: two pink circles side-by-side, with two red circles side-by-side underneath them (in the y axis), and one pink circle behind all 4 in the middle, all set against a solid red background. Then all those circle layers are repeated with an offset background-position to make the hearts repeat more interestingly. [2]

To me, "Hearts" is a good example of why we should not have this complex syntax. Here is how it is written (note the varying percentages for the same-sized circles):

background: 
  radial-gradient(60% 43%, closest-side circle, #b03 26%, rgba(187,0,51,0) 27%),
  radial-gradient(40% 43%, closest-side circle, #b03 26%, rgba(187,0,51,0) 27%),
  radial-gradient(40% 22%, closest-side circle, #d35 45%, rgba(221,51,85,0) 46%),
  radial-gradient(60% 22%, closest-side circle, #d35 45%, rgba(221,51,85,0) 46%),
  radial-gradient(50% 35%, closest-side circle, #d35 30%, rgba(221,51,85,0) 31%),

  /* same layers, but offset down and to the right 50px: /*
  radial-gradient(60% 43%, closest-side circle, #b03 26%, rgba(187,0,51,0) 27%) 50px 50px,
  radial-gradient(40% 43%, closest-side circle, #b03 26%, rgba(187,0,51,0) 27%) 50px 50px,
  radial-gradient(40% 22%, closest-side circle, #d35 45%, rgba(221,51,85,0) 46%) 50px 50px,
  radial-gradient(60% 22%, closest-side circle, #d35 45%, rgba(221,51,85,0) 46%) 50px 50px,
  radial-gradient(50% 35%, closest-side circle, #d35 30%, rgba(221,51,85,0) 31%) 50px 50px;
background-color:#b03;
background-size:100px 100px;


-- But it could have been written like this (less complicated, and easier to understand) for the same visual result:

background:
  radial-gradient(#b03 14%, transparent 14%) 60px 43px, 
  radial-gradient(#b03 14%, transparent 14%) 40px 43px,
  radial-gradient(#d35 14%, transparent 14%) 40px 22px,
  radial-gradient(#d35 14%, transparent 14%) 60px 22px,
  radial-gradient(#d35 14%, transparent 14%) 50px 35px,

  /* same layers, but offset down and to the right 50px: /*
  radial-gradient(#b03 14%, transparent 14%) 110px 93px, 
  radial-gradient(#b03 14%, transparent 14%) 90px 93px,
  radial-gradient(#d35 14%, transparent 14%) 90px 72px,
  radial-gradient(#d35 14%, transparent 14%) 110px 72px,
  radial-gradient(#d35 14%, transparent 14%) 100px 85px;
background-color:#b03;
background-size:100px 100px;


-- or more compactly and possibly more obviously like this:

background:
  radial-gradient(#b03 14%, transparent 14%), 
  radial-gradient(#b03 14%, transparent 14%),
  radial-gradient(#d35 14%, transparent 14%),
  radial-gradient(#d35 14%, transparent 14%),
  radial-gradient(#d35 14%, transparent 14%);
background-color:#b03;
background-size:100px 100px;
background-position: 60px 43px, 40px 43px, 40px 22px, 60px 22px, 50px 35px,
/* 50px offsets: */    110px 93px,  90px 93px, 90px 72px, 110px 72px, 100px 85px;

(The color stop percentages would be different if all radial gradients were 'contain'.)

The only downside of using background-position like this, instead of <bg-position> in the image itself, would be that if you change the background-size you have to change the background-positions too (you can't do percentages of the image width [or half-width] for the position). The upside is much simpler and easier to read code, without having to learn a complex new syntax. Even being in the WG and following the drafts pretty closely, I had to study the CSS and go back to the draft to first understand what "Hearts" was doing exactly (using lots of different percentages to get circles that were all the same size). 

"Hearts"  demonstrates how adding a couple non-essential arguments to a function syntax can not only lead to redundant ways of doing the same thing, but also to making CSS code harder to read. The percentages listed in the color stops are percentages of the gradient path, but because of the offsetting of the center of the gradient, it is no longer as simple and easy to tell how long the gradient path is. It's much more complicated than just saying "14%" for the color stops to get even-sized little circles (each heart is just a grouping of little circles, with the bottom two in the same color as the canvas and covering up the middle circle).

GOOGLE CODE:
I did a search in Google Code Project Hosting for CSS files stored there that contain either '-moz-radial-gradient' or '-webkit-radial-gradient'. I got 27 results. [3] The same search, but for linear gradients, maxed out at 40 results (it seems to be the max they'll show for any search there). Most of the ones I saw fell into 3 categories:
1. They were simple (color stops only, sometimes with optional 'circle' keyword), or
2. Used offsets that could easily be converted into background-position, or
3. Used an obsolete syntax that wouldn't work in the current WD (a moz variant that had degrees).

When I did a similar search for 'farthest-side', I found 8 results (including one that used degrees too), all of them using non-centered positions.[4] So it was essentially used to make the gradient fill to the edges even when offsetting the center (apparently the only time you would need to use this keyword). 

In other words, it allowed for a mostly-contain-like behavior, in which the edge of the gradient aligns with the edge of the image, even when the opposite edge has been moved outside the image (due to moving the center). However, this sizing could have been done by using background-size and background-position for the same result. For instance:

	background: -webkit-radial-gradient(50% 10%, farthest-side, black, white 1px, black 99%, white 100%);

...is visually the same as...

	background: -webkit-radial-gradient(contain, black, white 1px, black 99%, white 100%) 0% 100% / 100% 160%;

The vertical 100% of the background-position causes it to align with the bottom, while the 180% of the background-size causes its center to be 80% of the background area up from that edge (half of the 160%, and 20% down from the opposite edge, which is twice the radius offset of the other syntax, since the center is at half the image height). It's not complicated (except to explain); it is just another way of thinking about what you are doing, using simpler gradient syntax in combination with familiar background syntax.

If all radial gradients were 'contain' without having to say so, then this would be roughly the same number of characters (actually a few less):

	background: -webkit-radial-gradient(black, white 1px, black 99%, white 100%) 0% 100% / 100% 180%;

When I did a search Google Code Project Hosting for CSS files containing 'closest-corner', I found only one, where it combined it with 'circle' and centered gradient centers, essentially meaning the same as 'cover' (or 'contain' with color-stop positions multiplied by 1.42). (It also used the older moz syntax that included degrees.)

CONCLUSIONS:

• Explicit sizing within the image offers no advantages over 'background-size', aside from the entirely theoretical and unlikely or rare use outside of backgrounds.
• 'farther-side' is only useful when there is a offset center within the image. But such offsets within the image are not necessary, because the entire image can be positioned within the background positioning area.
• 'closest-corner' is not useful.
• Limiting the implicit sizing keywords to 'contain' and 'cover' would be more than enough power to include, and would simplify the understandability of the syntax. Since 'cover' is the same as ('contain' x 142%), the syntax can be simplified even more, and eliminates multiple way of doing the same thing. This simplification would be a great benefit to authors and learners, with little to no downside.
• Letting all radial gradients be 'contain' has a slight expressive advantage (for when you want a hard edge color stop to just touch the sides) over letting them all be 'cover', without giving up any of the power to create gradients that do cover.  
• Letting the 'circle' keyword create an implicit aspect ratio for the image will benefit authors positioning a round gradient that has width or height that is based on a percentage of the background positioning area.
• Adopting a more limited approach now for CSS3 images does not prevent us from adding back in more controls for CSS4 images if there turns out to be a greater demand for it (which I still consider unlikely).
• Having a syntax that is nothing but color stops and an optional "circle" keyword is so simple that anyone can quickly and easily learn it, without having to understand complex interrelationships of more complex syntax. Given that these type of images will most often be used almost exclusively in backgrounds, it will be sufficient to use existing background properties for position and size.
• Any radial gradient needs beyond those can be handled with this simpler syntax are more appropriate for SVG. We already have this distinction for linear gradients, to great effect, and we should carry the same guidance into designing the radial-gradient syntax.
• The need for radial gradients starts out being small compared to the need for linear gradients. The need to offset the gradient within the image or size the gradient within the image for gradients appearing in non-backgrounds, or in places where they cannot be sized or positioned by other means (mostly this is list markers and border images) extremely minor, and we should not be adding complexity to the syntax for just those rare and limited cases.





[1] http://leaverou.me/css3patterns
[2] http://leaverou.me/css3patterns/#hearts
[3] http://code.google.com/query/#q=%22(moz%7Cwebkit)-radial-gradient%22%20filetype:css (then click on Project Hosting)
[4] http://code.google.com/query/#q=%22farthest-side%22%20filetype:css (then click on Project Hosting)

Received on Sunday, 4 September 2011 18:38:02 UTC