Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Printing breaks bootstrap layout

I have the following page:

<html>
<head>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
  <div class="container">
      <div class="row">
          <div class="col-xs-12 col-sm-6">
              <p>Column 1,1 in small row 1 in xs</p>
          </div>
          <div class="col-xs-12 col-sm-6">
              <p>Column 1,2 in small row 2 in xs</p>
          </div>
          <div class="col-xs-12 col-sm-6">
              <p>Column 2,1 in small row 3 in xs</p>
          </div>
          <div class="col-xs-12 col-sm-6">
              <p>Column 2,2 in small row 4 in xs</p>
          </div>
      </div>
    </div>
  </body>
</html>

This page appears as expected when viewing (i.e. there's 2 columns and 2 rows on small+ screens and 4 rows on mobile) however the problem is when trying to print.

When I do CTRL+P in Chrome I get the following preview:

enter image description here

The page size is set to A4. The issue does not occur in Firefox (but does also occur in IE and Edge)

Any idea how to get the print view to render the small (or medium) layout instead of the mobile view?

like image 884
apokryfos Avatar asked Jan 02 '23 05:01

apokryfos


1 Answers

Demo

https://so51099397.surge.sh/

(I feel the fix that I have implemented is a little too much but I don't think there is any other way to fix the problem)

(If you are wondering why I hosted a site instead of something like SO snippet or jsfiddle I have explained in the comment)

Explanation

Why am I getting "mobile" view in print?

  1. width of A4 = 210mm ≈ 8.27 in ≈ 595 px (72 dpi)
  2. xs applies when width < 768px
  3. So technically what chrome is doing is correct and what other browsers are doing is incorrect

How to fix this?

  1. Bootstrap is written in mobile-first pattern.
  2. Meaning the stylesheet looks something like this:
    .col-xs-12 {
        width: 100%;
    }
    @media (min-width: 768px){
        .col-sm-6 {
            width: 50%;
        }
    }
  1. Now somehow if I unwrap rules inside @media (min-width: 768px) only during print, my problem will be solved.
  2. To implement this we will get all rules inside @media (min-width: 768px) using document.styleSheets (don't forget to add crossorigin="anonymous" in bootstrap <link> or else you would get an error while accessing the stylesheet)
  3. Once we have all those styles, we'll wrap them in a <style> and append in <head> on beforeprint event
  4. On afterprint event we'll remove that <style>

Code (same as in demo) :

window.addEventListener("beforeprint", function(){
    let printStyleSheet = document.createElement("style");
    printStyleSheet.classList.add("print-stylesheet");
    document.head.appendChild(printStyleSheet)

    let printRulesText = "";
    Array.from(document.styleSheets).forEach(styleSheet => {
        Array.from(styleSheet.cssRules)
        .filter(rule => rule.media && /(min-width: 768px)/g.test(rule.media.mediaText))
        .forEach(rule => {
            Array.from(rule.cssRules).forEach(rule => printRulesText += rule.cssText)
        })
    })
    printStyleSheet.innerHTML = printRulesText;
})
window.addEventListener("afterprint", function(){
    Array.from(document.querySelectorAll(".print-stylesheet")).forEach(style => style.remove())
})

Note: This will give you the tablet view (sm) if you want the medium desktop view (md) you'll have to unwrap both, min-width: 768px as well as min-width: 992px similarly for large desktop (lg) also

Will this work?

  1. It works for the code in the question
  2. It should work for the other components too.

Can I fix it by applying html, body { width: 769px } during print? (as 769 > 768)

  1. No it won't work because media queries work on viewport and not on width of html or body

Okay so can I append <meta name="viewport" content="width=769"> and remove other viewport meta during print? (as 769 > 768)

  1. Umm... No. It doesn't work. (I think it should tho but somehow doesn't work)

Any other fixes?

  1. Manually override xs styles like @Arex did

  2. Get the bootstrap css source code and modify it using print media queries something like this:

     .col-xs-12 {
         width: 100%;
     }
     @media (min-width: 768px){
         .col-sm-6 {
             width: 50%;
         }
     }
    
     /* add such rules everywhere */
     @media print{
         .col-sm-6 {
             width: 50%;
         }
     }
    
  3. Put all contents of the current page in an iframe with width: 100%; height: 100%; border: none; and make everything else display: none;. But I feel it won't work because if the iframe gets a scrollbar the contents out of the view won't get printed. Also looks much more hackish

PS: It's pretty obvious but let me explicitly say this... The fix implemented (as seen in the demo) does not fix just the grid system or the snippet in the question... But fixes the behaviour of ALL components (button, table, inputs, grid, etc) by faking a min-width: 768px media query. That's what the OP wants. Not individually fixing the components

PPS: Read Floyd's comment we can create print stylesheet during build-time itself which is better

like image 108
Devansh J Avatar answered Jan 14 '23 02:01

Devansh J