html2pdf.js icon indicating copy to clipboard operation
html2pdf.js copied to clipboard

@media print (or @media not print) in Css ignored

Open JVApen opened this issue 7 years ago • 15 comments

When using @media print and @media not print in the CSS in order to get rid of color, images ... the generated pdf will ignore these.

On https://jsfiddle.net/wb2tgm6f/l the generated pdf still includes the

as visible text

JVApen avatar Feb 17 '18 17:02 JVApen

Have you found a solution or a workaround for using @media print ?

ghost avatar Feb 19 '18 09:02 ghost

No, I'm experimenting with this library to see what is possible to automate some manual actions. Right now not usable as is does not give me the same PDFs as if I would print them.

JVApen avatar Feb 19 '18 22:02 JVApen

This isn't printing, so the print media query won't get triggered. The workaround I've used is through the css preprocessor I'm using, I duplicate the print styles on both @media print and body.print. Then I add the print class to the body right before triggering html2pdf, and then removing it right after.

cwallen avatar Feb 20 '18 19:02 cwallen

Native Support would be great, but good workaround cwallen! Thanks!

meincms avatar Apr 12 '18 13:04 meincms

Is there any plans to add support for this?

maxshuty avatar Jul 12 '18 15:07 maxshuty

I appreciate @cwallen's workaround. That's probably a good option in most cases. In my case I have code that's meant to work with whatever CSS others have written (specifically, I have a WordPress plugin for making a printing a page, and I'd like to also have the option to easily create a PDF from the page).

This isn't printing, so the print media query won't get triggered.

This is technically correct, but IMO creating a PDF from the web page is effectively printing... you're just printing to PDF. (Which is what some browsers, like Chrome, call it.) We're using the page-break CSS styles when creating the PDFs, which, to my knowledge, only apply to printing. IMO it would be great if we could use the rest of the print media styles too.

mnelson4 avatar Nov 15 '18 04:11 mnelson4

Hello!

  1. Thanks for the suggestion @JVApen, I'll look into adding the feature.
  2. Thanks for the workaround @cwallen!
  3. @mnelson4 yes, creating a PDF is like printing, and it would be best to support printing-related features. That said, if we want the feature then we have to implement it!

I've investigated and I think this is doable. It would require altering the stylesheets in html2canvas's cloned document, which we can access with html2canvas's onclone callback. In there, we could put something like:

// Function to remove a specified medium (e.g. 'print').
function deleteMedium(item, medium) {
    if (item.media && item.media.toString().toLowerCase().indexOf(medium) !== -1) {
        item.media.deleteMedium(medium);
    }
}

// Loop through each stylesheet.
Array.prototype.forEach.call(document.styleSheets, function (styleSheet) {
    // Remove any print rules on the stylesheet itself.
    deleteMedium(styleSheet, 'print');

    // Loop through each rule and remove any print rules.
    var rules = styleSheet.cssRules || styleSheet.rules;
    Array.prototype.forEach.call(rules, function (rule) {
        deleteMedium(rule, 'print');
    });
});

Needs testing in all relevant browsers. More info here - I stripped down the example code there to just what we would need.

Not sure if there's any possible optimisation - two thoughts:

  1. The linked example doesn't traverse the stylesheet if it has a media= value that doesn't contain the desired medium (e.g. print). I don't know enough about it to say whether that's safe.
  2. Also, could skip the stylesheet entirely if it did have the desired medium (because why use @media print inside of a media="print" stylesheet?). But again, I don't know all the details.

I'll put this on the to-do list. I'm planning another change that will require tapping into html2canvas's onclone, so I'll attach this to that project.

eKoopmans avatar Jan 26 '19 13:01 eKoopmans

@cwallen the work around is fine, however I am using display:none for several elements which is causing UI distortion when I add the class before generating pdf. Could you please give some advice guys?

@eKoopmans when can we have this enhancement?

KulkarniSiddhesh avatar Jun 19 '19 09:06 KulkarniSiddhesh

This isn't printing, so the print media query won't get triggered. The workaround I've used is through the css preprocessor I'm using, I duplicate the print styles on both @media print and body.print. Then I add the print class to the body right before triggering html2pdf, and then removing it right after.

Don't know why, but this does not work for me... 😭

var source = document.getElementById('body');
source.classList.add('print');
html2pdf(source, {
     filename: 'Test.pdf',
     image: { type: 'jpeg', quality: 1 },
     html2canvas: { scale: 2 },
     jsPDF: { unit: 'mm', format: 'A4', orientation: 'landscape', putOnlyUsedFonts: true }
});
source.classList.remove('print');

Do anybody found other workarounds?

pietrogregorini avatar Nov 09 '19 18:11 pietrogregorini

Okay, forgot it... I had to run the removal of class after html2pdf function had completely run.

var source = document.getElementById('body');
source.classList.add('print');
html2pdf(source, {
     filename: 'Test.pdf',
     image: { type: 'jpeg', quality: 1 },
     html2canvas: { scale: 2 },
     jsPDF: { unit: 'mm', format: 'A4', orientation: 'landscape', putOnlyUsedFonts: true }
}).then(function(){
     source.classList.remove('print');
});

It's not perfect (there's a little subtle change in layout, of course) but whatever... 😉

pietrogregorini avatar Nov 09 '19 18:11 pietrogregorini

@eKoopmans has there been any updates or plans to implement the work you described?

maxshuty avatar Jun 10 '21 16:06 maxshuty

This isn't printing, so the print media query won't get triggered. The workaround I've used is through the css preprocessor I'm using, I duplicate the print styles on both @media print and body.print. Then I add the print class to the body right before triggering html2pdf, and then removing it right after.

If someone is wondering how to do this, here is how I did it, simply wrap your classes with a mixin like I have in this fiddle.

maxshuty avatar Jul 23 '21 18:07 maxshuty

Here trying the same, sad that there seems no way to trigger a media query manually.

Can someone include an example of @cwallen's workaround? I'm not that good at CSS and i can't seem to understand it 😅 Ideally i want to be able to toggle the CSS for all children of a node, so they can adapt to the printing style.

Only idea for now is iterating through all of these is programmatically doing this modification for each, and then rollback.

MartinCura avatar Aug 15 '22 17:08 MartinCura

Well i'm not sure if this is the correct interpretation of what you guys were saying but i solved it with a child selector, made prettier in Tailwind (which i'm using) by a plugin to add as a new pdf: variant. Pretty happy with how simple it turned out.

Code (Next.js with TailwindCSS):

// tailwind.config.js
...
  plugins: [
    plugin(function ({ addVariant }) {
      // Add a `pdf` variant, ie. `pdf:bg-white`, that applies when
      // element is element or is child of element with class "pdf"
      addVariant("pdf", ['&[class~="pdf"]', ".pdf &"]);
    }),
  ],
// MyComponent.tsx
        <Col id="pdf-canvas" xl="8" className="pdf:max-w-none pdf:flex-1">
          <Row> {/* ... */} </Row>

          <Row className="pdf:hidden"> {/* ... */}  </Row>
        </Col>
// pdf.ts
export const generatePDF = async (
  filename: string = "Player report.pdf"
) => {
  // deferred import of html2pdf.js
  const html2pdf = (await import("html2pdf.js")).default;

  const canvas = document.querySelector("#pdf-canvas");
  if (!canvas) {
    console.warn("Did not found #pdf-canvas, nothing to do");
    return;
  }

  const activateCSSForPDF = () => document.body.classList.add("pdf");
  const deactivateCSSForPDF = () => document.body.classList.remove("pdf");

  activateCSSForPDF();

  const opts = {
    filename,
    margin: 7,
    image: { type: "jpeg", quality: 0.95 },
    html2canvas: {
      scale: 2, // higher quality
      windowWidth: 1024, // simulate a browser size with this width
    },
    jsPDF: { orientation: "portrait" },
  };
  html2pdf()
    .set(opts)
    .from(canvas)
    .save()
    .finally(() => {
      deactivateCSSForPDF();
    });
};

MartinCura avatar Aug 15 '22 19:08 MartinCura

I've used this script and it's working fine! it's just to make a promise out of the html2pdf() method and then do whatever I want after it. document.querySelector('.download').addEventListener('click', function () { var element = document.getElementById('receipt'); element.style.display = "block";

// Call html2pdf() and save the Promise object that it returns
var pdfPromise = html2pdf().from(element).save();

// Wait for the PDF generation to complete before hiding the element
pdfPromise.then(function () {
    element.style.display = "none";
});

});

mostafaelhussainy avatar Mar 21 '23 09:03 mostafaelhussainy