Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Insert page breaks dynamically based on content overflow

I have a large webpage generated through many Vue components. The rendered HTML structure is somewhat similar to this:

<header></header>
<element1></element1>
<element2></element2>
<element3></element3>
<table></table>
<element4></element4>
<footer></footer>

I want to print this nicely on an A5 page having header and footer repeated on every printed page. I've tried this with 2 approaches:

Converting HTML structure into page containers and splitting elements based on total clientHeight. Something like

<section class="page">
    <header></header>
    <element1></element1>
    <element2></element2>
    <element3></element3>
    <footer></footer>
</section>
<section class="page">
    <header></header>
    <table></table>
    <element4></element4>
    <footer></footer>
</section>

Or, Adding CSS page break properties to header and dynamically inserting at content overflow locations. For example

<header></header>
<element1></element1>
<element2></element2>
<element3></element3>
<footer></footer>
<header style="page-break-before: always"></header>
<table></table>
<element4></element4>
<footer></footer>

The DOM traversal to find overflow point looks like

var availHeight = 20;    // Height of A5 page - tolerance
var body = document.querySelector('body');
var initialWidth = body.style.width;
body.style.width = '14.85cm';

if (body.clientHeight > availHeight) {    // if content exceeds page height
    var scrollHeight = 0;
    for (var i = 0; i < body.children.length; i++) {
        var child = body.children[i];
        scrollHeight += child.clientHeight;

        if (scrollHeight > availHeight) {    // if children traversed till now make up to the height of page
            if (child.clientHeight < availHeight) {
                child.insertAdjacentHTML('beforebegin', '"page break html here..."');
                scrollHeight = 0;
            }
        }
    }
}
body.style.width = initialWidth;

However, both approaches give inaccurate results while printing.

CSS @page is not supported by Safari, hence cannot be useful.

display: table-header-group and table-footer-group doesn't work either.

Is there a different cross-browser solution to achieve nicely printed pages with repeating headers and footers and not clipping/overlapping any content, or if the DOM traversal code can be improved to be more generic and stable? Can things be corrected or simplified here?

Thanks in advance. I haven't reached a satisfactory workaround or solution for this problem.

like image 489
Shreevardhan Avatar asked Mar 07 '18 10:03

Shreevardhan


1 Answers

Not sure if this qualifies for a separate answer or should just be a comment with some (hopefully useful) tips.

So... this is a battle web developers have long fought. You can find various answers under this question, which this question actually seems to be a duplicate of (somewhat).

Disclaimer: I can't test the techniques mentioned on iOS, Safari nor IE myself atm. They're more of a recollection of my previous experiences and knowledge gathered from SO posts and various articles' comment threads.

I'll try to provide some pointers and techniques to get you slightly closer to full browser support, but there are no promises until @print is fully supported :/

  1. Fixed positioning. For whatever reason, browsers repeat the fixed elements in printed pages. The hard part is tweaking your page margins so that your header and footer don't overlap with page content, but then you should be golden. It's not easy since even with @page support, the page margin also pushes down the fixed content. And without support, you'd have to resolve to some display: table hacks maybe. I've replicated the fixed solution in a simple example (I inlined the CSS so you could open it in a separate HTML file locally for easier testing):

https://jsfiddle.net/vjvu8uox/

  1. @media print is something you could look into alongside @print, since it's supported since CSS2. It doesn't provide the same powerful selectors, but allows you to tweak your styles slightly for print-specific purposes.

    "The ‘print’ and ‘screen’ media types are defined in HTML4. The complete list of media types in HTML4 is: ‘aural’, ‘braille’, ‘handheld’, ‘print’, ‘projection’, ‘screen’, ‘tty’, ‘tv’. CSS2 defines the same list, deprecates ‘aural’ and adds ‘embossed’ and ‘speech’. Also, ‘all’ is used to indicate that the style sheet applies to all media types."

  2. Tables. A lot of browsers position the thead and tfoot elements at the top and bottom of every page on print. This works if your thead and tfoot don't contain multiple tr elements. The support for this landed in Chrome this March (2018). The useful keywords for this are:

    thead { display: table-header-group; }
    
    tfoot { display: table-footer-group; }
    

I'm wondering if you could maybe combine 1. and 3. to firstly position your header and footer with fixed so they'd repeat and then maybe create empty elements to act as table-header-group and table-footer-group, which could provide paddings for the fixed elements?


I'll quickly also address your traversal script by saying that you might want to account for your Vue's app container (unless you've mounted your Vue instance to the body element, which is considered somewhat of a bad practise) and all its nested children. In that sense, @MunimMunna's jQuery snippet serves you better. I'd rewrite it as vanilla JS and using semi-modern utility methods, we could easily fix the Safari support for that script (I do not have the time right now, but can look into it coming Monday when I'm on my work rig/Mac to test it on Safari).

Best of luck in your print-support endeavours!

like image 75
kano Avatar answered Nov 05 '22 02:11

kano