Re: [css3-images] Linear gradients feedback

On Sun, Aug 29, 2010 at 3:14 PM, Simon Fraser <smfr@me.com> wrote:
> On Aug 29, 2010, at 9:56 am, Tab Atkins Jr. wrote:
>
>> tl;dr: I've added instructions for serializing gradients.  I want to
>> add an ending-point argument to linear-gradient(), and change the
>> color transitions to happen in pre-multiplied rgba space.
>>
>> Simon and I recently had a twitter conversation about issues keeping
>> him from wanting to implement the draft gradient syntax in Webkit.
>>
>> One of the issues was that gradients didn't have a defined
>> serialization.  I've now defined this in the draft.
>>
>> Another issue was that linear-gradient() had too much "magic".  There
>> is explicitly different behavior for all four combinations of
>> specified and omitted values in the first argument.  He'd prefer that
>> the syntax have more regular behavior, such that a missing value is
>> just filled in with a default value.  He sent an email to this effect
>> back during the original syntax discussions:
>> http://lists.w3.org/Archives/Public/www-style/2009Nov/0050.html
>>
>> After thinking it through, I believe his complaint is valid, though I
>> strongly disagree with the fix he proposes in that email.  Instead,
>> I'd like to add an additional argument to the linear-gradient() syntax
>> - another <bg-position> given as the second argument to the function,
>> for specifying an ending-point for the gradient.
>
> Here's the current draft, for those following:
>
> <http://dev.w3.org/csswg/css3-images/#linear-gradients>
>
> It currently has:
>
>  linear-gradient([<bg-position> || <angle>,]? <color-stop>, <color-stop>[, <color-stop>]*);
>
> and you're suggesting (I think; my grammar is weak):
>
>  linear-gradient([<bg-position> <bg-position>? || <angle>,]? <color-stop>, <color-stop>[, <color-stop>]*);

Actually:

linear-gradient( [ [<bg-position> || <angle>], [ <bg-position>, ]? ]?
<color-stop>, <color-stop> [, <color-stop>]*);

Just to avoid ambiguities - you can't have two <bg-position>s next to
each other without some sort of separator.

So, for example, you'd type "linear-gradient(top left, bottom right,
white, silver);".

>> Let me explain why I think this addresses his complaint adequately.
>>
>> Right now, there are four cases that can be expressed by the first
>> argument to linear-gradient() - the <bg-position> can be present or
>> absent, and the <angle> can be present or absent.  These are all
>> handled somewhat specially, in what appears to be an ad hoc manner.
>> Really, though, the cases are very consistent and in-line with the
>> "unspecified values just take a default" ethos that Simon wants;
>> however, this isn't apparent with only four cases to look at.  Adding
>> an ending-point argument makes this much more apparent, and also
>> addresses several use-cases.
>>
>> In his email, Simon notes that it appears that linear-gradient() is
>> actually expressing two or three different syntaxes.  He's right -
>> there are two substantially different behaviors here, keyed by the
>> presence or absence of the angle.
>>
>> If the angle is absent, the gradient-line simply goes from one point
>> to another point.  If both points are specified, you're done.  If the
>> ending-point is missing, it defaults to rotating the starting-point
>> around the center of the box.  If the starting-point is missing, it
>> defaults to "top center".
>>
>> If the angle is present, the gradient-line starts at the
>> starting-point, extends outward at the angle specified, and ends when
>> a line drawn perpendicular to the gradient-line would intersect with
>> the ending-point.  If both points are specified, you're done.
>
> You'd have to describe what happens when the angle is perpendicular to the line
> between the endpoints.

That's already addressed - the actual ending-point of the
gradient-line then overlaps with the starting-point, which means it's
a degenerate gradient and is displayed as a solid-color image the
color of the last color-stop.  (This situation can come up with a few
other existing combinations of values.)


>> If the
>> ending-point is missing, it defaults to the corner of the box in the
>> direction of the angle.  If the starting-point is missing, it default
>> to the corner of the box in the opposite direction of the angle.
>
> That seems inconsistent (aka "magic") to me, and a surprising difference
> from the no-angle case. In the no-angle case, the end point is obtained by mirroring
> around the center of the box. In this case, a "extend to the edge of the box"
> method is used.

Hmm, I can see the logic of reusing the "mirror around the box" logic
here for determining a missing end-point.  That seems fine to me.
I'll make the change.

We still *need* an angle-based default for a missing starting-point,
though.  As far as I can tell, there is no single default that works
well for angle-based gradients, let alone one that also works well for
no-angle gradients.


> I think there are several reasons to try to avoid the special behaviors
> that the current spec has.
>
> With most compound or shorthand CSS properties, you can omit values
> that have default values without change in behavior. This is not
> the case with gradients. For example:
>
>  linear-gradient(top left, ...)
>
> is not equivalent to:
>
>  linear-gradient(top left, 0, ...)
>
> Similarly
>
>  linear-gradient(200deg, ...)
>
> is not equivalent to
>
>  linear-gradient(top 200deg, ...)
>
> This violates the principle of least surprise for authors.

I don't think that either of those are particularly surprising,
personally, but I can imagine opinions differing.  I don't believe
it's possible to define a default angle in such a way as to preserve
the current behavior of a two-points gradient, nor is it possible to
define a default start point that will preserve the usefulness of the
current behavior of an angle gradient.


> A second reason to try to avoid these branches in the gradient algorithm is because
> it makes interpolating between them for animation harder. Without a canonical
> form, you can't map all inputs to a common representation for interpolation.

Correct, you can't.  Angle-based gradients and no-angle gradients
calculate their positions in fundamentally different ways which cannot
be reconciled without losing information.  Angle-based gradients have
an additional constraint that no-angle gradients don't have.  Even if
we did define a shared default for the first <bg-position>, there is
*no way* to define a default <angle> for both cases.  The mere
presence of an <angle>, no matter the value, adds an additional
constraint to how the gradient positions itself that can't be
generally replicated in the two-point gradient.


>> In either case, missing points have consistent, well-defined default
>> values.  The defaults are just completely different between the two
>> cases.  I believe this is necessary to make the two cases actually be
>> useful.  Simon's proposal for always defaulting the starting-point to
>> the top-left is insufficient for either case.  If the angle is absent,
>> it makes the default behavior a diagonal gradient, when the most
>> common gradients on the web and elsewhere are vertical and horizontal.
>> If the angle is present, it makes the only useful angle values be
>> between 0deg and -90deg - 0deg to 90deg and 180deg to 270deg have
>> unintuitive results, while 90deg to 180deg appears to have no effect
>> at all in most cases (it draws the gradient completely outside of the
>> box unless there are color-stops with positions lower than 0%).
>
> I was trying to think along the lines of considering the two
> <bg-positions> not as endpoints of the gradient specifically, but
> as the corners of a virtual box that bounds the gradient (following
> the "extend to the edges" logic that exists in the spec). Unfortunately
> this doesn't solve the problem of having sensible defaults. The gradient
>
>    linear-gradient(top 45deg, ...)

That never did extend from the top-left to the bottom-right.  It
starts in the top-center, actually.


> would no longer extend from the top-left to bottom-right corners.
>
>    linear-gradient(top left 45deg, ...)
>
> would, however.
>
>> I believe that the current level of "smarts" used in differentiating
>> the defaults of the two cases is the minimum necessary to make the
>> syntax useful.  The benefits of simplifying it further would be
>> strongly outweighed by the downsides of the bad defaults, in my
>> opinion.
>
> Fundamentally, there are two ways to describe a linear gradient; either as
> two end points, or a start point (or center point), angle, and length.
>
> That argues for two forms:
>
>  linear-gradient(<bg-position> <bg-position>, ...);
>
> and
>
>  linear-gradient(<bg-position> <angle> <length>, ...);
>
> This latter form would be more useful if <bg-position> specifies the center-point,
> because then any angle works. A length of 100% could be specified to that which
> has the gradient extend to fill the box. The defaults are a little tricky; you'd
> want the <bg-position> to default to center, and <angle> to 90deg for a vertical gradient.

Wouldn't this still suffer from the same interpolation problems?  Is
there a hidden benefit to doing it this way that I'm missing?

Regarding the actual proposal:

So, if <bg-position> was the center point for the gradient, I suppose
the starting-point and ending-point for the gradient-line are based on
the old "where a line perpedicular to the gradient-line would
intersect" corners based on the <angle>?  Where are the 0% and 100%
points located for color-stops?  Making the <bg-position> specify the
center-point seems to be sacrificing both readability and writeability
for the purpose of gaining a definite default value, which I think is
backwards.

If it's specifying the starting-point, then going with a <length> or a
second <bg-position> seems about equivalent.  I think I argued in the
old threads that if you want an angle-gradient of a particular length
you can just specify those lengths in the color-stops.

~TJ

Received on Monday, 30 August 2010 19:43:34 UTC