Last active
June 22, 2020 03:10
-
-
Save ulises-jeremias/39d7a50ce7d00a9c7984a4ba9e348a45 to your computer and use it in GitHub Desktop.
React to Print
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.print { | |
@media print { | |
@page { | |
size: A4; /* auto is the initial value */ | |
margin: 0; /* this affects the margin in the printer settings */ | |
} | |
html, body { | |
margin: 0 !important; | |
} | |
} | |
&:last-child { | |
page-break-after: auto; | |
} | |
} | |
.print-no-mount { | |
display: none !important; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { findDOMNode } from 'react-dom' | |
import PropTypes from 'prop-types' | |
class ReactToPrint extends Component { | |
constructor(props) { | |
super(props) | |
const { content, on } = this.props | |
if (on === 'mount') { | |
const contentEl = content() | |
// eslint-disable-next-line react/no-find-dom-node | |
const contentNodes = findDOMNode(contentEl) | |
this.state = { | |
contentNodes | |
} | |
} else { | |
this.state = { | |
contentNodes: null | |
} | |
} | |
this.handlePrint = this.handlePrint.bind(this) | |
} | |
componentDidMount() { | |
const { content, on } = this.props | |
if (on === 'click') { | |
const contentEl = content() | |
this.setState(() => ({ | |
// eslint-disable-next-line react/no-find-dom-node | |
contentNodes: findDOMNode(contentEl) | |
})) | |
} | |
} | |
handlePrint() { | |
const { | |
bodyClass, | |
copyStyles, | |
pageStyle, | |
} = this.props | |
const { contentNodes } = this.state | |
if (!contentNodes) { | |
return null | |
} | |
let printWindow = document.createElement('iframe') | |
printWindow.style.position = 'absolute' | |
printWindow.style.top = '-1000px' | |
printWindow.style.left = '-1000px' | |
const linkNodes = document.querySelectorAll('link[rel="stylesheet"]') | |
this.linkTotal = linkNodes.length || 0 | |
this.linkLoaded = 0 | |
const markLoaded = (type) => { | |
void type | |
this.linkLoaded++ | |
if (this.linkLoaded === this.linkTotal) { | |
this.triggerPrint(printWindow) | |
} | |
} | |
printWindow.onload = () => { | |
/* IE11 support */ | |
if (window.navigator && window.navigator.userAgent.includes('Trident/7.0')) { | |
printWindow.onload = null | |
} | |
let domDoc = printWindow.contentDocument || printWindow.contentWindow.document | |
const srcCanvasEls = [...contentNodes.querySelectorAll('canvas')] | |
domDoc.open() | |
domDoc.write(contentNodes.outerHTML) | |
domDoc.close() | |
/* remove date/time from top */ | |
const defaultPageStyle = pageStyle === undefined ? | |
'@page { size: auto margin: 0mm } @media print { body { -webkit-print-color-adjust: exact } }' : | |
pageStyle | |
let styleEl = domDoc.createElement('style') | |
styleEl.appendChild(domDoc.createTextNode(defaultPageStyle)) | |
domDoc.head.appendChild(styleEl) | |
if (bodyClass.length) { | |
bodyClass.split(' ').forEach(className => domDoc.body.classList.add(className)) | |
} | |
const canvasEls = domDoc.querySelectorAll('canvas') | |
Array.from(canvasEls).forEach((node, index) => { | |
node.getContext('2d').drawImage(srcCanvasEls[index], 0, 0) | |
}) | |
if (copyStyles !== false) { | |
const headEls = document.querySelectorAll('style, link[rel="stylesheet"]') | |
Array.from(headEls).forEach((node, index) => { | |
let newHeadEl = domDoc.createElement(node.tagName) | |
let styleCSS = '' | |
if (node.tagName === 'STYLE') { | |
if (node.sheet) { | |
for (let i = 0; i < node.sheet.cssRules.length; i++) { | |
styleCSS += node.sheet.cssRules[i].cssText + '\r\n' | |
} | |
newHeadEl.setAttribute('id', `react-to-print-${index}`) | |
newHeadEl.appendChild(domDoc.createTextNode(styleCSS)) | |
} | |
} else { | |
let attributes = [...node.attributes] | |
attributes.forEach(attr => { | |
newHeadEl.setAttribute(attr.nodeName, attr.nodeValue) | |
}) | |
newHeadEl.onload = markLoaded.bind(null, 'link') | |
newHeadEl.onerror = markLoaded.bind(null, 'link') | |
} | |
domDoc.head.appendChild(newHeadEl) | |
}) | |
} | |
if (this.linkTotal === 0 || copyStyles === false) { | |
this.triggerPrint(printWindow) | |
} | |
} | |
document.body.appendChild(printWindow) | |
} | |
removeWindow(target) { | |
void this | |
setTimeout(() => { | |
target.parentNode.removeChild(target) | |
}, 500) | |
} | |
triggerPrint(target) { | |
const { onBeforePrint, onAfterPrint } = this.props | |
if (onBeforePrint) { | |
onBeforePrint() | |
} | |
setTimeout(() => { | |
target.contentWindow.focus() | |
target.contentWindow.print() | |
this.removeWindow(target) | |
if (onAfterPrint) { | |
onAfterPrint() | |
} | |
}, 500) | |
} | |
render() { | |
const { on, trigger, content } = this.props | |
if (!content) { | |
return null | |
} | |
if (on !== 'click') { | |
this.handlePrint() | |
return null | |
} | |
return React.cloneElement(trigger(), { | |
ref: el => this.triggerRef = el, | |
onClick: this.handlePrint | |
}) | |
} | |
} | |
ReactToPrint.propTypes = { | |
/** Copy styles over into print window. default: true */ | |
copyStyles: PropTypes.bool, | |
/** Trigger action used to open browser print */ | |
trigger: PropTypes.func, | |
/** Content to be printed */ | |
content: PropTypes.func.isRequired, | |
/** Callback function to trigger before print */ | |
onBeforePrint: PropTypes.func, | |
/** Callback function to trigger after print */ | |
onAfterPrint: PropTypes.func, | |
/** Optional class to pass to the print window body */ | |
bodyClass: PropTypes.string, | |
on: PropTypes.oneOf(['click', 'mount']), | |
pageStyle: PropTypes.oneOfType([ | |
PropTypes.string, | |
PropTypes.object, | |
]), | |
// closeAfterPrint: PropTypes.bool, | |
} | |
ReactToPrint.defaultProps = { | |
copyStyles: true, | |
// closeAfterPrint: true, | |
bodyClass: '', | |
onBeforePrint: null, | |
onAfterPrint: null, | |
trigger: null, | |
on: 'click', | |
pageStyle: null, | |
} | |
export default ReactToPrint |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useRef } from 'react' | |
import ReactToPrint from './ReactToPrint' | |
const Example = () => { | |
const printableComponentRef = useRef(null); | |
return ( | |
{printableComponentRef && ( | |
<ReactToPrint | |
bodyClass='print' | |
on='mount' | |
content={() => printableComponentRef} | |
/> | |
)} | |
<div className='print-no-mount'> | |
<div ref={printableComponentRef} /> | |
</div> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment