html2pdf.js
html2pdf.js copied to clipboard
@media print (or @media not print) in Css ignored
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
Have you found a solution or a workaround for using @media print ?
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.
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.
Native Support would be great, but good workaround cwallen! Thanks!
Is there any plans to add support for this?
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.
Hello!
- Thanks for the suggestion @JVApen, I'll look into adding the feature.
- Thanks for the workaround @cwallen!
- @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:
- 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. - Also, could skip the stylesheet entirely if it did have the desired medium (because why use
@media print
inside of amedia="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.
@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?
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
andbody.print
. Then I add the
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?
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... 😉
@eKoopmans has there been any updates or plans to implement the work you described?
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
andbody.print
. Then I add the
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.
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.
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();
});
};
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";
});
});