Positioned Layout proposal

Since I've worked at Chrome, one of the things I've heard developers
complain about a lot is the weakness of absolute positioning.  For
example, our app devs commonly want to position tooltips relative to
arbitrary elements in a contenteditable area.

As well, there are certain types of layouts that are not really
possible to do in any existing or proposed layout mode in CSS.
However, they may be easy to describe in terms of absolute positioning
relative to particular edges of other elements.  (I have an example,
but at the moment it only works in dev channel Chrome because it uses
classlist - I'll describe it later*.)

To fix this, I've written a personal draft of a new Positioned Layout
spec I'd like to see adopted by the group.  It's currently hosted on
my blog: <http://www.xanthir.com/blog/b48H0>.

As it's fairly long (it's in a rough spec form), I'll summarize here
the basic points:

1. I've specified the behavior of abspos-in-table as a general truth
of positioned elements, so that future layout models will treat them
consistently.  I've come to realize that this behavior really is best;
for one, it makes my draft a lot saner, as I can refer directly to the
position of the placeholder for later properties.

2. I've slightly reinterpreted the 'position' values to be consistent
with the above, and make all the values interact well with the new
properties I've created.  Pages still using 'position' as CSS2.1
defines it won't change at all.

3. I've introduced four new properties -
position-root-top/right/bottom/left - and position-root as a shorthand
to specify them all together.  This is the meat of the proposal.  I
was inspired by Daniel's "position:new" proposal at
<http://daniel.glazman.free.fr/weblog/position__new.html>.  In simple
terms, this just allows you to specify precisely which edge the
top/right/bottom/left properties measure themselves from.

    There are several predefined keywords which provide useful
references; you can specify edges of the previous element, parent
element, containing block, root element, or viewport.  You can specify
edges of arbitrary elements in the document with the element()
function (currently a moz-specific function used only as an <image>
type).

    There's also a more complex value that can be used to specify more
complex relationships.  In the aforementioned complex layouts*, it's
not always sufficient to specify the position of an element relative
to the edge of a single other element; you need to be able to refer to
the lowest edge amongst several elements, so you can position the
element lower than any of them.  I've defined the <edge-reference>
value for this - it takes a set of element/edge pairs and selects the
topmost/bottommost/rightmost/leftmost of them to use as the reference.
 This also uses an elements() function (note the plural), similar to
element(), for referring to a set of elements that match a selector,
so you can include the edges of all of them.

4. I haven't yet specced it, but I'll be including some way to
restrict element edges from not going past a certain other edge.
Right now I just have some brainstorming on how to do it.  For
example, when positioning a tooltip, you don't want it to extend out
of the viewport.  This also happens to be great for doing frozen table
headers:

thead {
  position: relative;
  position-root-top: bottommost(parent top, viewport top);
  position-contain: parent;
  top: 0;
}

This make the <thead> act normally if the table is on screen or below
the screen, stay visible when the table starts to scroll off the top
of the screen, but scroll away with the table when it goes completely
off-screen, exactly like frozen headers are supposed to.  A similar
technique can be used on "tr > th".

It still needs some fleshing out, but overall I think it's
feature-complete (or will be when I decide how I want to do
position-contain or whatever).  What does everyone think?  Can we
adopt this?  (Possibly we'd need to put it in the charter; is that
okay?)

~TJ

* Since I haven't yet gotten the layout demo separated into something
that'll work in public builds, I'll describe it in ASCII for now.
What I have is a newsreader app with the following layout:

+------+--------+--------+--------+
| 10am |   b1   |   m1   |   s1   |
|      |        |        |--------|
|      |        |--------|   s2   |
|      |--------|   m2   |--------|
|      |   b2   |        |   s3   |
|      |        |--------|--------|
|      |        |   m3   |   s4   |
|------|--------|        |--------|
|  8am |   b3   |--------|   s5   |
|      |        |   m4   |--------|
               ...

For each block of time, we subdivide stories by importance.  "Big"
news has a decent blurb and a picture, "medium" news has a small
blurb, and "small" news just has a title.

The edge of the timezones is ragged; m3 belongs to the 10am timezone
but extends into the 8am block, so you don't have unsightly gaps
between timezones because the story lengths are somewhat different.

This isn't too difficult to do in traditional CSS right now, but then
I throw a wrench into things.  When you click on a story, it expands
and shows the full story, looking something like this (assume I
expanded m2):

+------+--------+--------+--------+
| 10am |   b1   |   m1   |   s1   |
|      |        |        |--------|
|      |        |--------|   s2   |
|      +--------------------------+
|      |       expanded  m2       |
|      |                          |
|      |                          |
|      |                          |
|      +--------------------------+
|      |   b2   |   m3   |   s3   |
|      |        |        |--------|
|      |        |--------|   s4   |
|------|--------|   m4   |--------|
|  8am |   b3   |        |   s5   |
|      |        |--------|--------|
               ...

That is, the expanded story fills all three columns, and pushes
stories in all three columns below it.  This *cannot* be done with
traditional CSS layout, because there is no way for an element to live
in three columns at once.

You can sort of fake this by giving up and using tables (one 4-cell
row per timezone initially, split into multiple rows when you expand a
story, with rowspans and colspans set appropriately), but that kills
your ability to do continuous stories across timezones; you're forced
to have gaps at the end of every timezone so that the next timezone
can start fresh in a new row.  (You're forced to have gaps above an
expanded story, but that's both unavoidable and generally small, so
it's okay.)

That was unacceptable, so we ended up with an innovative solution - do
the entire thing with absolute positioning, but pair it with a
constraint solver in javascript so we could just specify which edges
were tied to which other edges, and it would automatically compute
positions for us.  This works wonderfully, and the app is beautiful,
but having the layout done by a js constraint solver isn't great.  We
could replace the entire thing by a handful of CSS rules using the
concepts in my draft, just using js to decide which edges are tied to
what when you expand/contract stories.

Received on Monday, 18 October 2010 19:32:58 UTC