Re: [css, bug of specification] negative margins, painting order, stacking context.

Andrew Fedoniouk wrote:
> 
> As we know[1]  rendering of static children of elements happens in 
> following order:
> 
> for all static children:
>  step #1: draw backgrounds of these elements;
>  step #2: draw content of these elements (text in particular);
> 
> This algorithm (strange, imo) works in most cases but plays
> badly with negative margins.

I do not find this algorithm strange.  It provides a way of ensuring, by 
default, that text remains visible under various situations in which 
overflow and negative margins are thrown into the mix.  If, for each box 
in turn, the text were painted at the same time as the backgrounds, it 
would not be possible to achieve this visibility.

> Here is an example of very strange (non-intuitive if you wish) effect 
> that can
> be observed when negative margins are used:
> 
> <html>
> <head>
>  <style>
>    div.red     {  width:100px;    height:100px;    
> background-color:red;   margin-bottom:-100px;    }
>    div.green  {  width:90px;      height:90px;      
> background-color:green;    }
>    div.red:hover { background-color:orange;  }
>    div.green:hover { background-color:lime;  }
>  </style>  </head>
> <body>
>  <div class="red">This text has red background.</div>
>  <div class="green"></div>
> </body>
> </html>
> 
> Problem here is that due to [1] these two elements have non-trivial 
> overlaying structure.
> Each point inside div.green participate in following layers:
> 
> 1) background of  div.red
> 2) background of  div.green
> 3) content(text) of  div.red
> 4) content(text) of  div.green
> 
> This clearly produces logical errors in detection of div.red:hover and 
> div.green:hover
> conditions. Try to move mouse over and near the text.

This is not a logical error.  The text boxes are the "uppermost" of the 
boxes under the mouse and so receive the hover event.  These text boxes 
form part of the red element, which is thus the element to which the 
event is propagated. (Events apply to (pseudo-)elements, not boxes, of 
course.)  When you hover over the green box away from the text, the 
green box is the "uppermost" of the boxes under the mouse and so its 
corresponding element reacts to the hover event.

> That appears as a bug of the specification.  Non-intuitive, non-logical, 
> etc.

In no sense is this a bug in the specification.  The behaviour is both 
intentional and desirable.  In particular, if the specification were 
different on this point only, it would be impossible to reproduce the 
current behaviour (maximum text visibility) by other means, whereas it 
currently /is/ possible to obtain the results you seem to desire through 
the use of stacking contexts and z-index.

> Possible resolutions of the problem:
> 
> A) To change [1] so drawing of the element background and its content is 
> atomic:
> 
>    For each static child draw its background and content(text) on top of 
> it.
>    (That  is how it is made in all UI systems I know)

This does not achieve the effect you desire, because the now-atomic 
background+text boxes of non-positioned in-flow elements will still be 
rendered in document tree order, and merely setting negative margins 
will not cause an earlier box to be painted on top of a later box.

Moreover, the rendering which this approach does achieve is undesirable:

A stacking context consists of several painting layers onto which 
different types of box are painted.  Currently, in between the stacking 
layers for background and text of in-flow non-positioned dependants 
("descendants") comes the stacking layer for non-positioned floated 
dependants.

So proposal (A) effectively moves the painting layer for floats to after 
the now-unified painting layer for background and text of in-flow 
non-positioned dependants.  Overflow from floats would now obscure the 
any adjacent text boxes belonging to their containing stacking context. 
  Again, if you /want/ this behaviour you can currently  have it through 
the use of stacking contexts; but if this behaviour were not the default 
you would not be able to reproduce it.

> B) Treat all elements having negative margins as a separate layer laying 
> over
>    normal static children of the element thus rendering will happen this 
> way:
> 
>   step #1, for all static children *without* negative margins do:
>      step #1.1: draw backgrounds of elements;
>      step #1.2: draw content of elements (text in particular);
>   step #2, for all static children *with* negative margins do:
>      step #2.1: draw backgrounds of elements;
>      step #2.2: draw content of elements (text in particular);

Whereas proposal (A) reorders the painting layers within a stacking 
context, proposal (B) introduces new painting layers in order to divide 
up the current behaviour in which boxes in the same painting layer are 
painted in document tree order.  It is proposal (B) which was being 
discussed and formulated in your previous correspondence in a different 
thread.  However, it does not seem well thought through.

Your suggested change boils down to dividing the current painting step 
for text into two parts (text for non-negative- and text for 
negative-margined boxes) and inserting a new painting layer for the 
backgrounds of in-flow non-positioned dependants with negative margins 
in between.  To avoid that this new painting layer for backgrounds sit 
on top of the painting layer for non-positioned floats (which would of 
course be unacceptable), the floats layer has to be moved to after the 
new text layer and we are more-or-less back to situation (A) and 
additionally we now have a different approach to honouring the document 
tree order.

Moreover, with this approach, if divs D1, D2 and D3 are in-flow and 
non-positioned in a common stacking context, and are such that both D1 
and D2 have a negative margin-bottom, then D2 would overlap D1 and D3 
would overlap D2.  But this doesn't seem to solve the issue you were 
wishing to address, which was that by specifying a margin-bottom on D1 
you wanted the bottom part of D1 to be rendered on /top/ of the top part 
of D2.  To to address your issue you would actually have to 
differentiate between boxes with negative margin-top, boxes with 
negative margin-bottom and boxes with negative top and bottom margin in 
some way, and treat each case not only differently but also with respect 
to the margin values of the surrounding element; hence there are even 
more cases than these three, corresponding to the possible different 
combinations.  The task of fleshing out the resulting algorithm - and 
expressing it in the specification - is not one which I would relish, 
nor one which has merit IMO.


Tab Atkins Jr. wrote:
 > On Thu, Feb 5, 2009 at 2:57 PM, Andrew Fedoniouk
 > <news@terrainformatica.com> wrote:
 >
 >> Tab Atkins Jr. wrote:
 >>
 >>> This makes complete sense.  Elements later in the document order are
 >>> later in the paint order as well.
 >>>
 >> Try this:
 >>
 >> <html>
 >> <head>
 >>  <style>
 >>   .layer div { width:100px; height:100px; }
 >>   .layer div.principal { margin:-10px 0; width:110px; }
 >>   .layer div:hover { background:gold !important; }
 >>  </style> </head>
 >> <body>
 >>
 >>  <div class="layer">
 >>   <div class="static" style="background:red">Red</div>
 >>   <div class="principal" style="opacity:0.5; background:blue">Blue</div>
 >>   <div class="static" style="background:green">Green</div>
 >>  </div>
 >>  <hr/>
 >>  <div class="layer">
 >>   <div class="static" style="background:red">Red</div>
 >>   <div class="principal" style="background:blue">Blue</div>
 >>   <div class="static" style="background:green">Green</div>
 >>  </div>
 >>
 >> </body>
 >> </html>
 >>
 >> Which rendering on these two div.layer's is more intuitive?
 >>
 >
 > Personally?  The second.
 >
 > But as you said before, people can disagree about what's the most 
intuitive.
 >
 > My point is that the 'normal' rendering (document order maps to paint
 > order) isn't *un*intuitive, it provides an effect that can't be easily
 > replicated in any other way, and the alternatives *can* be easily
 > replicated through other methods.

I am in complete agreement with Tab's three statements.

Regarding the third, I suspect that the current algorithm was conceived 
to reflect the order in which rendering engines actually need to handle 
the various boxes: floats sit on top of the backgrounds of incident 
elements but push their line boxes to the side.  So the rendering would 
appear to run in parallel to the calculations being made, in the order 
backgrounds-floats-inlines.  (Not that this in itself is an argument in 
favour of the status quo, because presumably the rendering order doesn't 
have to match the calculation order.  Rather, Tab's argument is the 
argument in favour.)

However, we should note that the use of stacking contexts and z-index to 
obtain the result you require (viz, D1, D2, D3 with D2 on top of D1 and 
D3) does have consequences:  by positioning D2 to "promote" it to a 
later painting layer, you are of course making it a stacking context 
which has ramifications for its descendants, and furthermore you are 
making it the containing block for its closest positioned descendants. 
This is not necessarily a desirable side-effect of what you wished to 
achieve.

Andrew Fedoniouk wrote:
 > <html>
 > <head>
 >  <style>
 >    .layer div { width:100px; height:100px; }
 >    .layer div.principal { z-index:-1; position:relative; margin:-10px 0;
 > width:110px; }
 >    .layer div:hover { background:gold !important; }
 >    .aside { position:fixed; left:70px; top:0; bottom:0; width:10px;
 > background:magenta; z-index:-1;}
 >  </style> </head>
 > <body>
 >
 >  <div class="layer">
 >    <div class="static" style="background:red">Red</div>
 >    <div class="principal" style="background:blue">Blue</div>
 >    <div class="static" style="background:green">Green</div>
 >  </div>
 >  <div class="aside" >.</div>
 > </body>
 > </html>
 >
 > In this example I just wanted to put ".layer div.principal" element
 > behind its siblings. Intuitive solution for me is to use negative 
margins.

Why?  Negative margins are not for putting things behind other things in 
some specific order.  That's what stacking contexts and z-index are for.

 > But if I will start playing with z-indexes and positioning then the
 > problem expands to the interaction with the whole DOM tree.

Of course.  Box placement in the third dimension is controlled by the 
stacking mechanism, which is a global concept;  it expected that one 
should have to deal with the tree as a whole in order to achieve a given 
result.

 > And about previous example: do you have an idea why opacity changes
 > the element position in rendering stack?

CSS3-COLOR[1] states:

"If an element with opacity less than 1 is not positioned, 
implementations must paint the layer it creates, within its parent 
stacking context, at the same stacking order that would be used if it 
were a positioned element with ‘z-index: 0’ and ‘opacity: 1’. If an 
element with opacity less than 1 is positioned, the ‘z-index’ property 
applies as described in [CSS21], except that ‘auto’ is treated as ‘0’ 
since a new stacking context is always created."

(Alas this paragraph introduces yet /another/ undefined term, "stacking 
order" to an already ambiguous description of the stacking model in 
CSS21.  The phrase "at the same stacking order" is equivalent to the 
phrase "on the same painting layer" which I have been using.)

 > What should intuition tell us there?:)

This behaviour is related not to intuition but rather to implementation. 
CSS3-COLOR[1] states:

"Since an element with opacity less than 1 is composited from a single 
offscreen image, content outside of it cannot be layered in z-order 
between pieces of content inside of it. For the same reason, 
implementations must create a new stacking context for any element with 
opacity less than 1."


Cheers,
Anton Prowse
http://dev.moonhenge.net

[1] http://www.w3.org/TR/css3-color/#transparency

Received on Saturday, 7 February 2009 18:53:20 UTC