CSS Page Floats

Shortname: css-page-floats
Level: 3
Group: csswg
Status: ED
ED: http://dev.w3.org/csswg/css-page-floats/
Previous Version: https://hg.csswg.org/drafts/raw-file/108d7e3ff204/css-page-floats/Overview.html
Editor: Johannes Wilm, Vivliostyle, johanneswilm@vivliostyle.com
Former Editor: Håkon Wium Lie, Opera Software, howcome@opera.com
Abstract: This module describes features often used in printed publications.
Abstract: Figures and photos are often moved to the top or bottom of columns and
Abstract: pages, along with their captions. Some elements (e.g., call-outs) push
Abstract: aside other content in the flow of text, and sometimes one column is
Abstract: given a different styling than others. This draft describes how to
Abstract: achieve these effects, both on paper and screens.
Ignored Terms: near, 3em, intrude, top-corner, bottom-corner

Introduction

This section is not normative. This module describes features often used in printed publications. Figures and photos are often moved to the top or bottom of columns and pages, along with their captions. Some elements (e.g., call-outs) push aside other content in the flow of text, and sometimes one column is given a different styling than others. This draft describes how to achieve these effects, both on paper and screens.

Overview

In page-based layouts, images and figures are often displayed at the top or bottom of pages. This specificaton adds new keywords on the 'float' property to create page floats. A page float can float inside its natural column/page, or its placement can be deferred to a following column/page with the float-defer properties. Page floats can be set to span several columns, thereby supporting commonly used newspaper layouts. New values on the 'clear' property adds further ways of refining layouts with page floats.

Not all multicol elements are constrained by the page box. Therefore, a more accurate term for page floats may be column floats as all of them are constrained by the column box. However, in most cases, page floats seems like a better term.

Floating to the top/bottom: top, bottom, snap

These new keywords on 'float' have been added:

top
The float is floated to the top of the column
bottom
The float is floated to the bottom of the column
snap(<length> <length>? [, top | bottom | near ]?)
Makes the element float to the top or bottom if it naturally appears within a certain distance from the top or bottom. The length value(s) specifies the maxium distance from the top/bottom that an element must be within in order to be floated; one length value specifies the distance from both the top and the bottom, two length values specify the distance from the top and bottom, respectively. The optional keyword value specifies where the element is floated: top, bottom, or the nearest of the two. The initial value is 'near'. If 'near' is in effect and the element is within the specified distance both from the top and the bottom, bottom wins. An element is considered to be a float if it has a snap() value, even if the element does not appear within the specified distance. This way, it can be determined whether an element is float or not without laying out the document.
snap
same as snap(2em, near)
These new keywords only apply in paged media; in continous media declarations with these keywords are ignored. Elements with any of these new keywords are called 'page floats'. Each page float has a natural column, which is the column where the element would have started to appears it it was not a float. Also, each page float has a natural page, which is the page where the element would have started if the was not a float. Unless other constrained by lack of space or other float-* properties, page floats should appear in their natural column on the natural page.
Float figure to top of natural column:
.figure { float: top }
sample rendering
.figure { float: top; width: 50% }
sample rendering
In this example, a figure naturally appears close to a column break. There is not enough space for the figure in the first column, and it is therefore placed in the second column, leaving white space at the bottom of the first column. sample rendering To avoid the white space, the image can be floated to the nearest edge (in the block direction):
.figure { float: snap }
In this example, the figure is already at the nearest edge, so it does not move. However, page floats allow subsequent content to be displayed before the page float and the white space can therefore be filled: sample rendering
In this example, two figures naturally appear in the text flow: sample rendering A typographer would typically try to avoid single lines of text above/below figures, which can be achieved with:
div.figure { float: snap(1.5em) }
The length value specifies the reach of the snap function; in this example the second figure is affected, but not the first.
In this example, two figures naturally appear in the text flow: sample rendering To make the figures snap to the nearest edges, this code can be applied:
div.figure { float: snap(2.5em) }
The resultant rendering is: sample rendering
Float figure to top of the natural column, spanning all columns:
.figure { float: top; column-span: all }
sample redering
In this example, tables will snap to the top/bottom if the top/bottom of the border box is closer than '3em' from the top/bottom of the page/column.
table { float: snap }
table { float: snap(3em) }
table { float: snap(3em, bottom) }
table { float: snap(3em 2em, bottom) }

Do numeric values, to represent line numbers, make sense, like for orphans/widows?

Spanning columns

The 'column-span' property is extended with integer values so that elements can span several columns. If the specified integer value is equal to, or larger than the number of columns in the multicol element, the number of columns spanned will be the same as if 'column-span: all' had been specified.
In this example, a commonly used newspaper layout is easily described:
body { columns: 3 }
img.A { column-span: 2; width: 100% }  /* image spans two columns */
.one { column-span: 2 }                /* lead paragraph spans two columns */
sample rendering
Further, the 'page' value is added to 'column-span' so that the page (and not the column or element) becomes the reference.
In this example, the footer is floated to the bottom of the last page (and not the bottom of the article element):
footer { float: bottom; column-span: page }

<article>
  ...
  <footer>...<.footer>
</article>

An alternative way to express this would be to add a separate propertye, e.g., float-reference: column | multicol | page

Deferring floats: float-defer-column, float-defer-page

A page float can be deferred to a following column/page with these new properties:

'float-defer-column'

Name: float-defer-column
Value: <integer> | last | none
Initial: none
Applies to: page floats
Inherited: no
Percentages: N/A
Media: visual
Computed value: specified value

'float-defer-page'

Name: float-defer-page
Value: <integer> | last | none
Initial: none
Applies to: page floats
Inherited: no
Percentages: N/A
Media: visual
Computed value: specified value
These properties specify whether page floats should appear in their natural column/page, or in a following column/page. Values are:
none
the page float appears in the natural column/page
<integer>
A positive integer value indicates that the page float should be displayed in a following column/page. A negative integer value indicates that the page float should be displayed in a following column/page, counted from the last column/page. When counting columns, the starting point is the last column of the multicol element on the natural page. When counting pages, the starting point is the last page of the multicol element.
Float figure to the top of the column that follows the natural column:
.figure { float: top }
.figure { float-defer-column: 1 }
Float figure to the top of the next-to-last column:
.figure { float: top; float-defer-column: -1 }
sample rendering
Float figure to top of the last column of the multicol element on the current page:
.figure { float: top; float-defer-column: last }
sample rendering
In combination with 'column-span', the figure is floated to the top corner of the multicol element on that page:
.figure { float: top; column-span: 2; float-defer-column: last; width: 100% }
sample rendering
Float figure to the top of the second column, spanning two columns:
.figure {
  float: top; column-span: 2;
  float-defer-column: 1;
}
sample rendering
Float figure to the top right, leaving one full column:
.figure {
  float: top; column-span: 2;
  float-defer-column: -1;
}
sample rendering Given that there are four columnn, the same layout would be achived with this code:
.figure {
  float: top; column-span: 2;
  float-defer-column: 1;
}
Float figure to the top of the first column on the next-to-last page:
.figure { float: top }
.figure { float-defer-page: -1 }
Float figure to the top of the next-to-last column on the next-to-last page:
.figure { float: top }
.figure { float-defer-column: -1 }
.figure { float-defer-page: -1 }
last
The page float should be displayed in the last colum, or on the last page.
Float figure to the top of the last column on the natural page:
.figure { float: top }
.figure { float-defer-column: last }
sample rendering
Float figure to the last column on the last page:
.figure { float: top }
.figure { float-defer-column: last }
.figure { float-defer-page: last }
none
The page floats should appear in their natural column/page, if possible.
Zero is not a legal value.

Clearing page floats

Page floats may request to not be stacked, or to be the only page float on a page/column through new values on the 'clear' property:
top/bottom
The page float requests to be the only page float at the top/bottom of the column. If there is already a page float at the requested position, the page float is moved to the next column which does not have a page float in the requested position.
column
The page float requests to be the only page float in the column. If there is already another page float on the page, the page float is moved to the next page which does not have a page float.
page
The page float requests to be the only page float on the page. If there is already another page float on the page, the page float is moved to the next page which does not have a page float.
In this example, the two figures may appear in the same column:
.figure { float: bottom; clear: none }

<div class=figure></div>
<div class=figure></div>
In this example, the two figures will appear in different columns:
.figure { float: bottom; clear: column }

<div class=figure></div>
<div class=figure></div>
In this example, the two figures may appear at the bottom of the same column due to clearing only at the top:
.figure { float: bottom; clear: top }

<div class=figure></div>
<div class=figure></div>
In this example, the two figures will appear in different columns due to clearing at the bottom:
.figure { float: bottom; clear: bottom }

<div class=figure></div>
<div class=figure></div>
In this example, the two figures end up the top corner of two different pages:
.figure { float: top; float-defer-column: last; clear: page }

<div class=figure></div>
<div class=figure></div>
In this example, the two figures request different positions, and they may therefore end up in the same column:
.figure.one { float: top; clear: top }
.figure.two { float: bottom; clear: bottom }

<div class="figure one"></div>
<div class="figure two"></div>

Floating inside and outside pages

Two allow content to flow to the inside and outside of a page, these keywords are added to the 'float' property:
inside
On a right page, this value is synonymous with 'left'. On a left page, this value is synonymous with 'right'.
outside
On a left page, this value is synonymous with 'left', On a right page, this value is synonymous with 'right'.
These new values do not create page floats, the are simply aliases for 'left' and 'right'.
.figure { float: outside }

Should there be a way to combine float: top/bottom with left/right?

Should there be a way to delete page floats that end up lonesome on pages?

Wrapping around page floats

Name: float-wrap
Value: none | wrap
Initial: none
Applies to: page floats
Inherited: no
Percentages: N/A
Media: visual
Computed value: specified value
This property indicates whether other content may wrap around a page float:
none
other content may not wrap around the page float
wrap
other content may wrap around the page float
The 'intrude' value only works in combination with one of these keywords: 'left'/'right'/'top'/'bottom'/'top-corner'/'bottom-corner'.
img { float: top; column-span: 2; float-wrap: wrap; width: 120%;  }
In this example, the image is wider than the column and will therefore intrude into the neighboring column. At the bottom of the middle column is a long word that is clipped in the middle of the column gap. sample rendering

The 'float-offset' property

Name: float-offset
Value: <length> <length> ?
Initial: 0 0
Applies to: floated elements
Inherited: no
Percentages: see prose
Media: visual, paged
Computed value: one or two absolute lengths
This property pushes a float in opposite direction of the where it has been floated with 'float'. If one value is specified, it is the horizontal offset. If two values are specified, the first is the horizontal and the second is the vertical offset. If no vertical value has been specified, the vertical offset is set to zero. This property can only influence a float along an axis it has been floated.
img {
  float: left;
  float-offset: 2em 3em;
}
In this example, the image is floated to the left. Therefore, 'float-offset' may only push the element in the horizontal direction, and the vertical value is ignored.
Negative values are allowed; a negative values will push the float in the same direction as it has been floated with 'float' This property may move floats into other column than where they naturally appear.
img {
  float: right;
  float-offset: 5px;
}
sample rendering
Percentage values are computed according to this formula:
  (containing-block-width - float-width) * percentage
  (containing-block-height - float-height) * percentage
Pull quotes are often centered in a column. In this example, the pull quote is floated to the right, and then pushed back into the center.
.pullquote {
  float: right;
  float-offset: 50%; /* 50% centers the box */
}
When negative values are set on this property, the column gap is also part of the calculation:
  ((containing-block-width + 2 * column-gap) - float-width) * percentage
img {
  float: top right;
  float-offset: -50% 3em;  /* 50% centers the box */
  width: 120%;
}
sample rendering
img {
  float: top right;
  float-offset: -80% 2em;
  width: 100%;
}
sample rendering

Overconstrained page floats

In many cases, the specified values on these properties cannot be honored.
The number of columns is limited, and high values therefore cannot be honored:
.figure { float: top; float-defer-column: 1000 }
A narrow screen may only have room for one column, in which case this request cannot be honored:
.figure { float: top; float-defer-column: -5 }
In long documents, all content cannot fit on the last page, and this rule therefore cannot be honored:
p { float: top; float-defer-page: last }
Page floats are processed in the order they appear in the source. However, the visual order of page floats may not ne the same as the source order.
Consider this code:
.one { float: top; float-defer-page: last; column-span: all }
.two { float: top; clear: column }

<div class=one></div>
<div class=two></div>
In this example, the first element requests to appear on the last page, while the second element requests to appear in the natural column. If the natural column of the second element appears on a page before the last page, the second element will appear visually before the first.
Consider this code:
.one { float: top; float-defer-page: last; column-span: all }
.two { float: top; clear: column }

<div class=one></div>
<div class=two></div>
If all content can fit on one page, the first page will also be the last page. The first element is processed first and is placed on top of the first page. Then the second element is processed. It reqests a clear top, somthing which is not possible on the first page. Therefore, a second page is created and the first element is moved there. Even if the first element requests to be on the last page, it will not appear there.
When resolving over-constrained layouts, the order of importance for defined goals are:
  1. honor basic multi-column layout ('columns', 'column-span: all', 'column-gap' etc)
  2. honor 'column-span: <integer>'
  3. honor 'clear: top/bottom/pcolumn/page'
  4. honor 'float-defer-page'
  5. honor 'float-defer-column'
  6. honor 'float: top/bottom'
  7. display all content (as described by other CSS properties)
  8. keep the number of pages to a minimum

Changes

Changes from the 24 September 2013 CSS Page Floats Editor's Draft:
  1. Removed sections on Regions and Exclusions
  2. Changed editor

Acknowledgments

This specification is made possible by input from Tab Atkins Jr., David Baron, Lars Erik Bolstad, Bert Bos, Mike Bremford, Michael Day, Werner Donné, Brady Duga, James Elmore, Elika Etemad, Michel Fortin, Daniel Glazman, Melinda Grant, Ian Hickson, Laurens Holst, Brad Kemper, Toru Kawakubo, Rune Lillesveen, Peter Linss, Cameron McCormack, Paul E. Merrell, Del Merritt, Markus Mielke, Kelly Miller, Alex Mogilevsky, Peter Moulder, Shinyu Murakami, Michel Onoff, Anton Prowse, Liam R E Quin, Jacob Grundtvig Refstrup, Florian Rivoal, Christian Roth, Allan Sandfeld Jensen, Simon Sapin, Morten Stenshorne, Philip Taylor, Ian Tindale, Ladd Van Tol, Tarquin (Mark) Wilton-Jones, Steve Zilles, Tantek Çelik and the CSS Working Group members.