Recently received a demand, it involves web printing, thought the browser’s own execution window. The print () method of obtaining the print control, didn’t think it is not so simple, not only in the print preview style is wrong, even can’t show the whole content, more than the content of the page only shows a page, it obviously can’t meet the demand of project!

Therefore, relevant optimization schemes are studied and sorted out as follows:

Window.print () default effect defect

1. Print control is not paginated by default, only one page is displayed

2. Dom layouts and styles can easily be misplaced and lost

3. I want to print locally, but the default is to get the entire body.innerHTML content

Print style optimization

This is done by adding specific CSS code for printers and previews to adjust the effect. CSS is introduced in the following ways

Four ways to load the print style

1.<link rel="stylesheet" href="" media="print">

2.@import url print

Overview @import CSS@ rules for importing style rules from other style sheets. These rules must precede all other types of rules except the @charset rule; Because it is not a nested statement, @import cannot be used in the rules of conditional groups. Thus, user agents can avoid retrieving resources for unsupported media types, and authors can specify media-dependent @import rules. These conditions import comma-separated media queries specified after the URI. In the absence of any media queries, the import is unconditional. Specifies that all media have the same effect. Grammar @ import url; @import url list-of-media-queries;Copy the code

Media =”print”

4. Query information through @media

Remove header footer

<style> @media print { @page { margin: 0; // You can control the print layout (four margins)} body {border: 1px solid #999; } } </style>Copy the code

@ page introduction

The @Page rule is used to modify certain CSS properties when printing a document. Orphans,widow, and Page breaks of the document You may not modify all CSS attributes using the @page rule, but rather may modify margin,orphans,widow, and Page breaks of the document. Changes to other attributes are not valid.

@page {
  margin: 1cm;
}

@page :first {
  margin: 2cm;
}
Copy the code

Control the paging

Page-break-before: Controls whether to page before the specified element

Page-break-after: Controls whether paging occurs after the specified element

Page-break-inside Controls whether page breaks can be inserted in the specified element

Optional parameters: always | auto | get | left | right | inherit

Example:

<style>
@media print {
  @page {
    margin: 0;
  }
  body {
    border: 1px solid #999;
  }
  p {
    page-break-after: always;
  }
}
</style>
Copy the code

Package print.js to implement local printing

Since we’re printing the contents of the body, we can manually create a DOM element that replaces the body when we call print(), which has two lifecycle hooks, Beforeprint and AfterPrint, replace the DOM before printing to print the DOM I want, and then restore the PREVIOUS DOM.

I found a case study on Github and tested it

/ / https://github.com/xyl66/vuePlugs_printjs/blob/master/print.js / / Print the class attribute, method definition / * eslint - disable * / const Print = function (dom, options) { if (! (this instanceof Print)) return new Print(dom, options); this.options = this.extend({ 'noPrint': '.no-print' }, options); if ((typeof dom) === "string") { this.dom = document.querySelector(dom); } else { this.isDOM(dom) this.dom = this.isDOM(dom) ? dom : dom.$el; } this.init(); }; Print.prototype = { init: function () { var content = this.getStyle() + this.getHtml(); this.writeIframe(content); }, extend: function (obj, obj2) { for (var k in obj2) { obj[k] = obj2[k]; } return obj; }, getStyle: function () { var str = "", styles = document.querySelectorAll('style,link'); for (var i = 0; i < styles.length; i++) { str += styles[i].outerHTML; } str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none; }</style>"; return str; }, getHtml: function () { var inputs = document.querySelectorAll('input'); var textareas = document.querySelectorAll('textarea'); var selects = document.querySelectorAll('select'); for (var k = 0; k < inputs.length; k++) { if (inputs[k].type == "checkbox" || inputs[k].type == "radio") { if (inputs[k].checked == true) { inputs[k].setAttribute('checked', "checked") } else { inputs[k].removeAttribute('checked') } } else if (inputs[k].type == "text") { inputs[k].setAttribute('value', inputs[k].value) } else { inputs[k].setAttribute('value', inputs[k].value) } } for (var k2 = 0; k2 < textareas.length; k2++) { if (textareas[k2].type == 'textarea') { textareas[k2].innerHTML = textareas[k2].value } } for (var k3 = 0; k3 < selects.length; k3++) { if (selects[k3].type == 'select-one') { var child = selects[k3].children; for (var i in child) { if (child[i].tagName == 'OPTION') { if (child[i].selected == true) { child[i].setAttribute('selected', "Selected ")} else {child[I].removeAttribute('selected')}}}}} https://github.com/xyl66/vuePlugs_printjs/issues/36 let outerHTML = this.wrapperRefDom(this.dom).outerHTML return outerHTML; }, // loop through the parent element to wrap the current element that needs to be printed // prevent root CSS selectors from not working wrapperRefDom: Function (refDom) {let prevDom = null let currDom = refDom // If (! this.isInBody(currDom)) return currDom while (currDom) { if (prevDom) { let element = currDom.cloneNode(false) element.appendChild(prevDom) prevDom = element } else { prevDom = currDom.cloneNode(true) } currDom = currDom.parentElement } return prevDom }, writeIframe: function (content) { var w, doc, iframe = document.createElement('iframe'), f = document.body.appendChild(iframe); iframe.id = "myIframe"; //iframe.style = "position:absolute; width:0; height:0; top:-10px; left:-10px;" ; iframe.setAttribute('style', 'position:absolute; width:0; height:0; top:-10px; left:-10px; '); w = f.contentWindow || f.contentDocument; doc = f.contentDocument || f.contentWindow.document; doc.open(); doc.write(content); doc.close(); var _this = this iframe.onload = function(){ _this.toPrint(w); setTimeout(function () { document.body.removeChild(iframe) }, 100) } }, toPrint: function (frameWindow) { try { setTimeout(function () { frameWindow.focus(); try { if (! frameWindow.document.execCommand('print', false, null)) { frameWindow.print(); } } catch (e) { frameWindow.print(); } frameWindow.close(); }, 10); } catch (err) { console.log('err', err); }}, // Check if an element is a descendant of the body element and not the body element itself isInBody: function (node) {return (node === document.body)? false : document.body.contains(node); }, isDOM: (typeof HTMLElement === 'object') ? function (obj) { return obj instanceof HTMLElement; } : function (obj) { return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'; }}; const MyPlugin = {} MyPlugin.install = function (Vue, options) { // 4. Prototype.$print = print} export default MyPluginCopy the code

Its more than 100 lines have achieved the effect described just now, but its function is relatively simple, and it does not support the configuration of some common parameters such as custom style overwriting. Moreover, I found a bug, that is, 2 pages are displayed by default, but there is very little content at this time, resulting in another blank page. Nevertheless can expand on this foundation, still good.

CLODOP print control

It is said to be very powerful and can print silently, but does not support MAC OS, pass website

Ultimate solution: printJS

This is the solution I finally settled on. Compared to the simple JS wrapper function above, it provides more configuration and supports PDF, HTML, image, JSON, raw-HTML printing.

Making the address

It’s also easy to use:

Prototype.$printJS = printJS // HTML $printJS({printable: 'elementId',type: 'HTML ', : ['*']})Copy the code

I once encountered a bug in this scheme when USING it, because I used the image format and converted webpage elements into PNG images based on HTML-to-image, and then used it

this.$printJS({printable: image-url,type: 'image'})

Copy the code

To print, there is an advantage of using images to print, is the style will not be misaligned, but there is an error: CSS cross-domain, the problem is the HTML-to-image plug-in, do not use this plug-in will not have this bug.

CSS cross-domain is often due to the use of the LINK tag inside JS to introduce CSS styles. With the increasingly strict security requirements of browsers, it is necessary to configure crossOrigin=”anonymous” on the link.

There is another problem. When I use the Intranet CDN, all CSS styles are lost in the print preview. I don’t know the reason yet, so I give up using the CDN form first.

conclusion

The function of web page printing has been very common in some IMS systems. The above several optimization schemes are the results of temporary research after recent encounter, which have met our project requirements. There may be some deficiencies, so they are only for reference.


Collection: My Github blog and case source code

Team blog: SAIC Insurance Blog