W3C home > Mailing lists > Public > www-style@w3.org > February 2009

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

From: Andrew Fedoniouk <news@terrainformatica.com>
Date: Sat, 07 Feb 2009 13:50:25 -0800
Message-ID: <498E0221.8010005@terrainformatica.com>
To: Anton Prowse <prowse@moonhenge.net>
CC: "www-style@w3.org List" <www-style@w3.org>

Anton Prowse wrote:
> 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.

Static text can be covered by background of positioned elements.
Text can be clipped by overflow:ed elements.

Following your "this visibility" (as far as I understand it)
requirement we should draw the text on top of everything and
never clip it.

So clarification "this visibility" principle of yours will be appreciated.

>> 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.

I understand what is written in the spec. Question is "why?".
Why text box of element "A" has to be rendered after background of some 
other non-child element "B" is drawn?

If you have atomic rendering of element(its background and its
content as a whole) it is indistinguishable from the case
defined in the specification currently.

The only difference in rendering arises when you have negative
margins. And that is what I am talking about.

That is also source of the problem mentioned by David Hyatt I think.

>> 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.

"maximum text visibility" appears as a fuzzy logic statement.

I would like to know why "overflow:hidden" complies to "maximum text 
visibility" and say "margin-top:-10px;" is not.

>> 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.

I am not sure I understand you here.

Static floating elements that overly the text they are "sitting in"
is a bug of implementation.

"If a shortened line box is too small to contain any further content,
  then it is shifted downward until either it fits or there are no more
  floats present."

source: http://www.w3.org/TR/CSS21/visuren.html#floats

>> 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.

I think that A and B should be just combined together.
That would be most human comprehensible and intuitive :) solution.

> 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.

I do not think that it should be as such complex as you defined.

It has to be just three layers:
   1) normal static elements drawn atomically;
   2) static elements with negative margins drawn atomically;
   3) static floated elements drawn atomically;

This will solve problem mentioned by David Hyatt.
Thus it will allow in CSS to implement visual effects
without the need to redraw the whole tree from its root each time.

> 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.

What is the purpose of these "negative margins" then?

Negative margins (NM) mean that one element is sitting on top of
another. I would like to see specification of what is sitting
on top of what. That is it.

Current non-specified specification de-facto moves bottom of the NM
element underneath its next sibling and on top of its previous sibling.
That creates stacking context by itself. But very strange one.

>  > 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.)

Need of exceptions like this just highlights the flaw of
current way of rendering backgrounds of elements and their content
on separate layers. In case of atomic rendering this particular
problem will not even exist.

This particular exception makes hacks like this:

   border-bottom: 1px solid red;
   margin-bottom: -1px;

quite popular without doing any good for the rendering speed.

>  > 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."

Atomic drawing assumes that *any* element establishes its own stacking 
context for its children. If it would be in place from the
very beginning then these kludges will not be needed.

When elements interbreed background and content separately with
the rest of DOM tree you definitely will need more of those "but if"s.

... wondering what will happen when we will start practically define
animations and the like effects on such flacky cake.

> Cheers,
> Anton Prowse
> http://dev.moonhenge.net
> [1] http://www.w3.org/TR/css3-color/#transparency

Andrew Fedoniouk.

Received on Saturday, 7 February 2009 21:51:10 UTC

This archive was generated by hypermail 2.4.0 : Friday, 25 March 2022 10:07:33 UTC