Proposals/z-index

From SVG

TODO: comp-op should create a new stacking context.

From http://www.w3.org/mid/4AE510F6.6050404@jwatt.org

Hi list,

Here's my first pass at some spec text to define 'z-index' behavior for SVG.

Note that Andrew Emmons has made a different proposal to create a new property
with similar functionality to z-index on the Working Group's public list:

  http://lists.w3.org/Archives/Public/public-svg-wg/2009OctDec/0027.html

I would personally prefer that we try to define sensible behavior for 'z-index'
in SVG before resorting to creating a new property with similar yet different
behavior, and I think what follows actually turns out to be pretty reasonable.
For authors and implementations that have to deal with both HTML and SVG -
especially inline in the same document - it's especially desirable to avoid
creating a new similar-yet-different property that would require authors to know
about two different properties (and their models), and possibly require two
different implementation strategies.

Contrary to what I thought during the last F2F meeting, I now think that
simplified stacking context behavior (as opposed to no stacking context
behavior) is desirable. (Stacking contexts are explained below for those of you
that are unfamiliar with them.) A couple of reasons for this are:

  * To have sane behavior, avoiding excessive implementation complexity
    and avoiding poor performance whenever 'z-index' (or whatever it's
    called) is used on descendant elements of an element that is clipped
    (implicitly or explicitly), filtered, masked or has an 'opacity' of
    less than 1. (For an implementation that supports inline SVG in
    HTML, that can mean every SVG element.)

  * Preventing fragments of supposedly self contained SVG that contains
    'z-index' from interacting in unexpected ways with other content in
    a document. This is probably important in any SVG content of
    reasonable complexity, but also for any snippets of SVG content that
    should be able to be dropped into an arbitrary document, markers,
    symbols, SVG generated by JavaScript libraries for mash-ups, etc.

The draft proposed text follows. It is broken up into two sections: first some
informative text that would probably be best in a separate primer document;
second the normative text intended for the spec.

-Jonathan


-----------------------------------------------------------------------

The following section is informative.

SVG elements are normally painted in document order using the painters model
(elements that come later in the document paint over the top of elements that
came earlier in the document). However, sometimes it is desirable to be able to
force an element to a higher or lower position visually without changing its
position in the DOM tree. This functionality is provided by the 'z-index' property.

Understanding the 'z-index' property involves understanding the terms "stack
level" and "stacking context". Let's consider each in turn:

The 'z-index' property moves an element up or down a conceptual z-axis into
layers called "stack levels". By default all the elements in a document are in
stack level 0. By setting 'z-index' on an element to an integer other than 0, an
author can force an element into a higher or lower stack level, thereby pushing
that element above or below the elements in other stack levels. Documents are
rendered by painting each stack level in turn, from lowest to highest, and
painting the elements in each stack level in document order (i.e. using the
painters model). Here is a simple example:

  <svg xmlns="http://www.w3.org/2000/svg">
    <rect  x="0" width="100" height="100" fill="red"    z-index="-1"/>
    <rect x="40" width="100" height="100" fill="lime"/>
    <rect x="80" width="100" height="100" fill="blue"   z-index="1"/>
    <rect x="60" width="100" height="100" fill="aqua"/>
    <rect x="20" width="100" height="100" fill="yellow" z-index="-1"/>
  </svg>

In this example there are three stack levels: -1, 0 (the default) and 1. The red
and yellow rects are in stack level -1, the lime and aqua rects are in stack
level 0 (the default), and the blue rect is in stack level 1. Going from lowest
stack level to highest, and painting the elements in each stack level in
document order, the painting order is: red, yellow, lime, aqua, blue.

In addition to putting an element into a stack level, setting an element's
'z-index' property to an integer value also creates something called a "stacking
context". A stacking context is a stack level container that groups together an
entirely new set of stack levels for that element's descendants. A 'z-index'
property on any descendant will then place that descendant into one of the stack
level in this new stacking context (descendants go into its level 0 by default),
not into the stack levels of the parent or any other stacking context. When an
element that creates a new stacking context is painted, so is the new stacking
context that it created, along with all of that stacking context's stack levels
and the elements that they contain.

Stacking contexts allow self contained document fragments to use z-index without
needing to worry about their children interleaving badly with other elements
(z-indexed or otherwise) in other parts of the document. In the following
example, the z-index attribute on the 'g' element is set to zero. This creates a
new stacking context to contain the 'g' element's children without moving the
'g' to a different level in the document's root stacking context:

  <svg xmlns="http://www.w3.org/2000/svg">
    <g z-index="0">
      <!-- this is a self contained graphic -->
      <rect x="40" width="100" height="100" fill="lime" z-index="1"/>
      <rect x="20" width="100" height="100" fill="yellow"/>
    </g>
    <rect x="60" width="100" height="100" fill="aqua"/>
    <rect x="0" width="100" height="100" fill="red" z-index="-1"/>
  </svg>

The example's root stacking context contains two stack levels: -1 and 0. The red
rect is in level -1, and the 'g' element and aqua rect are in level 0. Inside
stack level 0, the 'g' element's z-index attribute creates a new nested stacking
context at the 'g' for the 'g' element's children. In this child stacking
context there are two stack levels: 0 and 1. The yellow rect is in level 0 (the
default), and the lime rect is in level 1.

Painting of this example starts with the stack levels of the root stacking
context. First the red rect is painted in level -1, then in level 0 the 'g'
element is painted followed by the aqua rect. When the 'g' element is painted,
the child stacking context that its z-index created and all of that context's
stack levels are also painted. In this child stacking context, first the yellow
rect in level 0 is painted, followed by the lime rect in level 1. It's only
after the 'g' and the stacking context that it creates has been painted that the
aqua rect is painted. Note that this means that although the z-index of 1 for
the lime rect is a higher value than the (implicit) z-index of 0 for the aqua
rect, the containment provided by the 'g's child stacking context results in the
aqua rect painting over the lime rect. The painting order is therefore: red,
yellow, lime, aqua.

Note that setting 'z-index' to an integer value is not the only way that a new
stacking contexts is triggered for an element's descendants. See the normative
text for a full list.


-----------------------------------------------------------------------

The following section is normative.

CSS specifies a property called 'z-index'. The CSS rules that define the effect
of the 'z-index' property [1][2] were written specifically for the CSS box
model, and those rules do not make sense as they stand for most SVG elements
(most SVG elements do not participate in or establish a CSS box model layout).
This section specifies how implementations must handle the 'z-index' property on
elements in the SVG namespace.

'z-index'
    Value:           auto | <integer> | inherit
    Initial:         auto
    Applies to:      all elements, but see prose *
    Inherited:       no
    Percentages:     N/A
    Media:           visual
    Computed value:  as specified

* /Contrary to the rules in CSS 2.1, the 'z-index' property applies to all SVG
elements regardless of the value of the 'position' property, with one exception:
as for boxes in CSS 2.1, outer 'svg' elements must be positioned for 'z-index'
to apply to them./

The 'z-index' property specifies:

  1. The stack level of the element in the current stacking context.
  2. Whether the element establishes a new local stacking context.

Values have the following meanings:

  <integer>
    This integer is the stack level of the element in the current
    stacking context. The element also establishes a new local
    stacking context for its descendants.

  auto
    The stack level of the element in the current stacking context
    is the same as its parent element, unless its parent established
    a new stacking context, in which case its stack level is 0. The
    element does not establish a new local stacking context.

A new stacking context must be established at an SVG element for its descendants if:

  * it is the root element

  * the 'z-index' property applies to the element and its computed
    value is an integer

  * the element is an outer 'svg' element, or a 'foreignObject', 'image',
    'marker', 'mask', 'pattern', 'symbol' or 'use' element

  * the element is an inner 'svg' element and the computed value of its
    'overflow' property is a value other than 'visible'

  * the element is subject to explicit clipping:

      * the 'clip' property applies to the element and it has a
        computed value other than 'auto'

      * the 'clip-path' property applies to the element and it has a
        computed value other than 'none'

  * the 'opacity' property  applies to the element and it has a
    computed value other than '1'

  * the 'mask' property applies to the element and it has a computed
    value other than 'none'

  * the 'filter' property applies to the element and it has a
    computed value other than 'none'

For a user friendly explanation of the terms "stack level" and "stacking
context", see the z-index primer document. For the normative rules regarding how
stacking contexts and stack levels affect SVG elements, see below.

Stacking contexts and stack levels are conceptual tools used to describing the
order in which elements must be painted one on top of the other when the
document is rendered, and for determining which element is highest when
determining the target of a pointer event. Stacking contexts and stack levels do
not affect the position of elements in the DOM tree, and their presence or
absence does not affect an element's position, size or orientation in the
canvas' X-Y plane - only the order in which it is painted.

Stacking contexts can contain further stacking contexts. A stacking context is
atomic from the point of view of its parent stacking context; elements in
ancestor stacking contexts may not come between any of its elements.

Each element belongs to one stacking context. Each element in a given stacking
context has an integer stack level. Elements with a higher stack level must be
placed in front of elements with lower stack levels in the same stacking
context. Elements may have negative stack levels. Elements with the same stack
level in a stacking context must be stacked back-to-front according to document
tree order.

With the exception of the 'foreignObject' element, the back to front stacking
order for a stacking context created by an SVG element is:

  1. the background and borders of the element forming the stacking
     context, if any

  2. child stacking contexts created by descendants with negative stack
     levels, primarily ordered by most negative first, then by tree order

  3. descendants with 'z-index: auto' or 'z-index: 0', in tree order

  4. child stacking contexts created by descendants with positive stack
     levels, primarily ordered by lowest index first, then by tree order

Since the 'foreignObject' element creates a "fixed position containing block" in
CSS terms, the normative rules for the stacking order of the stacking context
created by 'foreignObject' elements are the rules in Appendix E of CSS 2.1.


1. http://www.w3.org/TR/CSS2/visuren.html#propdef-z-index
2. http://www.w3.org/TR/CSS2/zindex.html

Usecases

  • To avoid reloading of external resources when visually reordering content (moving nodes in the DOM tree temporarily removes them from the document, causing a reload). bogus usecase?
    • e.g. jwatt's coverflow demo where the "cover's" contained iframes
  • Planets around sun, moons around planets
  • Performance. It's cheaper to change z-index than to move the node in the DOM. If the node is complex, the performance difference may be significant.
  • Positioning labels logically next to the item they label in the DOM, but bringing all labels to the top graphically. Useful for maps and other things.
  • Reordering without script support (CSS :hover for example).

Unsolved usecases

  • Moving an element above everything else in the document without moving it's node - not something that z-index generally supports given the concept of 'stacking contexts'. Perhaps a 'stacking-context' property could be added, allowing you to specify the ID of an ancestor element to use as the stacking context when you need more control?

Issues

One of the issues with z-index is that it requires manual tracking of actual index values. For example, in the coverflow example, what should the indexes of the "covers" be? What about when new items are interleaved between the existing covers? Probably the index of existing covers needs to be recalculated.