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

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

From: Anton Prowse <prowse@moonhenge.net>
Date: Mon, 09 Feb 2009 20:39:29 +0100
Message-ID: <49908671.4060802@moonhenge.net>
To: "www-style@w3.org" <www-style@w3.org>

Andrew Fedoniouk wrote:
> 
> 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.

Of course.  Positioned elements lie on a higher painting layer. 
Overflow:hidden explicitly instructs the UA to clip the contents.

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

It's not a principle "of mine"; it's a description of the status quo, 
which certainly does not result in the behaviour that you extrapolated.

Consider D1 and D2 as consecutive divs.  D1 is static with a fixed 
height; perhaps it's a horizontal menu.  D2 is static.  Currently, the 
backgrounds of D1 and D2 are painted before the text of D1 and D2.  When 
the user increases the text size via their browser, the contents of D1 
may overflow.  Happily, the text is still visible because it is painted 
/after/ the background of D2.  This outcome cannot arise if the 
background and contents of D1 are painted together, before the 
background of D2.


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

I imagine so;  yet "this clearly produces logical errors in detection of 
[hover conditions]" is a rather odd choice of phrase given that the 
hover detection is working as specified.

Question is "why?".

Presumably the question you are actually asking is, "is this behaviour, 
which is a consequence of the algorithm currently specified, 
desirable?".  Whilst I imagine that this edge-case behaviour was hardly 
the motivating factor for the current design of the stacking mechanism, 
it is an expected consequence of the algorithm and is it not 
troublesome.  I am not clear about what specifically you are objecting 
to here.  That hovering over the red box's text fires a hover event on 
the red element and not the green, even though the green element's 
background is foremost?  This can be solved by positioning the green 
element.

> Why text box of element "A" has to be rendered after background of some 
> other non-child element "B" is drawn?

For the reason I stated above: visibility of overflowing line boxes.

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

No it isn't.  The background of D2 would be drawn on top of the overflow 
from D1.

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

The two issues are related but not equal in scope.  David Hyatt was 
pondering whether elements with overflow should be made into 
(pseudo-)stacking contexts like floats and be painted on a higher 
painting layer than that for text.  As far as I am aware, this was not 
because he felt that the non-overflow div should be atomic as concerns 
its background and text; rather, it was because there is some 
awkwardness in the particular case of overflow with (CSS- or 
JS-triggered) scrollbars.  The change he proposed preserves the 
non-atomicity and focuses on changing the behaviour of an already rather 
special type of box.


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

If it seems fuzzy, it was because I cannot have expressed myself well. 
Text should be as visible as possible subject to the desirable and 
currently-specified constraint of certain types of elements (currently 
positioned elements with non-negative z-index, and elements with 
opacity<1) being rendered on higher painting layers.

Again, this is not /my/ principle;  it is the current behaviour, and one 
which I think is good.


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

Because if overflow has explicitly been set to hidden, then the author 
has chosen to limit visibility.  Whereas if the author has chosen to use 
negative margins, there may have been many varied reasons for doing so, 
unrelated to text visibility.  More on this below.


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

The float does not overlay the text.  However, your proposal (A) has the 
consequence that the /overflow/ from floats does overlay the text 
(instead of lying underneath the text that the floats "sit in", as per 
the current behaviour).  This means that the surrounding text is 
obscured (and is possibly unselectable via traditional mouse 
interaction) if the float's overflowing content itself has a background.

However, the argument against proposal (A) concerns not just overflow of 
floats but also overflow of static non-floated elements, as described 
earlier.


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

It would be less complex.  It would also be less user-friendly because 
the current behaviour which ensures that text remains visible after 
increasing the text size or having strings which are too long for their 
container would be lost.

Note that it is proposal A which is most destructive in this sense.

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

Well, it does if we are to address the use case that you originally 
raised when you began this correspondence.  I assume that you are no 
longer motivated by that use case.

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

This is true.  However, aside from reducing the number of situations in 
which overflow remains visible, it is also (to me) less comprehensible 
and intuitive:  would negative /horizontal/ margins trigger this effect 
(even though you use cases concerned negative vertical margins?  It 
would be hard for authors to correlate negative horizontal margins with 
a painting layer promotion.


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

For pulling the border area box past surrounding boxes (or pulling the 
surrounding boxes inwards past the border area box, depending upon your 
perspective).  A typical example would be pulling left floats leftwards 
over their parent's left padding to create a less linear feel to a given 
design.

The /stacking order/ that a negatively-margined box should have was not 
really even contemplated in CSS2.0 as far as I can tell -- it certainly 
didn't appear to be a primary consideration when introducing negative 
margins -- and this was part of the reason why stacking in CSS2.0 was so 
drastically underspecified.

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

There is already specification of what is sitting on top of what.  The 
issue is that you find this part of the specification disagreeable.

> Current non-specified specification de-facto moves bottom of the NM
> element underneath its next sibling and on top of its previous sibling.

It is not "non-specified".  You seem to be of the opinion that negative 
margins are somehow a forgotten aspect of the stacking mechanism. 
They're not.  Boxes with the same painting layer in a stacking context 
are stacked back-to-front according to document tree order, irrespective 
of their margin values.

 > That creates stacking context by itself. But very strange one.

I do not understand what you mean here.


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

It doesn't;  it merely highlights the exceptional nature of opacity.

 > In case of atomic rendering this particular
> problem will not even exist.

yes, but atomic rendering is a sledgehammer which "solves" some special 
cases such as David Hyatt's overflow issue and the exceptional behaviour 
of opacity, but at the significant cost of making overflow less useful.

> This particular exception makes hacks like this:
> 
> .static-nm
> {
>   opacity:0.99;
>   border-bottom: 1px solid red;
>   margin-bottom: -1px;
> }
> 
> quite popular without doing any good for the rendering speed.

I have never seen that in the wild, and I can think of no reason 
whatsoever why any author would choose to do that, given that exactly 
the same rendering can be achieved by

.static-nm
{
    position: relative;
    border-bottom: 1px solid red;
    margin-bottom: -1px;
}

without hurting rendering speed at all.


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

I fully assume that you did not intend to mean that.  Atomic drawing of 
an element's background and text is one thing.  Forming a stacking 
context is an entirely different concept.

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

Perhaps there will be more "but if"s.  But each one (like opacity) is 
introduced due to some peculiar behaviour of the thing being introduced. 
    Hence the right way to fix it is to special-case that thing.

Your proposal (A) is less complex but also less powerful and less 
user-friendly than the current behaviour.

I note that your last email almost entirely concerned proposal (A), 
whereas in my opinion proposal (B) is more interesting, although I 
dislike the resulting behaviour as concerns overflow, and I am 
unconvinced by the principle ("negative margins are special") which 
motivates it.

Cheers,
Anton Prowse
http://dev.moonhenge.net
Received on Monday, 9 February 2009 19:40:22 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 17:20:16 GMT