Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Page break on screen (css multiple column layout)

Tags:

html

css

I want to display a multiple columns document in HTML. A multiple column "box" is restarted in print after a page break. I would like to emulate this behavior (page break) on-screen, so column heights should not be higher than the user-agent window's height.

Just using a new container for each virtual page would be easy but I don't know where exactly the screen will end so I want text to be able to flow between these virtual pages.

I'm aware that today's JS has the abilities necessary to pull this off, but I would like to use CSS as this is a layout problem.

like image 982
vbence Avatar asked Sep 08 '12 10:09

vbence


People also ask

Does CSS support multiple columns for laying out text?

CSS Multi-column Layout is a module of CSS that adds support for multi-column layouts.

Which layout allows to set multiple columns of text like in Newspapers?

The basic idea of multicol, is that you can take a chunk of content and flow it into multiple columns, as in a newspaper.

Which CSS technology allows for easy creation of multi-column layouts?

The W3C multi-column module is a CSS level-three working draft, proposed by the W3C to extend the current CSS box model. The module's intent is to allow content to flow into multiple columns inside an element.


1 Answers

I had precisely the same desire as described in this question. Here is an HTML/CSS-only solution that worked well enough for my purposes, and may be useful to others. It has a few potential drawbacks, which I'll describe before giving the code. In the discussion below, I use the word "subpage" for what the questioner called a "virtual page," namely one horizontal set of contiguous columns of content of the desired height (note the text continues from the bottom right of one subpage to the top left of the next subpage.)

  1. It requires that the content being flowed into subpages be reiterated in the HTML multiple times (although it only appears once in the rendered output). In the case in which the content is dynamically generated (as in my case, as the result of Wordpress function calls) it may not pose any difficulty to loop and produce multiple copies of the content.
  2. It requires choosing a specific, arbitrary maximum number of subpages that can occur on the screen; any content that flows beyond that is simply lost. In my case, the purpose of the site meant that I knew there could be at most five subpages, so this was no real obstacle.
  3. A repetitive sequence of CSS lines is required, one for each possible subpage. If the repeated occurrences of the content are being generated programmatically, it is little additional work to put the one required CSS property inline in a "style" attribute on the repeated copies, and that's what I did in practice. (The example shown below separates all HTML and CSS for clarity.)
  4. Possibly the most significant drawback: I could not find a precise way to (in effect) calculate the height of the resulting flowed configuration (namely, the number of subpages actually needed to fit all of the content, times the height of each subpage). I had to resort to an approximation that can be pretty rough. As a result, if the extra vertical height tacked on in this approximation is large enough to ensure that none of the text will be clipped vertically, then sometimes there will be a lot of vertical whitespace at the bottom of the layout. Again, in my case the subpaged layout was the last thing that occurred on my webpage, so whitespace at the bottom really didn't faze me.

OK, to the actual code. The basic strategy here is simple: Break the same text multiple times into columns of the desired subpage height. Stack the resulting horizontal sets of columns on top of each other, but move each successive copy exactly one subpage width page to the left. Finally, clip the whole stack (with overflow:hidden;) to the width of the subpage. The key difficulty is getting that clipping div to be the correct height. It would be straightforward if it were possible to CSS select all of the subpage units that end up with no ink inside them when they are clipped and then simply not display them, but I couldn't find a way to do this. The rough approximation I ended up with is to invisibly lay out all of the content one more time, in the desired subpage width and column structure, and use the height of that layout to determine the height of the clipping. However, that layout is always shorter, because it's more efficient to lay content out in a bunch of long columns than in successive blocks of short columns, and plus you need some kind of subpage separators or the subpaged layout becomes super confusing. So I fudged this by just throwing in a big bottom padding in that clipping div. One could make a better approximation if CSS calc() allowed integer division or rounding. And finally, the whole task would be trivial if CSS Regions ever took off, but that's looking unlikely.

Sorry about the tl;dr; -- here's the code, html first, then CSS:

<div class="column-paginator">
    <div class="vertical-strut mypage-h vertical-fuzz">
        [COPY OF CONTENT HERE]
    </div>
    <div class="column-paginator-clipper">
        <div class="mypage-h mypage-v column-paginator-0">
            [COPY OF CONTENT HERE]
        </div>
        <div class="mypage-h mypage-v column-paginator-1">
            [COPY OF CONTENT HERE]
        </div>
        <div class="mypage-h mypage-v column-paginator-2">
            [COPY OF CONTENT HERE]
        </div>
        <div class="mypage-h mypage-v column-paginator-3">
            [COPY OF CONTENT HERE]
        </div>
        <div class="mypage-h mypage-v column-paginator-4">
            [COPY OF CONTENT HERE]
        </div>
    </div>
</div>

And now the CSS:

div.column-paginator {
    position:relative; /* Ensure we can anchor positions to this element */
}
div.vertical-strut {
    visibility: hidden; /* Only laying this out to get its height */
}
div.vertical-fuzz {
    padding-bottom: 300px; /* extra height because unpaginated text
                            * takes less vertical height. This is a
                            * crude measure. */
}
div.mypage-h {
    /* The desired horizontal setup of the column pagination */
    columns: 2 200px;
    column-gap: 2em;
}
div.column-paginator-clipper {
    /* The div that actually constrains the output */
    position: absolute;
    top: 0px;
    left: 0px;
    height: 100%;
    max-height: 100%;
    overflow: hidden;
}
div.mypage-v {
    /* the desired vertical setup of the column pagination */
    height: 300px;
    position: relative;
    overflow: visible;
    margin-bottom: 1.5em;
}
div.mypage-v:after {
    /* Automatically throw in subpage separator. */
    content: "";
    height:1em;
    position: absolute;
    left: 0;
    top: 100%;
    width:500%;  /* must be MAX_SUBPAGES * 100% */
    background: lightcyan;
}
/* Note the 2em below is precisely the column-gap above; not sure how
 * to enforce/encode that equality in CSS.
 */
div.column-paginator-0 { left:calc(0 * (-100% - 2em)); }
div.column-paginator-1 { left:calc(1 * (-100% - 2em)); }
div.column-paginator-2 { left:calc(2 * (-100% - 2em)); }
div.column-paginator-3 { left:calc(3 * (-100% - 2em)); }
div.column-paginator-4 { left:calc(4 * (-100% - 2em)); }
div.column-paginator-5 { left:calc(5 * (-100% - 2em)); }
like image 161
Glen Whitney Avatar answered Sep 20 '22 04:09

Glen Whitney